mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge pull request #4987 from netbox-community/4982-apiselect-improvements
Closes #4982: Overhaul the APISelect widget and support ObjectVar filtering
This commit is contained in:
@ -163,12 +163,46 @@ In the example above, selecting the choice labeled "North" will submit the value
|
||||
|
||||
### ObjectVar
|
||||
|
||||
A NetBox object of a particular type, identified by the associated queryset.
|
||||
A particular object within NetBox. Each ObjectVar must specify a particular model, and allows the user to select one of the available instances. ObjectVar accepts several arguments, listed below.
|
||||
|
||||
* `queryset` - The base [Django queryset](https://docs.djangoproject.com/en/stable/topics/db/queries/) for the model
|
||||
* `model` - The model class
|
||||
* `display_field` - The name of the REST API object field to display in the selection list (default: `'name'`)
|
||||
* `query_params` - A dictionary of query parameters to use when retrieving available options (optional)
|
||||
* `null_option` - A label representing a "null" or empty choice (optional)
|
||||
|
||||
!!! warning
|
||||
Because the available options for this field are populated using the REST API, any filtering or exclusions performed on the specified queryset will not have any effect. While it is possible to influence the manner in which field options are populated using NetBox's `APISelect` widget, please note that this component is not officially supported and is planned to be replaced in a future release.
|
||||
The `display_field` argument is useful when referencing a model which does not have a `name` field. For example, when displaying a list of device types, you would likely use the `model` field:
|
||||
|
||||
```python
|
||||
device_type = ObjectVar(
|
||||
model=DeviceType,
|
||||
display_field='model'
|
||||
)
|
||||
```
|
||||
|
||||
To limit the selections available within the list, additional query parameters can be passed as the `query_params` dictionary. For example, to show only devices with an "active" status:
|
||||
|
||||
```python
|
||||
device = ObjectVar(
|
||||
model=Device,
|
||||
query_params={
|
||||
'status': 'active'
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Multiple values can be specified by assigning a list to the dictionary key. It is also possible to reference the value of other fields in the form by prepending a dollar sign (`$`) to the variable's name.
|
||||
|
||||
```python
|
||||
region = ObjectVar(
|
||||
model=Region
|
||||
)
|
||||
site = ObjectVar(
|
||||
model=Site,
|
||||
query_params={
|
||||
'region_id': '$region'
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### MultiObjectVar
|
||||
|
||||
@ -207,9 +241,8 @@ These variables are presented as a web form to be completed by the user. Once su
|
||||
from django.utils.text import slugify
|
||||
|
||||
from dcim.choices import DeviceStatusChoices, SiteStatusChoices
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Site
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from extras.scripts import *
|
||||
from utilities.forms import APISelect
|
||||
|
||||
|
||||
class NewBranchScript(Script):
|
||||
@ -225,12 +258,17 @@ class NewBranchScript(Script):
|
||||
switch_count = IntegerVar(
|
||||
description="Number of access switches to create"
|
||||
)
|
||||
manufacturer = ObjectVar(
|
||||
model=Manufacturer,
|
||||
required=False
|
||||
)
|
||||
switch_model = ObjectVar(
|
||||
description="Access switch model",
|
||||
queryset=DeviceType.objects.all(),
|
||||
widget=APISelect(
|
||||
display_field='model'
|
||||
)
|
||||
model=DeviceType,
|
||||
display_field='model',
|
||||
query_params={
|
||||
'manufacturer_id': '$manufacturer'
|
||||
}
|
||||
)
|
||||
|
||||
def run(self, data, commit):
|
||||
|
@ -8,9 +8,9 @@ from extras.models import Tag
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField,
|
||||
CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField,
|
||||
StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||
add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DatePicker,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SmallTextarea, SlugField, StaticSelect2,
|
||||
StaticSelect2Multiple, TagFilterField,
|
||||
)
|
||||
from .choices import CircuitStatusChoices
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
@ -106,21 +106,15 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
asn = forms.IntegerField(
|
||||
required=False,
|
||||
@ -271,18 +265,12 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
||||
type = DynamicModelMultipleChoiceField(
|
||||
queryset=CircuitType.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
provider = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=CircuitStatusChoices,
|
||||
@ -292,21 +280,15 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
commit_rate = forms.IntegerField(
|
||||
required=False,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -290,42 +290,27 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
role = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
platform = DynamicModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
cluster_group = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
cluster_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
@ -335,26 +320,17 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
tenant_group = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tenant = DynamicModelMultipleChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tag = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
@ -413,9 +389,9 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
|
||||
user = DynamicModelMultipleChoiceField(
|
||||
queryset=User.objects.all(),
|
||||
required=False,
|
||||
display_field='username',
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/users/users/',
|
||||
display_field='username'
|
||||
)
|
||||
)
|
||||
changed_object_type = forms.ModelChoiceField(
|
||||
|
@ -170,28 +170,42 @@ class ChoiceVar(ScriptVariable):
|
||||
class ObjectVar(ScriptVariable):
|
||||
"""
|
||||
A single object within NetBox.
|
||||
|
||||
:param model: The NetBox model being referenced
|
||||
:param display_field: The attribute of the returned object to display in the selection list (default: 'name')
|
||||
:param query_params: A dictionary of additional query parameters to attach when making REST API requests (optional)
|
||||
:param null_option: The label to use as a "null" selection option (optional)
|
||||
"""
|
||||
form_field = DynamicModelChoiceField
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
def __init__(self, model=None, queryset=None, display_field='name', query_params=None, null_option=None, *args,
|
||||
**kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Queryset for field choices
|
||||
self.field_attrs['queryset'] = queryset
|
||||
# Set the form field's queryset. Support backward compatibility for the "queryset" argument for now.
|
||||
if model is not None:
|
||||
self.field_attrs['queryset'] = model.objects.all()
|
||||
elif queryset is not None:
|
||||
warnings.warn(
|
||||
f'{self}: Specifying a queryset for ObjectVar is no longer supported. Please use "model" instead.'
|
||||
)
|
||||
self.field_attrs['queryset'] = queryset
|
||||
else:
|
||||
raise TypeError('ObjectVar must specify a model')
|
||||
|
||||
self.field_attrs.update({
|
||||
'display_field': display_field,
|
||||
'query_params': query_params,
|
||||
'null_option': null_option,
|
||||
})
|
||||
|
||||
|
||||
class MultiObjectVar(ScriptVariable):
|
||||
class MultiObjectVar(ObjectVar):
|
||||
"""
|
||||
Like ObjectVar, but can represent one or more objects.
|
||||
"""
|
||||
form_field = DynamicModelMultipleChoiceField
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Queryset for field choices
|
||||
self.field_attrs['queryset'] = queryset
|
||||
|
||||
|
||||
class FileVar(ScriptVariable):
|
||||
"""
|
||||
|
@ -1,5 +1,4 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
|
||||
from dcim.models import Device, Interface, Rack, Region, Site
|
||||
@ -10,10 +9,9 @@ from extras.models import Tag
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField,
|
||||
CSVModelChoiceField, CSVModelForm, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||
BOOLEAN_WITH_BLANK_CHOICES,
|
||||
add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
|
||||
DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, ReturnURLForm,
|
||||
SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
from .choices import *
|
||||
@ -217,10 +215,7 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
queryset=RIR.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
label='RIR',
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
label='RIR'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
@ -255,41 +250,32 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF'
|
||||
label='VRF',
|
||||
display_field='display_name'
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'vlan_group': 'site_id',
|
||||
'vlan': 'site_id',
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
vlan_group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False,
|
||||
label='VLAN group',
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'vlan': 'group_id'
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
label='VLAN',
|
||||
widget=APISelect(
|
||||
display_field='display_name'
|
||||
)
|
||||
display_field='display_name',
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'group_id': '$vlan_group',
|
||||
}
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=Role.objects.all(),
|
||||
@ -469,9 +455,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF',
|
||||
widget=APISelectMultiple(
|
||||
null_option=True,
|
||||
)
|
||||
null_option='Global'
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=PrefixStatusChoices,
|
||||
@ -481,31 +465,22 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
role = DynamicModelMultipleChoiceField(
|
||||
queryset=Role.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
is_pool = forms.NullBooleanField(
|
||||
required=False,
|
||||
@ -525,29 +500,26 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'interface': 'device_id'
|
||||
}
|
||||
)
|
||||
display_field='display_name'
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
virtual_machine = DynamicModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'vminterface': 'virtual_machine_id'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
vminterface = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
required=False,
|
||||
label='Interface'
|
||||
label='Interface',
|
||||
query_params={
|
||||
'virtual_machine_id': '$virtual_machine'
|
||||
}
|
||||
)
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
@ -557,56 +529,42 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
|
||||
nat_site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
label='Site',
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'nat_rack': 'site_id',
|
||||
'nat_device': 'site_id'
|
||||
}
|
||||
)
|
||||
label='Site'
|
||||
)
|
||||
nat_rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
display_field='display_name',
|
||||
filter_for={
|
||||
'nat_device': 'rack_id'
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true'
|
||||
}
|
||||
)
|
||||
display_field='display_name',
|
||||
null_option='None',
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
nat_device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
display_field='display_name',
|
||||
filter_for={
|
||||
'nat_inside': 'device_id'
|
||||
}
|
||||
)
|
||||
display_field='display_name',
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'rack_id': '$nat_rack',
|
||||
}
|
||||
)
|
||||
nat_vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF',
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'nat_inside': 'vrf_id'
|
||||
}
|
||||
)
|
||||
label='VRF'
|
||||
)
|
||||
nat_inside = DynamicModelChoiceField(
|
||||
queryset=IPAddress.objects.all(),
|
||||
required=False,
|
||||
label='IP Address',
|
||||
widget=APISelect(
|
||||
display_field='address'
|
||||
)
|
||||
display_field='address',
|
||||
query_params={
|
||||
'device_id': '$nat_device',
|
||||
'vrf_id': '$nat_vrf',
|
||||
}
|
||||
)
|
||||
primary_for_parent = forms.BooleanField(
|
||||
required=False,
|
||||
@ -920,9 +878,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF',
|
||||
widget=APISelectMultiple(
|
||||
null_option=True,
|
||||
)
|
||||
null_option='Global'
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=IPAddressStatusChoices,
|
||||
@ -980,22 +936,16 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region',
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -1007,18 +957,14 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'group': 'site_id'
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=Role.objects.all(),
|
||||
@ -1102,16 +1048,14 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'group': 'site_id'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
@ -1147,31 +1091,25 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region',
|
||||
'group_id': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
required=False,
|
||||
label='VLAN group',
|
||||
widget=APISelectMultiple(
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=VLANStatusChoices,
|
||||
@ -1182,10 +1120,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
queryset=Role.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -74,7 +74,7 @@ $(document).ready(function() {
|
||||
form.submit();
|
||||
});
|
||||
|
||||
// Parse URLs which may contain variable refrences to other field values
|
||||
// Parse URLs which may contain variable references to other field values
|
||||
function parseURL(url) {
|
||||
var filter_regex = /\{\{([a-z_]+)\}\}/g;
|
||||
var match;
|
||||
@ -87,7 +87,7 @@ $(document).ready(function() {
|
||||
rendered_url = rendered_url.replace(match[0], custom_attr);
|
||||
} else if (filter_field.val()) {
|
||||
rendered_url = rendered_url.replace(match[0], filter_field.val());
|
||||
} else if (filter_field.attr('nullable') == 'true') {
|
||||
} else if (filter_field.attr('data-null-option')) {
|
||||
rendered_url = rendered_url.replace(match[0], 'null');
|
||||
}
|
||||
}
|
||||
@ -123,7 +123,7 @@ $(document).ready(function() {
|
||||
|
||||
// API backed selection
|
||||
// Includes live search and chained fields
|
||||
// The `multiple` setting may be controled via a data-* attribute
|
||||
// The `multiple` setting may be controlled via a data-* attribute
|
||||
$('.netbox-select2-api').select2({
|
||||
allowClear: true,
|
||||
placeholder: "---------",
|
||||
@ -157,47 +157,23 @@ $(document).ready(function() {
|
||||
// Allow for controlling the brief setting from within APISelect
|
||||
parameters.brief = ( $(element).is('[data-full]') ? undefined : true );
|
||||
|
||||
// filter-for fields from a chain
|
||||
var attr_name = "data-filter-for-" + $(element).attr("name");
|
||||
var form = $(element).closest('form');
|
||||
var filter_for_elements = form.find("select[" + attr_name + "]");
|
||||
|
||||
filter_for_elements.each(function(index, filter_for_element) {
|
||||
var param_name = $(filter_for_element).attr(attr_name);
|
||||
var is_required = $(filter_for_element).attr("required");
|
||||
var is_nullable = $(filter_for_element).attr("nullable");
|
||||
var is_visible = $(filter_for_element).is(":visible");
|
||||
var value = $(filter_for_element).val();
|
||||
|
||||
if (param_name && is_visible) {
|
||||
if (value) {
|
||||
parameters[param_name] = value;
|
||||
} else if (is_required && is_nullable) {
|
||||
parameters[param_name] = "null";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Conditional query params
|
||||
// Attach any extra query parameters
|
||||
$.each(element.attributes, function(index, attr){
|
||||
if (attr.name.includes("data-conditional-query-param-")){
|
||||
var conditional = attr.name.split("data-conditional-query-param-")[1].split("__");
|
||||
var field = $("#id_" + conditional[0]);
|
||||
var field_value = conditional[1];
|
||||
|
||||
if ($('option:selected', field).attr('api-value') === field_value){
|
||||
var _val = attr.value.split("=");
|
||||
parameters[_val[0]] = _val[1];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Additional query params
|
||||
$.each(element.attributes, function(index, attr){
|
||||
if (attr.name.includes("data-additional-query-param-")){
|
||||
var param_name = attr.name.split("data-additional-query-param-")[1];
|
||||
if (attr.name.includes("data-query-param-")){
|
||||
var param_name = attr.name.split("data-query-param-")[1];
|
||||
|
||||
$.each($.parseJSON(attr.value), function(index, value) {
|
||||
// Referencing the value of another form field
|
||||
if (value.startsWith('$')) {
|
||||
let ref_field = $('#id_' + value.slice(1));
|
||||
if (ref_field.val() && ref_field.is(":visible")) {
|
||||
value = ref_field.val();
|
||||
} else if (ref_field.attr("required") && ref_field.attr("data-null-option")) {
|
||||
value = "null";
|
||||
} else {
|
||||
return true; // Skip if ref_field has no value
|
||||
}
|
||||
}
|
||||
if (param_name in parameters) {
|
||||
if (Array.isArray(parameters[param_name])) {
|
||||
parameters[param_name].push(value);
|
||||
@ -261,7 +237,7 @@ $(document).ready(function() {
|
||||
if (element.getAttribute('data-null-option') && data.previous === null) {
|
||||
results.unshift({
|
||||
id: 'null',
|
||||
text: 'None'
|
||||
text: element.getAttribute('data-null-option')
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ from extras.forms import (
|
||||
)
|
||||
from extras.models import Tag
|
||||
from utilities.forms import (
|
||||
APISelectMultiple, BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, SlugField, StaticSelect2Multiple, TagFilterField,
|
||||
BootstrapMixin, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
SlugField, TagFilterField,
|
||||
)
|
||||
from .constants import *
|
||||
from .models import Secret, SecretRole, UserKey
|
||||
@ -63,7 +63,8 @@ class SecretRoleCSVForm(CSVModelForm):
|
||||
|
||||
class SecretForm(BootstrapMixin, CustomFieldModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all()
|
||||
queryset=Device.objects.all(),
|
||||
display_field='display_name'
|
||||
)
|
||||
plaintext = forms.CharField(
|
||||
max_length=SECRET_PLAINTEXT_MAX_LENGTH,
|
||||
@ -178,10 +179,7 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
role = DynamicModelMultipleChoiceField(
|
||||
queryset=SecretRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
)
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -35,33 +35,3 @@
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var device_list = $('#id_devices');
|
||||
var disabled_indicator = device_list.attr('disabled-indicator');
|
||||
$('#id_search').autocomplete({
|
||||
source: function(request, response) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: netbox_api_path + 'dcim/devices/',
|
||||
data: 'q=' + request.term,
|
||||
beforeSend: function() {
|
||||
device_list.empty();
|
||||
},
|
||||
success: function(data) {
|
||||
response($.map(data.results, function(item) {
|
||||
var option = $("<option></option>").attr("value", item['id']).text(item['display_name']);
|
||||
if (disabled_indicator && item[disabled_indicator]) {
|
||||
option.attr("disabled", "disabled");
|
||||
}
|
||||
device_list.append(option);
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -5,8 +5,8 @@ from extras.forms import (
|
||||
)
|
||||
from extras.models import Tag
|
||||
from utilities.forms import (
|
||||
APISelect, APISelectMultiple, BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, TagFilterField,
|
||||
BootstrapMixin, CommentField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, SlugField, TagFilterField,
|
||||
)
|
||||
from .models import Tenant, TenantGroup
|
||||
|
||||
@ -106,10 +106,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
@ -122,18 +119,14 @@ class TenancyForm(forms.Form):
|
||||
tenant_group = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
'tenant': 'group_id',
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
query_params={
|
||||
'group_id': '$tenant_group'
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -153,20 +146,14 @@ class TenancyFilterForm(forms.Form):
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
filter_for={
|
||||
'tenant': 'group'
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
tenant = DynamicModelMultipleChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'group': '$tenant_group'
|
||||
}
|
||||
)
|
||||
|
@ -11,6 +11,7 @@ from django.db.models import Count
|
||||
from django.forms import BoundField
|
||||
from django.urls import reverse
|
||||
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.choices import unpack_grouped_choices
|
||||
from utilities.validators import EnhancedURLValidator
|
||||
from . import widgets
|
||||
@ -244,9 +245,58 @@ class TagFilterField(forms.MultipleChoiceField):
|
||||
|
||||
|
||||
class DynamicModelChoiceMixin:
|
||||
"""
|
||||
:param display_field: The name of the attribute of an API response object to display in the selection list
|
||||
:param query_params: A dictionary of additional key/value pairs to attach to the API request
|
||||
: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 brief_mode: Use the "brief" format (?brief=true) when making API requests (default)
|
||||
"""
|
||||
filter = django_filters.ModelChoiceFilter
|
||||
widget = widgets.APISelect
|
||||
|
||||
def __init__(self, display_field='name', query_params=None, null_option=None, disabled_indicator=None,
|
||||
brief_mode=True, *args, **kwargs):
|
||||
self.display_field = display_field
|
||||
self.query_params = query_params or {}
|
||||
self.null_option = null_option
|
||||
self.disabled_indicator = disabled_indicator
|
||||
self.brief_mode = brief_mode
|
||||
|
||||
# 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')
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = {
|
||||
'display-field': self.display_field,
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
# Toggle brief mode
|
||||
if not self.brief_mode:
|
||||
attrs['data-full'] = 'true'
|
||||
|
||||
# Attach any static query parameters
|
||||
for key, value in self.query_params.items():
|
||||
widget.add_query_param(key, value)
|
||||
|
||||
return attrs
|
||||
|
||||
def get_bound_field(self, form, field_name):
|
||||
bound_field = BoundField(form, self, field_name)
|
||||
|
||||
|
@ -79,29 +79,12 @@ class SelectWithDisabled(forms.Select):
|
||||
|
||||
class StaticSelect2(SelectWithDisabled):
|
||||
"""
|
||||
A static content using the Select2 widget
|
||||
|
||||
:param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the
|
||||
name of the filter-for field (child field) and the value is the name of the query param filter.
|
||||
A static <select> form widget using the Select2 library.
|
||||
"""
|
||||
|
||||
def __init__(self, filter_for=None, *args, **kwargs):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.attrs['class'] = 'netbox-select2-static'
|
||||
if filter_for:
|
||||
for key, value in filter_for.items():
|
||||
self.add_filter_for(key, value)
|
||||
|
||||
def add_filter_for(self, name, value):
|
||||
"""
|
||||
Add details for an additional query param in the form of a data-filter-for-* attribute.
|
||||
|
||||
:param name: The name of the query param
|
||||
:param value: The value of the query param
|
||||
"""
|
||||
self.attrs['data-filter-for-{}'.format(name)] = value
|
||||
|
||||
|
||||
class StaticSelect2Multiple(StaticSelect2, forms.SelectMultiple):
|
||||
@ -140,93 +123,31 @@ class APISelect(SelectWithDisabled):
|
||||
A select widget populated via an API call
|
||||
|
||||
:param api_url: API endpoint URL. Required if not set automatically by the parent field.
|
||||
:param display_field: (Optional) Field to display for child in selection list. Defaults to `name`.
|
||||
:param value_field: (Optional) Field to use for the option value in selection list. Defaults to `id`.
|
||||
:param disabled_indicator: (Optional) Mark option as disabled if this field equates true.
|
||||
:param filter_for: (Optional) A dict of chained form fields for which this field is a filter. The key is the
|
||||
name of the filter-for field (child field) and the value is the name of the query param filter.
|
||||
:param conditional_query_params: (Optional) A dict of URL query params to append to the URL if the
|
||||
condition is met. The condition is the dict key and is specified in the form `<field_name>__<field_value>`.
|
||||
If the provided field value is selected for the given field, the URL query param will be appended to
|
||||
the rendered URL. The value is the in the from `<param_name>=<param_value>`. This is useful in cases where
|
||||
a particular field value dictates an additional API filter.
|
||||
:param additional_query_params: Optional) A dict of query params to append to the API request. The key is the
|
||||
name of the query param and the value if the query param's value.
|
||||
:param null_option: If true, include the static null option in the selection list.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
api_url=None,
|
||||
display_field=None,
|
||||
value_field=None,
|
||||
disabled_indicator=None,
|
||||
filter_for=None,
|
||||
conditional_query_params=None,
|
||||
additional_query_params=None,
|
||||
null_option=False,
|
||||
full=False,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
def __init__(self, api_url=None, full=False, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.attrs['class'] = 'netbox-select2-api'
|
||||
if api_url:
|
||||
self.attrs['data-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/')) # Inject BASE_PATH
|
||||
if full:
|
||||
self.attrs['data-full'] = full
|
||||
if display_field:
|
||||
self.attrs['display-field'] = display_field
|
||||
if value_field:
|
||||
self.attrs['value-field'] = value_field
|
||||
if disabled_indicator:
|
||||
self.attrs['disabled-indicator'] = disabled_indicator
|
||||
if filter_for:
|
||||
for key, value in filter_for.items():
|
||||
self.add_filter_for(key, value)
|
||||
if conditional_query_params:
|
||||
for key, value in conditional_query_params.items():
|
||||
self.add_conditional_query_param(key, value)
|
||||
if additional_query_params:
|
||||
for key, value in additional_query_params.items():
|
||||
self.add_additional_query_param(key, value)
|
||||
if null_option:
|
||||
self.attrs['data-null-option'] = 1
|
||||
|
||||
def add_filter_for(self, name, value):
|
||||
"""
|
||||
Add details for an additional query param in the form of a data-filter-for-* attribute.
|
||||
|
||||
:param name: The name of the query param
|
||||
:param value: The value of the query param
|
||||
"""
|
||||
self.attrs['data-filter-for-{}'.format(name)] = value
|
||||
|
||||
def add_additional_query_param(self, name, value):
|
||||
def add_query_param(self, name, value):
|
||||
"""
|
||||
Add details for an additional query param in the form of a data-* JSON-encoded list attribute.
|
||||
|
||||
:param name: The name of the query param
|
||||
:param value: The value of the query param
|
||||
"""
|
||||
key = 'data-additional-query-param-{}'.format(name)
|
||||
key = f'data-query-param-{name}'
|
||||
|
||||
values = json.loads(self.attrs.get(key, '[]'))
|
||||
values.append(value)
|
||||
if type(value) is list:
|
||||
values.extend(value)
|
||||
else:
|
||||
values.append(value)
|
||||
|
||||
self.attrs[key] = json.dumps(values)
|
||||
|
||||
def add_conditional_query_param(self, condition, value):
|
||||
"""
|
||||
Add details for a URL query strings to append to the URL if the condition is met.
|
||||
The condition is specified in the form `<field_name>__<field_value>`.
|
||||
|
||||
:param condition: The condition for the query param
|
||||
:param value: The value of the query param
|
||||
"""
|
||||
self.attrs['data-conditional-query-param-{}'.format(condition)] = value
|
||||
|
||||
|
||||
class APISelectMultiple(APISelect, forms.SelectMultiple):
|
||||
|
||||
|
@ -13,10 +13,10 @@ from ipam.models import IPAddress, VLAN
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||
BulkRenameForm, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
|
||||
SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, BulkRenameForm, CommentField,
|
||||
ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea,
|
||||
StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from .choices import *
|
||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||
@ -166,39 +166,27 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
||||
type = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field='slug',
|
||||
)
|
||||
required=False
|
||||
)
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field='slug',
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
group = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field='slug',
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
@ -207,43 +195,32 @@ class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
"site": "region_id",
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
"rack": "site_id",
|
||||
"devices": "site_id",
|
||||
}
|
||||
)
|
||||
query_params={
|
||||
'region_id': '$region'
|
||||
}
|
||||
)
|
||||
rack = DynamicModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
"devices": "rack_id"
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None',
|
||||
display_field='display_name',
|
||||
query_params={
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
devices = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.filter(cluster__isnull=True),
|
||||
widget=APISelectMultiple(
|
||||
display_field='display_name',
|
||||
disabled_indicator='cluster'
|
||||
)
|
||||
queryset=Device.objects.all(),
|
||||
display_field='display_name',
|
||||
query_params={
|
||||
'site_id': '$site',
|
||||
'rack_id': '$rack',
|
||||
'cluster_id': 'null',
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -288,26 +265,20 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
cluster_group = DynamicModelChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
filter_for={
|
||||
"cluster": "group_id",
|
||||
},
|
||||
attrs={
|
||||
'nullable': 'true',
|
||||
}
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
cluster = DynamicModelChoiceField(
|
||||
queryset=Cluster.objects.all()
|
||||
queryset=Cluster.objects.all(),
|
||||
query_params={
|
||||
'group_id': '$cluster_group'
|
||||
}
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
additional_query_params={
|
||||
"vm_role": "True"
|
||||
}
|
||||
)
|
||||
query_params={
|
||||
"vm_role": "True"
|
||||
}
|
||||
)
|
||||
platform = DynamicModelChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
@ -444,11 +415,9 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
|
||||
vm_role=True
|
||||
),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
additional_query_params={
|
||||
"vm_role": "True"
|
||||
}
|
||||
)
|
||||
query_params={
|
||||
"vm_role": "True"
|
||||
}
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
@ -495,19 +464,13 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
cluster_type = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
cluster_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
@ -517,34 +480,25 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
required=False
|
||||
)
|
||||
site = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'region': '$region'
|
||||
}
|
||||
)
|
||||
role = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.filter(vm_role=True),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
additional_query_params={
|
||||
'vm_role': "True"
|
||||
}
|
||||
)
|
||||
null_option='None',
|
||||
query_params={
|
||||
'vm_role': "True"
|
||||
}
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=VirtualMachineStatusChoices,
|
||||
@ -555,10 +509,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
||||
queryset=Platform.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
value_field="slug",
|
||||
null_option=True,
|
||||
)
|
||||
null_option='None'
|
||||
)
|
||||
mac_address = forms.CharField(
|
||||
required=False,
|
||||
@ -575,24 +526,20 @@ class VMInterfaceForm(BootstrapMixin, forms.ModelForm):
|
||||
untagged_vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
tagged_vlans = DynamicModelMultipleChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -626,8 +573,8 @@ class VMInterfaceForm(BootstrapMixin, forms.ModelForm):
|
||||
# Add current site to VLANs query params
|
||||
site = virtual_machine.site
|
||||
if site:
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
@ -679,24 +626,20 @@ class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
|
||||
untagged_vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
tagged_vlans = DynamicModelMultipleChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -713,8 +656,8 @@ class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
|
||||
# Add current site to VLANs query params
|
||||
site = virtual_machine.site
|
||||
if site:
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
|
||||
|
||||
|
||||
class VMInterfaceCSVForm(CSVModelForm):
|
||||
@ -773,24 +716,20 @@ class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
untagged_vlan = DynamicModelChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
tagged_vlans = DynamicModelMultipleChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
display_field='display_name',
|
||||
full=True,
|
||||
additional_query_params={
|
||||
'site_id': 'null',
|
||||
},
|
||||
)
|
||||
display_field='display_name',
|
||||
brief_mode=False,
|
||||
query_params={
|
||||
'site_id': 'null',
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -808,8 +747,8 @@ class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
site = getattr(parent_obj.cluster, 'site', None)
|
||||
if site is not None:
|
||||
# Add current site to VLANs query params
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk)
|
||||
|
||||
|
||||
class VMInterfaceBulkRenameForm(BulkRenameForm):
|
||||
@ -824,17 +763,15 @@ class VMInterfaceFilterForm(forms.Form):
|
||||
cluster_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
label='Cluster',
|
||||
widget=APISelectMultiple(
|
||||
filter_for={
|
||||
'virtual_machine_id': 'cluster_id'
|
||||
}
|
||||
)
|
||||
label='Cluster'
|
||||
)
|
||||
virtual_machine_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
required=False,
|
||||
label='Virtual machine'
|
||||
label='Virtual machine',
|
||||
query_params={
|
||||
'cluster_id': '$cluster_id'
|
||||
}
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
required=False,
|
||||
|
Reference in New Issue
Block a user