mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Merge remote-tracking branch 'netbox-community/develop' into 2365-show-available-toggle
This commit is contained in:
commit
1c0de0093b
@ -80,6 +80,7 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
# User Groups for Permissions
|
# User Groups for Permissions
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`. You will also need to modify the import line to use `NestedGroupOfNamesType` instead of `GroupOfNamesType` .
|
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`. You will also need to modify the import line to use `NestedGroupOfNamesType` instead of `GroupOfNamesType` .
|
||||||
|
|
||||||
@ -117,6 +118,9 @@ AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
|||||||
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
|
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
|
||||||
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.
|
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Authentication will fail if the groups (the distinguished names) do not exist in the LDAP directory.
|
||||||
|
|
||||||
# Troubleshooting LDAP
|
# Troubleshooting LDAP
|
||||||
|
|
||||||
`supervisorctl restart netbox` restarts the Netbox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/supervisor/`.
|
`supervisorctl restart netbox` restarts the Netbox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/supervisor/`.
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
# v2.6.10 (FUTURE)
|
# v2.6.11 (2020-01-03)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3831](https://github.com/netbox-community/netbox/issues/3831) - Fix API-driven filter field rendering (#3812 regression)
|
||||||
|
* [#3833](https://github.com/netbox-community/netbox/issues/3833) - Add missing region filters for multiple objects
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# v2.6.10 (2020-01-02)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2233](https://github.com/netbox-community/netbox/issues/2233) - Add ability to move inventory items between devices
|
||||||
* [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results
|
* [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results
|
||||||
* [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses
|
* [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses
|
||||||
* [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception
|
* [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception
|
||||||
* [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts
|
* [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts
|
||||||
* [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets
|
* [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets
|
||||||
* [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items
|
* [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items
|
||||||
|
* [#3812](https://github.com/netbox-community/netbox/issues/3812) - Optimize size of pages containing a dynamic selection field
|
||||||
|
* [#3827](https://github.com/netbox-community/netbox/issues/3827) - Allow filtering console/power/interface connections by device ID
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
@ -17,6 +29,7 @@
|
|||||||
* [#3780](https://github.com/netbox-community/netbox/issues/3780) - Fix AttributeError exception in API docs
|
* [#3780](https://github.com/netbox-community/netbox/issues/3780) - Fix AttributeError exception in API docs
|
||||||
* [#3809](https://github.com/netbox-community/netbox/issues/3809) - Filter platform by manufacturer when editing devices
|
* [#3809](https://github.com/netbox-community/netbox/issues/3809) - Filter platform by manufacturer when editing devices
|
||||||
* [#3811](https://github.com/netbox-community/netbox/issues/3811) - Fix filtering of racks by group on device list
|
* [#3811](https://github.com/netbox-community/netbox/issues/3811) - Fix filtering of racks by group on device list
|
||||||
|
* [#3822](https://github.com/netbox-community/netbox/issues/3822) - Fix exception when editing a device bay (regression from #3596)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -18,6 +18,17 @@ class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='circuits__terminations__site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='circuits__terminations__site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='circuits__terminations__site',
|
field_name='circuits__terminations__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
|
@ -104,6 +104,18 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -302,6 +314,9 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
|||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url="/api/dcim/regions/",
|
api_url="/api/dcim/regions/",
|
||||||
value_field="slug",
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
|
@ -93,6 +93,17 @@ class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
|
|
||||||
|
|
||||||
class RackGroupFilter(NameSlugSearchFilterSet):
|
class RackGroupFilter(NameSlugSearchFilterSet):
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -125,6 +136,17 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -831,6 +853,28 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='device__site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='device__site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='device__site',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
label='Site (ID)',
|
||||||
|
)
|
||||||
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='device__site__slug',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Site name (slug)',
|
||||||
|
)
|
||||||
device_id = django_filters.ModelChoiceFilter(
|
device_id = django_filters.ModelChoiceFilter(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
label='Device (ID)',
|
label='Device (ID)',
|
||||||
@ -880,6 +924,17 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='master__site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='master__site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='master__site',
|
field_name='master__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
@ -935,7 +990,7 @@ class CableFilter(django_filters.FilterSet):
|
|||||||
device_id = MultiValueNumberFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
method='filter_device'
|
method='filter_device'
|
||||||
)
|
)
|
||||||
device = MultiValueNumberFilter(
|
device = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
field_name='device__name'
|
field_name='device__name'
|
||||||
)
|
)
|
||||||
@ -978,9 +1033,12 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
|
|||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
device = django_filters.CharFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
|
method='filter_device'
|
||||||
|
)
|
||||||
|
device = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
label='Device',
|
field_name='device__name'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -993,11 +1051,11 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset.filter(connected_endpoint__device__site__slug=value)
|
return queryset.filter(connected_endpoint__device__site__slug=value)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
Q(**{'{}__in'.format(name): value}) |
|
||||||
Q(connected_endpoint__device__name__icontains=value)
|
Q(**{'connected_endpoint__{}__in'.format(name): value})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1006,9 +1064,12 @@ class PowerConnectionFilter(django_filters.FilterSet):
|
|||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
device = django_filters.CharFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
|
method='filter_device'
|
||||||
|
)
|
||||||
|
device = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
label='Device',
|
field_name='device__name'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1021,11 +1082,11 @@ class PowerConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset.filter(_connected_poweroutlet__device__site__slug=value)
|
return queryset.filter(_connected_poweroutlet__device__site__slug=value)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
Q(**{'{}__in'.format(name): value}) |
|
||||||
Q(_connected_poweroutlet__device__name__icontains=value)
|
Q(**{'_connected_poweroutlet__{}__in'.format(name): value})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1034,9 +1095,12 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
device = django_filters.CharFilter(
|
device_id = MultiValueNumberFilter(
|
||||||
|
method='filter_device'
|
||||||
|
)
|
||||||
|
device = MultiValueCharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
label='Device',
|
field_name='device__name'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1052,11 +1116,11 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
Q(**{'{}__in'.format(name): value}) |
|
||||||
Q(_connected_interface__device__name__icontains=value)
|
Q(**{'_connected_interface__{}__in'.format(name): value})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1069,6 +1133,17 @@ class PowerPanelFilter(django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -1107,6 +1182,17 @@ class PowerFeedFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='power_panel__site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='power_panel__site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='power_panel__site',
|
field_name='power_panel__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
|
@ -364,6 +364,18 @@ class RackGroupCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class RackGroupFilterForm(BootstrapMixin, forms.Form):
|
class RackGroupFilterForm(BootstrapMixin, forms.Form):
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -635,11 +647,23 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
|
|
||||||
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Rack
|
model = Rack
|
||||||
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -651,16 +675,15 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
group_id = ChainedModelChoiceField(
|
group_id = FilterChoiceField(
|
||||||
label='Rack group',
|
queryset=RackGroup.objects.prefetch_related(
|
||||||
queryset=RackGroup.objects.prefetch_related('site'),
|
'site'
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
),
|
||||||
required=False,
|
label='Rack group',
|
||||||
|
null_label='-- None --',
|
||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url="/api/dcim/rack-groups/",
|
api_url="/api/dcim/rack-groups/",
|
||||||
null_option=True,
|
null_option=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
@ -3171,9 +3194,13 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=ColorSelect()
|
widget=ColorSelect()
|
||||||
)
|
)
|
||||||
device = forms.CharField(
|
device_id = FilterChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Device name'
|
label='Device',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url='/api/dcim/devices/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -3238,38 +3265,59 @@ class DeviceBayBulkRenameForm(BulkRenameForm):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
|
class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||||
site = forms.ModelChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
to_field_name='slug',
|
||||||
to_field_name='slug'
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
device = forms.CharField(
|
device_id = FilterChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Device name'
|
label='Device',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url='/api/dcim/devices/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
|
class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||||
site = forms.ModelChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
to_field_name='slug',
|
||||||
to_field_name='slug'
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
device = forms.CharField(
|
device_id = FilterChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Device name'
|
label='Device',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url='/api/dcim/devices/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
|
class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||||
site = forms.ModelChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False,
|
to_field_name='slug',
|
||||||
to_field_name='slug'
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
device = forms.CharField(
|
device_id = FilterChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Device name'
|
label='Device',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url='/api/dcim/devices/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -3285,9 +3333,12 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
|
'name', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
|
'device': APISelect(
|
||||||
|
api_url="/api/dcim/devices/"
|
||||||
|
),
|
||||||
'manufacturer': APISelect(
|
'manufacturer': APISelect(
|
||||||
api_url="/api/dcim/manufacturers/"
|
api_url="/api/dcim/manufacturers/"
|
||||||
)
|
)
|
||||||
@ -3323,9 +3374,19 @@ class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
queryset=InventoryItem.objects.all(),
|
queryset=InventoryItem.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
|
device = forms.ModelChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/devices/"
|
||||||
|
)
|
||||||
|
)
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/manufacturers/"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
part_id = forms.CharField(
|
part_id = forms.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -3349,18 +3410,48 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
device = forms.CharField(
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
required=False,
|
required=False,
|
||||||
label='Device name'
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
site = FilterChoiceField(
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'device_id': 'site'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
device_id = FilterChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label='Device',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url='/api/dcim/devices/',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
manufacturer = FilterChoiceField(
|
manufacturer = FilterChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
widget=APISelect(
|
||||||
|
api_url="/api/dcim/manufacturers/",
|
||||||
|
value_field="slug",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
discovered = forms.NullBooleanField(
|
discovered = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(
|
widget=StaticSelect2(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -3507,6 +3598,18 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -3612,6 +3715,18 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -3832,6 +3947,18 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
@ -2597,7 +2597,7 @@ class DeviceBay(ComponentModel):
|
|||||||
# Check that the installed device is not already installed elsewhere
|
# Check that the installed device is not already installed elsewhere
|
||||||
if self.installed_device:
|
if self.installed_device:
|
||||||
current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first()
|
current_bay = DeviceBay.objects.filter(installed_device=self.installed_device).first()
|
||||||
if current_bay:
|
if current_bay and current_bay != self:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'installed_device': "Cannot install the specified device; device is already installed in {}".format(
|
'installed_device': "Cannot install the specified device; device is already installed in {}".format(
|
||||||
current_bay
|
current_bay
|
||||||
|
@ -52,7 +52,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
else:
|
else:
|
||||||
initial = None
|
initial = None
|
||||||
field = forms.NullBooleanField(
|
field = forms.NullBooleanField(
|
||||||
required=cf.required, initial=initial, widget=forms.Select(choices=choices)
|
required=cf.required, initial=initial, widget=StaticSelect2(choices=choices)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Date
|
# Date
|
||||||
@ -71,7 +71,9 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
default_choice = cf.choices.get(value=initial).pk
|
default_choice = cf.choices.get(value=initial).pk
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
pass
|
pass
|
||||||
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
field = forms.TypedChoiceField(
|
||||||
|
choices=choices, coerce=int, required=cf.required, initial=default_choice, widget=StaticSelect2()
|
||||||
|
)
|
||||||
|
|
||||||
# URL
|
# URL
|
||||||
elif cf.type == CF_TYPE_URL:
|
elif cf.type == CF_TYPE_URL:
|
||||||
|
@ -4,10 +4,10 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Device, Interface, Region, Site
|
||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
@ -149,6 +149,17 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
|
|||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label='VRF (RD)',
|
label='VRF (RD)',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -375,6 +386,17 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
|
|
||||||
|
|
||||||
class VLANGroupFilter(NameSlugSearchFilterSet):
|
class VLANGroupFilter(NameSlugSearchFilterSet):
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
@ -400,6 +422,17 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
|
@ -3,7 +3,7 @@ from django.core.exceptions import MultipleObjectsReturned
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
|
|
||||||
from dcim.models import Site, Rack, Device, Interface
|
from dcim.models import Device, Interface, Rack, Region, Site
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -492,8 +492,8 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
field_order = [
|
field_order = [
|
||||||
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'site', 'role', 'tenant_group', 'tenant',
|
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'region', 'site', 'role', 'tenant_group',
|
||||||
'is_pool', 'expand',
|
'tenant', 'is_pool', 'expand',
|
||||||
]
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -534,6 +534,18 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
|||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -1034,6 +1046,18 @@ class VLANGroupCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -1215,11 +1239,24 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
|
|
||||||
class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region',
|
||||||
|
'group_id': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '2.6.10-dev'
|
VERSION = '2.6.12-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -103,14 +103,16 @@ $(document).ready(function() {
|
|||||||
placeholder: "---------",
|
placeholder: "---------",
|
||||||
theme: "bootstrap",
|
theme: "bootstrap",
|
||||||
templateResult: colorPickerClassCopy,
|
templateResult: colorPickerClassCopy,
|
||||||
templateSelection: colorPickerClassCopy
|
templateSelection: colorPickerClassCopy,
|
||||||
|
width: "off"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Static choice selection
|
// Static choice selection
|
||||||
$('.netbox-select2-static').select2({
|
$('.netbox-select2-static').select2({
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
placeholder: "---------",
|
placeholder: "---------",
|
||||||
theme: "bootstrap"
|
theme: "bootstrap",
|
||||||
|
width: "off"
|
||||||
});
|
});
|
||||||
|
|
||||||
// API backed selection
|
// API backed selection
|
||||||
@ -120,6 +122,7 @@ $(document).ready(function() {
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
placeholder: "---------",
|
placeholder: "---------",
|
||||||
theme: "bootstrap",
|
theme: "bootstrap",
|
||||||
|
width: "off",
|
||||||
ajax: {
|
ajax: {
|
||||||
delay: 500,
|
delay: 500,
|
||||||
|
|
||||||
@ -299,7 +302,8 @@ $(document).ready(function() {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
placeholder: "Tags",
|
placeholder: "Tags",
|
||||||
|
theme: "bootstrap",
|
||||||
|
width: "off",
|
||||||
ajax: {
|
ajax: {
|
||||||
delay: 250,
|
delay: 250,
|
||||||
url: netbox_api_path + "extras/tags/",
|
url: netbox_api_path + "extras/tags/",
|
||||||
|
@ -285,6 +285,8 @@ class APISelect(SelectWithDisabled):
|
|||||||
name of the query param and the value if the query param's value.
|
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.
|
:param null_option: If true, include the static null option in the selection list.
|
||||||
"""
|
"""
|
||||||
|
# Only preload the selected option(s); new options are dynamically displayed and added via the API
|
||||||
|
template_name = 'widgets/select_api.html'
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
9
netbox/utilities/templates/widgets/select_api.html
Normal file
9
netbox/utilities/templates/widgets/select_api.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
|
||||||
|
{% for group_name, group_choices, group_index in widget.optgroups %}
|
||||||
|
{% if group_name %}<optgroup label="{{ group_name }}">{% endif %}
|
||||||
|
{% for option in group_choices %}
|
||||||
|
{% if option.attrs.selected or option.value == "null" %}{% include option.template_name with widget=option %}{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if group_name %}</optgroup>{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
@ -36,6 +36,27 @@ class ClusterFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
label='Region (ID)',
|
||||||
|
)
|
||||||
|
region = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='site__region__in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
label='Site (ID)',
|
||||||
|
)
|
||||||
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='site__slug',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Site (slug)',
|
||||||
|
)
|
||||||
group_id = django_filters.ModelMultipleChoiceFilter(
|
group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
label='Parent group (ID)',
|
label='Parent group (ID)',
|
||||||
@ -56,16 +77,6 @@ class ClusterFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Cluster type (slug)',
|
label='Cluster type (slug)',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
label='Site (ID)',
|
|
||||||
)
|
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='site__slug',
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Site (slug)',
|
|
||||||
)
|
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -173,6 +173,29 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Cluster
|
model = Cluster
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
|
region = FilterChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/regions/",
|
||||||
|
value_field="slug",
|
||||||
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
site = FilterChoiceField(
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_label='-- None --',
|
||||||
|
required=False,
|
||||||
|
widget=APISelectMultiple(
|
||||||
|
api_url="/api/dcim/sites/",
|
||||||
|
value_field='slug',
|
||||||
|
null_option=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
type = FilterChoiceField(
|
type = FilterChoiceField(
|
||||||
queryset=ClusterType.objects.all(),
|
queryset=ClusterType.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -193,17 +216,6 @@ class ClusterFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
null_option=True,
|
null_option=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
null_label='-- None --',
|
|
||||||
required=False,
|
|
||||||
widget=APISelectMultiple(
|
|
||||||
api_url="/api/dcim/sites/",
|
|
||||||
value_field='slug',
|
|
||||||
null_option=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
||||||
@ -563,7 +575,9 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
|||||||
widget=APISelectMultiple(
|
widget=APISelectMultiple(
|
||||||
api_url='/api/dcim/regions/',
|
api_url='/api/dcim/regions/',
|
||||||
value_field="slug",
|
value_field="slug",
|
||||||
null_option=True,
|
filter_for={
|
||||||
|
'site': 'region'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
|
Loading…
Reference in New Issue
Block a user