jeremystretch 4 lat temu
rodzic
commit
3bb485d0b8
37 zmienionych plików z 361 dodań i 537 usunięć
  1. 1 1
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 1 1
      .github/ISSUE_TEMPLATE/feature_request.yaml
  3. 8 1
      docs/release-notes/version-3.1.md
  4. 10 20
      netbox/circuits/forms/filtersets.py
  5. 61 122
      netbox/dcim/forms/filtersets.py
  6. 6 12
      netbox/dcim/forms/models.py
  7. 7 2
      netbox/extras/forms/bulk_import.py
  8. 15 30
      netbox/extras/forms/filtersets.py
  9. 4 4
      netbox/extras/tests/test_views.py
  10. 27 54
      netbox/ipam/forms/filtersets.py
  11. 1 1
      netbox/ipam/views.py
  12. 0 0
      netbox/project-static/dist/netbox.js
  13. 1 1
      netbox/project-static/src/select/api/apiSelect.ts
  14. 1 1
      netbox/templates/base/layout.html
  15. 1 7
      netbox/templates/dcim/devicerole.html
  16. 1 7
      netbox/templates/dcim/devicetype.html
  17. 2 14
      netbox/templates/dcim/interface.html
  18. 1 1
      netbox/templates/dcim/virtualchassis.html
  19. 1 7
      netbox/templates/extras/customfield.html
  20. 1 7
      netbox/templates/extras/customlink.html
  21. 1 7
      netbox/templates/extras/exporttemplate.html
  22. 5 35
      netbox/templates/extras/webhook.html
  23. 1 1
      netbox/templates/generic/object_bulk_import.html
  24. 2 2
      netbox/templates/inc/panels/custom_fields.html
  25. 146 119
      netbox/templates/ipam/prefix.html
  26. 1 7
      netbox/templates/ipam/rir.html
  27. 1 7
      netbox/templates/ipam/vrf.html
  28. 3 2
      netbox/templates/users/api_tokens.html
  29. 2 14
      netbox/templates/users/profile.html
  30. 4 8
      netbox/tenancy/forms/filtersets.py
  31. 2 4
      netbox/tenancy/forms/forms.py
  32. 2 2
      netbox/utilities/forms/fields.py
  33. 5 0
      netbox/utilities/templates/helpers/checkmark.html
  34. 17 0
      netbox/utilities/templatetags/helpers.py
  35. 15 30
      netbox/virtualization/forms/filtersets.py
  36. 2 4
      netbox/wireless/forms/filtersets.py
  37. 2 2
      requirements.txt

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
     attributes:
       label: NetBox version
       label: NetBox version
       description: What version of NetBox are you currently running?
       description: What version of NetBox are you currently running?
-      placeholder: v3.1.2
+      placeholder: v3.1.3
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
     attributes:
       label: NetBox version
       label: NetBox version
       description: What version of NetBox are you currently running?
       description: What version of NetBox are you currently running?
-      placeholder: v3.1.2
+      placeholder: v3.1.3
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 8 - 1
docs/release-notes/version-3.1.md

@@ -1,16 +1,23 @@
 # NetBox v3.1
 # NetBox v3.1
 
 
-## v3.1.3 (FUTURE)
+## v3.1.4 (FUTURE)
+
+---
+
+## v3.1.3 (2021-12-29)
 
 
 ### Enhancements
 ### Enhancements
 
 
 * [#6782](https://github.com/netbox-community/netbox/issues/6782) - Enable the inclusion of custom links in tables
 * [#6782](https://github.com/netbox-community/netbox/issues/6782) - Enable the inclusion of custom links in tables
+* [#7600](https://github.com/netbox-community/netbox/issues/7600) - Include count of available IPs on prefix view
+* [#8034](https://github.com/netbox-community/netbox/issues/8034) - Enable specifying custom field validators during CSV import
 * [#8100](https://github.com/netbox-community/netbox/issues/8100) - Add "other" choice for FHRP group protocol
 * [#8100](https://github.com/netbox-community/netbox/issues/8100) - Add "other" choice for FHRP group protocol
 * [#8175](https://github.com/netbox-community/netbox/issues/8175) - Display parent object when attaching an image
 * [#8175](https://github.com/netbox-community/netbox/issues/8175) - Display parent object when attaching an image
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
 * [#7246](https://github.com/netbox-community/netbox/issues/7246) - Don't attempt to URL-decode NAPALM response payloads
 * [#7246](https://github.com/netbox-community/netbox/issues/7246) - Don't attempt to URL-decode NAPALM response payloads
+* [#7290](https://github.com/netbox-community/netbox/issues/7290) - Defer loading API-backed form fields
 * [#7887](https://github.com/netbox-community/netbox/issues/7887) - Forward `HTTP_X_FORWARDED_FOR` to custom scripts
 * [#7887](https://github.com/netbox-community/netbox/issues/7887) - Forward `HTTP_X_FORWARDED_FOR` to custom scripts
 * [#7962](https://github.com/netbox-community/netbox/issues/7962) - Fix user menu under report/script result view
 * [#7962](https://github.com/netbox-community/netbox/issues/7962) - Fix user menu under report/script result view
 * [#7972](https://github.com/netbox-community/netbox/issues/7972) - Standardize name of `RemoteUserBackend` logger
 * [#7972](https://github.com/netbox-community/netbox/issues/7972) - Standardize name of `RemoteUserBackend` logger

+ 10 - 20
netbox/circuits/forms/filtersets.py

@@ -26,14 +26,12 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -42,8 +40,7 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'site_group_id': '$site_group_id',
             'site_group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     asn = forms.IntegerField(
     asn = forms.IntegerField(
         required=False,
         required=False,
@@ -61,8 +58,7 @@ class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
     provider_id = DynamicModelMultipleChoiceField(
     provider_id = DynamicModelMultipleChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         required=False,
         required=False,
-        label=_('Provider'),
-        fetch_trigger='open'
+        label=_('Provider')
     )
     )
     service_id = forms.CharField(
     service_id = forms.CharField(
         max_length=100,
         max_length=100,
@@ -88,14 +84,12 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     type_id = DynamicModelMultipleChoiceField(
     type_id = DynamicModelMultipleChoiceField(
         queryset=CircuitType.objects.all(),
         queryset=CircuitType.objects.all(),
         required=False,
         required=False,
-        label=_('Type'),
-        fetch_trigger='open'
+        label=_('Type')
     )
     )
     provider_id = DynamicModelMultipleChoiceField(
     provider_id = DynamicModelMultipleChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         required=False,
         required=False,
-        label=_('Provider'),
-        fetch_trigger='open'
+        label=_('Provider')
     )
     )
     provider_network_id = DynamicModelMultipleChoiceField(
     provider_network_id = DynamicModelMultipleChoiceField(
         queryset=ProviderNetwork.objects.all(),
         queryset=ProviderNetwork.objects.all(),
@@ -103,8 +97,7 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'provider_id': '$provider_id'
             'provider_id': '$provider_id'
         },
         },
-        label=_('Provider network'),
-        fetch_trigger='open'
+        label=_('Provider network')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=CircuitStatusChoices,
         choices=CircuitStatusChoices,
@@ -114,14 +107,12 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -130,8 +121,7 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'site_group_id': '$site_group_id',
             'site_group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     commit_rate = forms.IntegerField(
     commit_rate = forms.IntegerField(
         required=False,
         required=False,

+ 61 - 122
netbox/dcim/forms/filtersets.py

@@ -62,14 +62,12 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -78,8 +76,7 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location_id = DynamicModelMultipleChoiceField(
     location_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -87,14 +84,12 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id',
             'site_id': '$site_id',
         },
         },
-        label=_('Location'),
-        fetch_trigger='open'
+        label=_('Location')
     )
     )
     virtual_chassis_id = DynamicModelMultipleChoiceField(
     virtual_chassis_id = DynamicModelMultipleChoiceField(
         queryset=VirtualChassis.objects.all(),
         queryset=VirtualChassis.objects.all(),
         required=False,
         required=False,
-        label=_('Virtual Chassis'),
-        fetch_trigger='open'
+        label=_('Virtual Chassis')
     )
     )
     device_id = DynamicModelMultipleChoiceField(
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -104,8 +99,7 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
             'location_id': '$location_id',
             'location_id': '$location_id',
             'virtual_chassis_id': '$virtual_chassis_id'
             'virtual_chassis_id': '$virtual_chassis_id'
         },
         },
-        label=_('Device'),
-        fetch_trigger='open'
+        label=_('Device')
     )
     )
 
 
 
 
@@ -114,8 +108,7 @@ class RegionFilterForm(CustomFieldModelFilterForm):
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Parent region'),
-        fetch_trigger='open'
+        label=_('Parent region')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -125,8 +118,7 @@ class SiteGroupFilterForm(CustomFieldModelFilterForm):
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Parent group'),
-        fetch_trigger='open'
+        label=_('Parent group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -147,20 +139,17 @@ class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     group_id = DynamicModelMultipleChoiceField(
     group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     asn_id = DynamicModelMultipleChoiceField(
     asn_id = DynamicModelMultipleChoiceField(
         queryset=ASN.objects.all(),
         queryset=ASN.objects.all(),
         required=False,
         required=False,
-        label=_('ASNs'),
-        fetch_trigger='open'
+        label=_('ASNs')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -175,14 +164,12 @@ class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -191,8 +178,7 @@ class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -201,8 +187,7 @@ class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'site_id': '$site_id',
             'site_id': '$site_id',
         },
         },
-        label=_('Parent'),
-        fetch_trigger='open'
+        label=_('Parent')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -224,8 +209,7 @@ class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -233,8 +217,7 @@ class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location_id = DynamicModelMultipleChoiceField(
     location_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -243,8 +226,7 @@ class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Location'),
-        fetch_trigger='open'
+        label=_('Location')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=RackStatusChoices,
         choices=RackStatusChoices,
@@ -265,8 +247,7 @@ class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=RackRole.objects.all(),
         queryset=RackRole.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
         required=False
         required=False
@@ -285,8 +266,7 @@ class RackElevationFilterForm(RackFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id',
             'site_id': '$site_id',
             'location_id': '$location_id',
             'location_id': '$location_id',
-        },
-        fetch_trigger='open'
+        }
     )
     )
 
 
 
 
@@ -301,8 +281,7 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -310,15 +289,13 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location_id = DynamicModelMultipleChoiceField(
     location_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.prefetch_related('site'),
         queryset=Location.objects.prefetch_related('site'),
         required=False,
         required=False,
         label=_('Location'),
         label=_('Location'),
-        null_option='None',
-        fetch_trigger='open'
+        null_option='None'
     )
     )
     user_id = DynamicModelMultipleChoiceField(
     user_id = DynamicModelMultipleChoiceField(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
@@ -326,8 +303,7 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         label=_('User'),
         label=_('User'),
         widget=APISelectMultiple(
         widget=APISelectMultiple(
             api_url='/api/users/users/',
             api_url='/api/users/users/',
-        ),
-        fetch_trigger='open'
+        )
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -347,8 +323,7 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
-        label=_('Manufacturer'),
-        fetch_trigger='open'
+        label=_('Manufacturer')
     )
     )
     part_number = forms.CharField(
     part_number = forms.CharField(
         required=False
         required=False
@@ -479,8 +454,7 @@ class PlatformFilterForm(CustomFieldModelFilterForm):
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
-        label=_('Manufacturer'),
-        fetch_trigger='open'
+        label=_('Manufacturer')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -501,14 +475,12 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -517,8 +489,7 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location_id = DynamicModelMultipleChoiceField(
     location_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -527,8 +498,7 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Location'),
-        fetch_trigger='open'
+        label=_('Location')
     )
     )
     rack_id = DynamicModelMultipleChoiceField(
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
@@ -538,20 +508,17 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
             'site_id': '$site_id',
             'site_id': '$site_id',
             'location_id': '$location_id',
             'location_id': '$location_id',
         },
         },
-        label=_('Rack'),
-        fetch_trigger='open'
+        label=_('Rack')
     )
     )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False,
         required=False,
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
-        label=_('Manufacturer'),
-        fetch_trigger='open'
+        label=_('Manufacturer')
     )
     )
     device_type_id = DynamicModelMultipleChoiceField(
     device_type_id = DynamicModelMultipleChoiceField(
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
@@ -559,15 +526,13 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
         query_params={
         query_params={
             'manufacturer_id': '$manufacturer_id'
             'manufacturer_id': '$manufacturer_id'
         },
         },
-        label=_('Model'),
-        fetch_trigger='open'
+        label=_('Model')
     )
     )
     platform_id = DynamicModelMultipleChoiceField(
     platform_id = DynamicModelMultipleChoiceField(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Platform'),
-        fetch_trigger='open'
+        label=_('Platform')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=DeviceStatusChoices,
         choices=DeviceStatusChoices,
@@ -689,14 +654,12 @@ class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -705,8 +668,7 @@ class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -722,8 +684,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -731,8 +692,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     rack_id = DynamicModelMultipleChoiceField(
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
@@ -741,8 +701,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         null_option='None',
         null_option='None',
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
-        },
-        fetch_trigger='open'
+        }
     )
     )
     type = forms.MultipleChoiceField(
     type = forms.MultipleChoiceField(
         choices=add_blank_choice(CableTypeChoices),
         choices=add_blank_choice(CableTypeChoices),
@@ -765,8 +724,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'tenant_id': '$tenant_id',
             'tenant_id': '$tenant_id',
             'rack_id': '$rack_id',
             'rack_id': '$rack_id',
         },
         },
-        label=_('Device'),
-        fetch_trigger='open'
+        label=_('Device')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -780,14 +738,12 @@ class PowerPanelFilterForm(CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -796,8 +752,7 @@ class PowerPanelFilterForm(CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location_id = DynamicModelMultipleChoiceField(
     location_id = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
@@ -806,8 +761,7 @@ class PowerPanelFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Location'),
-        fetch_trigger='open'
+        label=_('Location')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -823,14 +777,12 @@ class PowerFeedFilterForm(CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -838,8 +790,7 @@ class PowerFeedFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     power_panel_id = DynamicModelMultipleChoiceField(
     power_panel_id = DynamicModelMultipleChoiceField(
         queryset=PowerPanel.objects.all(),
         queryset=PowerPanel.objects.all(),
@@ -848,8 +799,7 @@ class PowerFeedFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Power panel'),
-        fetch_trigger='open'
+        label=_('Power panel')
     )
     )
     rack_id = DynamicModelMultipleChoiceField(
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
@@ -858,8 +808,7 @@ class PowerFeedFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Rack'),
-        fetch_trigger='open'
+        label=_('Rack')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=PowerFeedStatusChoices,
         choices=PowerFeedStatusChoices,
@@ -1109,8 +1058,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
     manufacturer_id = DynamicModelMultipleChoiceField(
     manufacturer_id = DynamicModelMultipleChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         required=False,
         required=False,
-        label=_('Manufacturer'),
-        fetch_trigger='open'
+        label=_('Manufacturer')
     )
     )
     serial = forms.CharField(
     serial = forms.CharField(
         required=False
         required=False
@@ -1144,8 +1092,7 @@ class ConsoleConnectionFilterForm(FilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -1153,8 +1100,7 @@ class ConsoleConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     device_id = DynamicModelMultipleChoiceField(
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -1162,8 +1108,7 @@ class ConsoleConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Device'),
-        fetch_trigger='open'
+        label=_('Device')
     )
     )
 
 
 
 
@@ -1171,8 +1116,7 @@ class PowerConnectionFilterForm(FilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -1180,8 +1124,7 @@ class PowerConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     device_id = DynamicModelMultipleChoiceField(
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -1189,8 +1132,7 @@ class PowerConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Device'),
-        fetch_trigger='open'
+        label=_('Device')
     )
     )
 
 
 
 
@@ -1198,8 +1140,7 @@ class InterfaceConnectionFilterForm(FilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -1207,8 +1148,7 @@ class InterfaceConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     device_id = DynamicModelMultipleChoiceField(
     device_id = DynamicModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -1216,6 +1156,5 @@ class InterfaceConnectionFilterForm(FilterForm):
         query_params={
         query_params={
             'site_id': '$site_id'
             'site_id': '$site_id'
         },
         },
-        label=_('Device'),
-        fetch_trigger='open'
+        label=_('Device')
     )
     )

+ 6 - 12
netbox/dcim/forms/models.py

@@ -300,16 +300,14 @@ class RackReservationForm(TenancyForm, CustomFieldModelForm):
         required=False,
         required=False,
         initial_params={
         initial_params={
             'sites': '$site'
             'sites': '$site'
-        },
-        fetch_trigger='open'
+        }
     )
     )
     site_group = DynamicModelChoiceField(
     site_group = DynamicModelChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
         initial_params={
         initial_params={
             'sites': '$site'
             'sites': '$site'
-        },
-        fetch_trigger='open'
+        }
     )
     )
     site = DynamicModelChoiceField(
     site = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -317,24 +315,21 @@ class RackReservationForm(TenancyForm, CustomFieldModelForm):
         query_params={
         query_params={
             'region_id': '$region',
             'region_id': '$region',
             'group_id': '$site_group',
             'group_id': '$site_group',
-        },
-        fetch_trigger='open'
+        }
     )
     )
     location = DynamicModelChoiceField(
     location = DynamicModelChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
         query_params={
         query_params={
             'site_id': '$site'
             'site_id': '$site'
-        },
-        fetch_trigger='open'
+        }
     )
     )
     rack = DynamicModelChoiceField(
     rack = DynamicModelChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         query_params={
         query_params={
             'site_id': '$site',
             'site_id': '$site',
             'location_id': '$location',
             'location_id': '$location',
-        },
-        fetch_trigger='open'
+        }
     )
     )
     units = NumericArrayField(
     units = NumericArrayField(
         base_field=forms.IntegerField(),
         base_field=forms.IntegerField(),
@@ -348,8 +343,7 @@ class RackReservationForm(TenancyForm, CustomFieldModelForm):
     )
     )
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
-        required=False,
-        fetch_trigger='open'
+        required=False
     )
     )
 
 
     class Meta:
     class Meta:

+ 7 - 2
netbox/extras/forms/bulk_import.py

@@ -3,9 +3,10 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.forms import SimpleArrayField
 from django.contrib.postgres.forms import SimpleArrayField
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
+from extras.choices import CustomFieldTypeChoices
 from extras.models import *
 from extras.models import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
-from utilities.forms import CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField
+from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField
 
 
 __all__ = (
 __all__ = (
     'CustomFieldCSVForm',
     'CustomFieldCSVForm',
@@ -22,6 +23,10 @@ class CustomFieldCSVForm(CSVModelForm):
         limit_choices_to=FeatureQuery('custom_fields'),
         limit_choices_to=FeatureQuery('custom_fields'),
         help_text="One or more assigned object types"
         help_text="One or more assigned object types"
     )
     )
+    type = CSVChoiceField(
+        choices=CustomFieldTypeChoices,
+        help_text='Field data type (e.g. text, integer, etc.)'
+    )
     choices = SimpleArrayField(
     choices = SimpleArrayField(
         base_field=forms.CharField(),
         base_field=forms.CharField(),
         required=False,
         required=False,
@@ -32,7 +37,7 @@ class CustomFieldCSVForm(CSVModelForm):
         model = CustomField
         model = CustomField
         fields = (
         fields = (
             'name', 'label', 'type', 'content_types', 'required', 'description', 'weight', 'filter_logic', 'default',
             'name', 'label', 'type', 'content_types', 'required', 'description', 'weight', 'filter_logic', 'default',
-            'choices', 'weight',
+            'choices', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
         )
         )
 
 
 
 

+ 15 - 30
netbox/extras/forms/filtersets.py

@@ -164,38 +164,32 @@ class ConfigContextFilterForm(FilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Regions'),
-        fetch_trigger='open'
+        label=_('Regions')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site groups'),
-        fetch_trigger='open'
+        label=_('Site groups')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label=_('Sites'),
-        fetch_trigger='open'
+        label=_('Sites')
     )
     )
     device_type_id = DynamicModelMultipleChoiceField(
     device_type_id = DynamicModelMultipleChoiceField(
         queryset=DeviceType.objects.all(),
         queryset=DeviceType.objects.all(),
         required=False,
         required=False,
-        label=_('Device types'),
-        fetch_trigger='open'
+        label=_('Device types')
     )
     )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False,
         required=False,
-        label=_('Roles'),
-        fetch_trigger='open'
+        label=_('Roles')
     )
     )
     platform_id = DynamicModelMultipleChoiceField(
     platform_id = DynamicModelMultipleChoiceField(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
-        label=_('Platforms'),
-        fetch_trigger='open'
+        label=_('Platforms')
     )
     )
     cluster_type_id = DynamicModelMultipleChoiceField(
     cluster_type_id = DynamicModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
@@ -206,33 +200,28 @@ class ConfigContextFilterForm(FilterForm):
     cluster_group_id = DynamicModelMultipleChoiceField(
     cluster_group_id = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Cluster groups'),
-        fetch_trigger='open'
+        label=_('Cluster groups')
     )
     )
     cluster_id = DynamicModelMultipleChoiceField(
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
-        label=_('Clusters'),
-        fetch_trigger='open'
+        label=_('Clusters')
     )
     )
     tenant_group_id = DynamicModelMultipleChoiceField(
     tenant_group_id = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Tenant groups'),
-        fetch_trigger='open'
+        label=_('Tenant groups')
     )
     )
     tenant_id = DynamicModelMultipleChoiceField(
     tenant_id = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         required=False,
         required=False,
-        label=_('Tenant'),
-        fetch_trigger='open'
+        label=_('Tenant')
     )
     )
     tag = DynamicModelMultipleChoiceField(
     tag = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
         required=False,
         required=False,
-        label=_('Tags'),
-        fetch_trigger='open'
+        label=_('Tags')
     )
     )
 
 
 
 
@@ -269,8 +258,7 @@ class JournalEntryFilterForm(FilterForm):
         label=_('User'),
         label=_('User'),
         widget=APISelectMultiple(
         widget=APISelectMultiple(
             api_url='/api/users/users/',
             api_url='/api/users/users/',
-        ),
-        fetch_trigger='open'
+        )
     )
     )
     assigned_object_type_id = DynamicModelMultipleChoiceField(
     assigned_object_type_id = DynamicModelMultipleChoiceField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
@@ -278,8 +266,7 @@ class JournalEntryFilterForm(FilterForm):
         label=_('Object Type'),
         label=_('Object Type'),
         widget=APISelectMultiple(
         widget=APISelectMultiple(
             api_url='/api/extras/content-types/',
             api_url='/api/extras/content-types/',
-        ),
-        fetch_trigger='open'
+        )
     )
     )
     kind = forms.ChoiceField(
     kind = forms.ChoiceField(
         choices=add_blank_choice(JournalEntryKindChoices),
         choices=add_blank_choice(JournalEntryKindChoices),
@@ -316,8 +303,7 @@ class ObjectChangeFilterForm(FilterForm):
         label=_('User'),
         label=_('User'),
         widget=APISelectMultiple(
         widget=APISelectMultiple(
             api_url='/api/users/users/',
             api_url='/api/users/users/',
-        ),
-        fetch_trigger='open'
+        )
     )
     )
     changed_object_type_id = DynamicModelMultipleChoiceField(
     changed_object_type_id = DynamicModelMultipleChoiceField(
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),
@@ -325,6 +311,5 @@ class ObjectChangeFilterForm(FilterForm):
         label=_('Object Type'),
         label=_('Object Type'),
         widget=APISelectMultiple(
         widget=APISelectMultiple(
             api_url='/api/extras/content-types/',
             api_url='/api/extras/content-types/',
-        ),
-        fetch_trigger='open'
+        )
     )
     )

+ 4 - 4
netbox/extras/tests/test_views.py

@@ -39,10 +39,10 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         }
         }
 
 
         cls.csv_data = (
         cls.csv_data = (
-            'name,label,type,content_types,weight,filter_logic,choices',
-            'field4,Field 4,text,dcim.site,100,exact,',
-            'field5,Field 5,integer,dcim.site,100,exact,',
-            'field6,Field 6,select,dcim.site,100,exact,"A,B,C"',
+            'name,label,type,content_types,weight,filter_logic,choices,validation_minimum,validation_maximum,validation_regex',
+            'field4,Field 4,text,dcim.site,100,exact,,,,[a-z]{3}',
+            'field5,Field 5,integer,dcim.site,100,exact,,1,100,',
+            'field6,Field 6,select,dcim.site,100,exact,"A,B,C",,,',
         )
         )
 
 
         cls.bulk_edit_data = {
         cls.bulk_edit_data = {

+ 27 - 54
netbox/ipam/forms/filtersets.py

@@ -48,14 +48,12 @@ class VRFFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     import_target_id = DynamicModelMultipleChoiceField(
     import_target_id = DynamicModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False,
         required=False,
-        label=_('Import targets'),
-        fetch_trigger='open'
+        label=_('Import targets')
     )
     )
     export_target_id = DynamicModelMultipleChoiceField(
     export_target_id = DynamicModelMultipleChoiceField(
         queryset=RouteTarget.objects.all(),
         queryset=RouteTarget.objects.all(),
         required=False,
         required=False,
-        label=_('Export targets'),
-        fetch_trigger='open'
+        label=_('Export targets')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -70,14 +68,12 @@ class RouteTargetFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     importing_vrf_id = DynamicModelMultipleChoiceField(
     importing_vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label=_('Imported by VRF'),
-        fetch_trigger='open'
+        label=_('Imported by VRF')
     )
     )
     exporting_vrf_id = DynamicModelMultipleChoiceField(
     exporting_vrf_id = DynamicModelMultipleChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label=_('Exported by VRF'),
-        fetch_trigger='open'
+        label=_('Exported by VRF')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -110,8 +106,7 @@ class AggregateFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     rir_id = DynamicModelMultipleChoiceField(
     rir_id = DynamicModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         required=False,
         required=False,
-        label=_('RIR'),
-        fetch_trigger='open'
+        label=_('RIR')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -127,14 +122,12 @@ class ASNFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     rir_id = DynamicModelMultipleChoiceField(
     rir_id = DynamicModelMultipleChoiceField(
         queryset=RIR.objects.all(),
         queryset=RIR.objects.all(),
         required=False,
         required=False,
-        label=_('RIR'),
-        fetch_trigger='open'
+        label=_('RIR')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
 
 
 
 
@@ -180,14 +173,12 @@ class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         label=_('Assigned VRF'),
         label=_('Assigned VRF'),
-        null_option='Global',
-        fetch_trigger='open'
+        null_option='Global'
     )
     )
     present_in_vrf_id = DynamicModelChoiceField(
     present_in_vrf_id = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label=_('Present in VRF'),
-        fetch_trigger='open'
+        label=_('Present in VRF')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
@@ -197,14 +188,12 @@ class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -213,15 +202,13 @@ class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region_id': '$region_id'
             'region_id': '$region_id'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     is_pool = forms.NullBooleanField(
     is_pool = forms.NullBooleanField(
         required=False,
         required=False,
@@ -257,8 +244,7 @@ class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         label=_('Assigned VRF'),
         label=_('Assigned VRF'),
-        null_option='Global',
-        fetch_trigger='open'
+        null_option='Global'
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=PrefixStatusChoices,
         choices=PrefixStatusChoices,
@@ -269,8 +255,7 @@ class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -308,14 +293,12 @@ class IPAddressFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
         label=_('Assigned VRF'),
         label=_('Assigned VRF'),
-        null_option='Global',
-        fetch_trigger='open'
+        null_option='Global'
     )
     )
     present_in_vrf_id = DynamicModelChoiceField(
     present_in_vrf_id = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
-        label=_('Present in VRF'),
-        fetch_trigger='open'
+        label=_('Present in VRF')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=IPAddressStatusChoices,
         choices=IPAddressStatusChoices,
@@ -377,32 +360,27 @@ class VLANGroupFilterForm(CustomFieldModelFilterForm):
     region = DynamicModelMultipleChoiceField(
     region = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     sitegroup = DynamicModelMultipleChoiceField(
     sitegroup = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site = DynamicModelMultipleChoiceField(
     site = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     location = DynamicModelMultipleChoiceField(
     location = DynamicModelMultipleChoiceField(
         queryset=Location.objects.all(),
         queryset=Location.objects.all(),
         required=False,
         required=False,
-        label=_('Location'),
-        fetch_trigger='open'
+        label=_('Location')
     )
     )
     rack = DynamicModelMultipleChoiceField(
     rack = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,
-        label=_('Rack'),
-        fetch_trigger='open'
+        label=_('Rack')
     )
     )
     min_vid = forms.IntegerField(
     min_vid = forms.IntegerField(
         min_value=VLAN_VID_MIN,
         min_value=VLAN_VID_MIN,
@@ -426,14 +404,12 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -442,8 +418,7 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region': '$region'
             'region': '$region'
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     group_id = DynamicModelMultipleChoiceField(
     group_id = DynamicModelMultipleChoiceField(
         queryset=VLANGroup.objects.all(),
         queryset=VLANGroup.objects.all(),
@@ -452,8 +427,7 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         query_params={
         query_params={
             'region': '$region'
             'region': '$region'
         },
         },
-        label=_('VLAN group'),
-        fetch_trigger='open'
+        label=_('VLAN group')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=VLANStatusChoices,
         choices=VLANStatusChoices,
@@ -464,8 +438,7 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     vid = forms.IntegerField(
     vid = forms.IntegerField(
         required=False,
         required=False,

+ 1 - 1
netbox/ipam/views.py

@@ -418,7 +418,7 @@ class PrefixView(generic.ObjectView):
         ).filter(
         ).filter(
             prefix__net_contains=str(instance.prefix)
             prefix__net_contains=str(instance.prefix)
         ).prefetch_related(
         ).prefetch_related(
-            'site', 'role'
+            'site', 'role', 'tenant'
         )
         )
         parent_prefix_table = tables.PrefixTable(
         parent_prefix_table = tables.PrefixTable(
             list(parent_prefixes),
             list(parent_prefixes),

Plik diff jest za duży
+ 0 - 0
netbox/project-static/dist/netbox.js


+ 1 - 1
netbox/project-static/src/select/api/apiSelect.ts

@@ -251,7 +251,7 @@ export class APISelect {
     } else if (collapse !== null) {
     } else if (collapse !== null) {
       this.trigger = 'collapse';
       this.trigger = 'collapse';
     } else {
     } else {
-      this.trigger = 'load';
+      this.trigger = 'open';
     }
     }
 
 
     switch (this.trigger) {
     switch (this.trigger) {

+ 1 - 1
netbox/templates/base/layout.html

@@ -20,7 +20,7 @@
         </div>
         </div>
 
 
         {# Top bar #}
         {# Top bar #}
-        <nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid border-bottom bg-light bg-gradient noprint">
+        <nav class="navbar navbar-light sticky-top flex-md-nowrap p-1 mb-3 search container-fluid border-bottom noprint">
 
 
             {# Mobile Navigation #}
             {# Mobile Navigation #}
             <div class="nav-mobile">
             <div class="nav-mobile">

+ 1 - 7
netbox/templates/dcim/devicerole.html

@@ -40,13 +40,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">VM Role</th>
             <th scope="row">VM Role</th>
-            <td>
-              {% if object.vm_role %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.vm_role %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Devices</th>
             <th scope="row">Devices</th>

+ 1 - 7
netbox/templates/dcim/devicetype.html

@@ -33,13 +33,7 @@
                         </tr>
                         </tr>
                         <tr>
                         <tr>
                             <td>Full Depth</td>
                             <td>Full Depth</td>
-                            <td>
-                                {% if object.is_full_depth %}
-                                    <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                                {% else %}
-                                    <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                                {% endif %}
-                            </td>
+                            <td>{% checkmark object.is_full_depth %}</td>
                         </tr>
                         </tr>
                         <tr>
                         <tr>
                             <td>Parent/Child</td>
                             <td>Parent/Child</td>

+ 2 - 14
netbox/templates/dcim/interface.html

@@ -48,23 +48,11 @@
                         </tr>
                         </tr>
                         <tr>
                         <tr>
                             <th scope="row">Enabled</th>
                             <th scope="row">Enabled</th>
-                            <td>
-                                {% if object.enabled %}
-                                    <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                                {% else %}
-                                    <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                                {% endif %}
-                            </td>
+                            <td>{% checkmark object.enabled %}</td>
                         </tr>
                         </tr>
                         <tr>
                         <tr>
                             <th scope="row">Management Only</th>
                             <th scope="row">Management Only</th>
-                            <td>
-                                {% if object.mgmt_only %}
-                                    <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                                {% else %}
-                                    <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                                {% endif %}
-                            </td>
+                            <td>{% checkmark object.mgmt_only %}</td>
                         </tr>
                         </tr>
                         <tr>
                         <tr>
                             <th scope="row">Parent</th>
                             <th scope="row">Parent</th>

+ 1 - 1
netbox/templates/dcim/virtualchassis.html

@@ -65,7 +65,7 @@
                             </td>
                             </td>
                             <td>
                             <td>
                               {% if object.master == vc_member %}
                               {% if object.master == vc_member %}
-                                <i class="mdi mdi-check-bold text-success"></i>
+                                {% checkmark True %}
                               {% endif %}
                               {% endif %}
                             </td>
                             </td>
                             <td>
                             <td>

+ 1 - 7
netbox/templates/extras/customfield.html

@@ -29,13 +29,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Required</th>
             <th scope="row">Required</th>
-            <td>
-              {% if object.required %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.required %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Weight</th>
             <th scope="row">Weight</th>

+ 1 - 7
netbox/templates/extras/customlink.html

@@ -33,13 +33,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">New Window</th>
             <th scope="row">New Window</th>
-            <td>
-              {% if object.new_window %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.new_window %}</td>
           </tr>
           </tr>
         </table>
         </table>
       </div>
       </div>

+ 1 - 7
netbox/templates/extras/exporttemplate.html

@@ -40,13 +40,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Attachment</th>
             <th scope="row">Attachment</th>
-            <td>
-              {% if object.as_attachment %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.as_attachment %}</td>
           </tr>
           </tr>
         </table>
         </table>
       </div>
       </div>

+ 5 - 35
netbox/templates/extras/webhook.html

@@ -17,13 +17,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Enabled</th>
             <th scope="row">Enabled</th>
-            <td>
-              {% if object.enabled %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.enabled %}</td>
           </tr>
           </tr>
         </table>
         </table>
       </div>
       </div>
@@ -36,33 +30,15 @@
         <table class="table table-hover attr-table">
         <table class="table table-hover attr-table">
           <tr>
           <tr>
             <th scope="row">Create</th>
             <th scope="row">Create</th>
-            <td>
-              {% if object.type_create %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.type_create %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Update</th>
             <th scope="row">Update</th>
-            <td>
-              {% if object.type_update %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.type_update %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Delete</th>
             <th scope="row">Delete</th>
-            <td>
-              {% if object.type_delete %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.type_delete %}</td>
           </tr>
           </tr>
         </table>
         </table>
       </div>
       </div>
@@ -100,13 +76,7 @@
         <table class="table table-hover attr-table">
         <table class="table table-hover attr-table">
           <tr>
           <tr>
             <th scope="row">SSL Verification</th>
             <th scope="row">SSL Verification</th>
-            <td>
-              {% if object.ssl_verification %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.ssl_verification %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">CA File Path</th>
             <th scope="row">CA File Path</th>

+ 1 - 1
netbox/templates/generic/object_bulk_import.html

@@ -66,7 +66,7 @@
                                             </td>
                                             </td>
                                             <td>
                                             <td>
                                                 {% if field.required %}
                                                 {% if field.required %}
-                                                    <i class="mdi mdi-check-bold text-success" title="Required"></i>
+                                                    {% checkmark True true="Required" %}
                                                 {% else %}
                                                 {% else %}
                                                     <span class="text-muted">&mdash;</span>
                                                     <span class="text-muted">&mdash;</span>
                                                 {% endif %}
                                                 {% endif %}

+ 2 - 2
netbox/templates/inc/panels/custom_fields.html

@@ -15,9 +15,9 @@
                                 {% if field.type == 'longtext' and value %}
                                 {% if field.type == 'longtext' and value %}
                                     {{ value|render_markdown }}
                                     {{ value|render_markdown }}
                                 {% elif field.type == 'boolean' and value == True %}
                                 {% elif field.type == 'boolean' and value == True %}
-                                    <i class="mdi mdi-check-bold text-success" title="True"></i>
+                                    {% checkmark value true="True" %}
                                 {% elif field.type == 'boolean' and value == False %}
                                 {% elif field.type == 'boolean' and value == False %}
-                                    <i class="mdi mdi-close-thick text-danger" title="False"></i>
+                                    {% checkmark value false="False" %}
                                 {% elif field.type == 'url' and value %}
                                 {% elif field.type == 'url' and value %}
                                     <a href="{{ value }}">{{ value|truncatechars:70 }}</a>
                                     <a href="{{ value }}">{{ value|truncatechars:70 }}</a>
                                 {% elif field.type == 'json' and value %}
                                 {% elif field.type == 'json' and value %}

+ 146 - 119
netbox/templates/ipam/prefix.html

@@ -4,127 +4,154 @@
 
 
 {% block content %}
 {% block content %}
 <div class="row">
 <div class="row">
-    <div class="col col-md-6">
-        <div class="card">
-            <h5 class="card-header">
-              Prefix
-            </h5>
-            <div class="card-body">
-              <table class="table table-hover attr-table">
-                <tr>
-                    <th scope="row">Family</th>
-                    <td>IPv{{ object.family }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">VRF</th>
-                    <td>
-                        {% if object.vrf %}
-                            <a href="{% url 'ipam:vrf' pk=object.vrf.pk %}">{{ object.vrf }}</a> ({{ object.vrf.rd }})
-                        {% else %}
-                            <span>Global</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Tenant</th>
-                    <td>
-                        {% if object.tenant %}
-                            {% if object.tenant.group %}
-                                <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
-                            {% endif %}
-                            <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Aggregate</th>
-                    <td>
-                        {% if aggregate %}
-                            <a href="{% url 'ipam:aggregate' pk=aggregate.pk %}">{{ aggregate.prefix }}</a> ({{ aggregate.rir }})
-                        {% else %}
-                            <span class="text-warning">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Site</th>
-                    <td>
-                        {% if object.site %}
-                            {% if object.site.region %}
-                                <a href="{{ object.site.region.get_absolute_url }}">{{ object.site.region }}</a> /
-                            {% endif %}
-                            <a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">VLAN</th>
-                    <td>
-                        {% if object.vlan %}
-                            {% if object.vlan.group %}
-                                <a href="{{ object.vlan.group.get_absolute_url }}">{{ object.vlan.group }}</a> /
-                            {% endif %}
-                            <a href="{% url 'ipam:vlan' pk=object.vlan.pk %}">{{ object.vlan }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Status</th>
-                    <td>
-                      <span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Role</th>
-                    <td>
-                        {% if object.role %}
-                            <a href="{{ object.role.get_absolute_url }}">{{ object.role }}</a>
-                        {% else %}
-                            <span class="text-muted">None</span>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Description</th>
-                    <td>{{ object.description|placeholder }}</td>
-                </tr>
-                <tr>
-                    <th scope="row">Is a pool</th>
-                    <td>
-                        {% if object.is_pool %}
-                            <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                        {% else %}
-                            <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                        {% endif %}
-                    </td>
-                </tr>
-                <tr>
-                    <th scope="row">Utilization</th>
-                    <td>
-                      {% if object.mark_utilized %}
-                        {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %}
-                        <small>(Marked fully utilized)</small>
-                      {% else %}
-                        {% utilization_graph object.get_utilization %}
-                      {% endif %}
-                    </td>
-                </tr>
-              </table>
-            </div>
-        </div>
-        {% plugin_left_page object %}
+  <div class="col col-md-6">
+    <div class="card">
+      <h5 class="card-header">Prefix</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">Family</th>
+            <td>IPv{{ object.family }}</td>
+          </tr>
+          <tr>
+            <th scope="row">VRF</th>
+            <td>
+              {% if object.vrf %}
+                <a href="{% url 'ipam:vrf' pk=object.vrf.pk %}">{{ object.vrf }}</a> ({{ object.vrf.rd }})
+              {% else %}
+                <span>Global</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Tenant</th>
+            <td>
+              {% if object.tenant %}
+                {% if object.tenant.group %}
+                  <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
+                {% endif %}
+                <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
+              {% else %}
+                <span class="text-muted">None</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Aggregate</th>
+            <td>
+              {% if aggregate %}
+                <a href="{% url 'ipam:aggregate' pk=aggregate.pk %}">{{ aggregate.prefix }}</a> ({{ aggregate.rir }})
+              {% else %}
+                <span class="text-warning">None</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Site</th>
+            <td>
+              {% if object.site %}
+                {% if object.site.region %}
+                  <a href="{{ object.site.region.get_absolute_url }}">{{ object.site.region }}</a> /
+                {% endif %}
+                <a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a>
+              {% else %}
+                <span class="text-muted">None</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">VLAN</th>
+            <td>
+              {% if object.vlan %}
+                {% if object.vlan.group %}
+                  <a href="{{ object.vlan.group.get_absolute_url }}">{{ object.vlan.group }}</a> /
+                {% endif %}
+                <a href="{% url 'ipam:vlan' pk=object.vlan.pk %}">{{ object.vlan }}</a>
+              {% else %}
+                <span class="text-muted">None</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Status</th>
+            <td>
+              <span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Role</th>
+            <td>
+              {% if object.role %}
+                <a href="{{ object.role.get_absolute_url }}">{{ object.role }}</a>
+              {% else %}
+                <span class="text-muted">None</span>
+              {% endif %}
+            </td>
+          </tr>
+          <tr>
+            <th scope="row">Description</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Is a pool</th>
+            <td>{% checkmark object.is_pool %}</td>
+          </tr>
+        </table>
+      </div>
     </div>
     </div>
-    <div class="col col-md-6">
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_right_page object %}
+      {% plugin_left_page object %}
+  </div>
+  <div class="col col-md-6">
+    <div class="card">
+      <h5 class="card-header">Addressing</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">Utilization</th>
+            <td>
+              {% if object.mark_utilized %}
+                {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %}
+                <small>(Marked fully utilized)</small>
+              {% else %}
+                {% utilization_graph object.get_utilization %}
+              {% endif %}
+            </td>
+          </tr>
+          {% with child_ip_count=object.get_child_ips.count %}
+            <tr>
+              <th scope="row">Child IPs</th>
+              <td>
+                <a href="{% url 'ipam:prefix_ipaddresses' pk=object.pk %}">{{ child_ip_count }}</a>
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">Available IPs</th>
+              <td>{{ object.get_available_ips|length }}</td>
+            </tr>
+          {% endwith %}
+          <tr>
+            <td>First available IP</td>
+            <td>
+              {% with first_available_ip=object.get_first_available_ip %}
+                {% if first_available_ip %}
+                  {% if perms.ipam.add_ipaddress %}
+                    <a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}">{{ first_available_ip }}</a>
+                  {% else %}
+                    {{ first_available_ip }}
+                  {% endif %}
+                {% else %}
+                  <span class="text-muted">None</span>
+                {% endif %}
+              {% endwith %}
+            </td>
+          </tr>
+        </table>
+      </div>
     </div>
     </div>
+    {% include 'inc/panels/custom_fields.html' %}
+    {% include 'inc/panels/tags.html' %}
+    {% plugin_right_page object %}
+  </div>
 </div>
 </div>
 <div class="row">
 <div class="row">
     <div class="col col-md-12">
     <div class="col col-md-12">

+ 1 - 7
netbox/templates/ipam/rir.html

@@ -30,13 +30,7 @@
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Private</th>
             <th scope="row">Private</th>
-            <td>
-              {% if object.is_private %}
-                <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-              {% else %}
-                <i class="mdi mdi-close-thick text-danger" title="No"></i>
-              {% endif %}
-            </td>
+            <td>{% checkmark object.is_private %}</td>
           </tr>
           </tr>
           <tr>
           <tr>
             <th scope="row">Aggregates</th>
             <th scope="row">Aggregates</th>

+ 1 - 7
netbox/templates/ipam/vrf.html

@@ -30,13 +30,7 @@
                   </tr>
                   </tr>
                   <tr>
                   <tr>
                       <th scope="row">Unique IP Space</th>
                       <th scope="row">Unique IP Space</th>
-                      <td>
-                          {% if object.enforce_unique %}
-                              <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                          {% else %}
-                              <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                          {% endif %}
-                      </td>
+                      <td>{% checkmark object.enforce_unique %}</td>
                   </tr>
                   </tr>
                   <tr>
                   <tr>
                       <th scope="row">Description</th>
                       <th scope="row">Description</th>

+ 3 - 2
netbox/templates/users/api_tokens.html

@@ -5,7 +5,7 @@
 
 
 {% block content %}
 {% block content %}
     <div class="row">
     <div class="row">
-        <div class="col col-md-12">
+        <div class="col col-md-10 offset-md-1">
             {% for token in tokens %}
             {% for token in tokens %}
                 <div class="card{% if token.is_expired %} bg-danger{% endif %}">
                 <div class="card{% if token.is_expired %} bg-danger{% endif %}">
                     <div class="card-header">
                     <div class="card-header">
@@ -49,7 +49,8 @@
                     </div>
                     </div>
                 </div>
                 </div>
             {% empty %}
             {% empty %}
-                <p>You do not have any API tokens.</p>
+              <h6><i class="mdi mdi-information"></i> You do not have any API tokens.</h6>
+              <p>Tokens are used to authenticate REST and GraphQL API requests.</p>
             {% endfor %}
             {% endfor %}
             <div class="text-end">
             <div class="text-end">
               <a href="{% url 'user:token_add' %}" class="btn btn-sm btn-primary my-3">
               <a href="{% url 'user:token_add' %}" class="btn btn-sm btn-primary my-3">

+ 2 - 14
netbox/templates/users/profile.html

@@ -35,23 +35,11 @@
             </tr>
             </tr>
             <tr>
             <tr>
               <th scope="row">Superuser</th>
               <th scope="row">Superuser</th>
-              <td>
-                {% if request.user.is_superuser %}
-                  <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                {% else %}
-                  <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                {% endif %}
-              </td>
+              <td>{% checkmark request.user.is_superuser %}</td>
             </tr>
             </tr>
             <tr>
             <tr>
               <th scope="row">Admin Access</th>
               <th scope="row">Admin Access</th>
-              <td>
-                {% if request.user.is_staff %}
-                  <i class="mdi mdi-check-bold text-success" title="Yes"></i>
-                {% else %}
-                  <i class="mdi mdi-close-thick text-danger" title="No"></i>
-                {% endif %}
-              </td>
+              <td>{% checkmark request.user.is_staff %}</td>
             </tr>
             </tr>
           </table>
           </table>
         </div>
         </div>

+ 4 - 8
netbox/tenancy/forms/filtersets.py

@@ -22,8 +22,7 @@ class TenantGroupFilterForm(CustomFieldModelFilterForm):
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Parent group'),
-        fetch_trigger='open'
+        label=_('Parent group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -38,8 +37,7 @@ class TenantFilterForm(CustomFieldModelFilterForm):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Group'),
-        fetch_trigger='open'
+        label=_('Group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -53,8 +51,7 @@ class ContactGroupFilterForm(CustomFieldModelFilterForm):
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Parent group'),
-        fetch_trigger='open'
+        label=_('Parent group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -74,7 +71,6 @@ class ContactFilterForm(CustomFieldModelFilterForm):
         queryset=ContactGroup.objects.all(),
         queryset=ContactGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Group'),
-        fetch_trigger='open'
+        label=_('Group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)

+ 2 - 4
netbox/tenancy/forms/forms.py

@@ -33,8 +33,7 @@ class TenancyFilterForm(forms.Form):
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Tenant group'),
-        fetch_trigger='open'
+        label=_('Tenant group')
     )
     )
     tenant_id = DynamicModelMultipleChoiceField(
     tenant_id = DynamicModelMultipleChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
@@ -43,6 +42,5 @@ class TenancyFilterForm(forms.Form):
         query_params={
         query_params={
             'group_id': '$tenant_group_id'
             'group_id': '$tenant_group_id'
         },
         },
-        label=_('Tenant'),
-        fetch_trigger='open'
+        label=_('Tenant')
     )
     )

+ 2 - 2
netbox/utilities/forms/fields.py

@@ -395,8 +395,8 @@ class DynamicModelChoiceMixin:
     filter = django_filters.ModelChoiceFilter
     filter = django_filters.ModelChoiceFilter
     widget = widgets.APISelect
     widget = widgets.APISelect
 
 
-    def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None, fetch_trigger=None,
-                 empty_label=None, *args, **kwargs):
+    def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None,
+                 fetch_trigger=None, empty_label=None, *args, **kwargs):
         self.query_params = query_params or {}
         self.query_params = query_params or {}
         self.initial_params = initial_params or {}
         self.initial_params = initial_params or {}
         self.null_option = null_option
         self.null_option = null_option

+ 5 - 0
netbox/utilities/templates/helpers/checkmark.html

@@ -0,0 +1,5 @@
+{% if value %}
+  <i class="mdi mdi-check-bold text-success" title="{{ true_label }}"></i>
+{% elif show_false %}
+  <i class="mdi mdi-close-thick text-danger" title="{{ false_label }}"></i>
+{% endif %}

+ 17 - 0
netbox/utilities/templatetags/helpers.py

@@ -426,6 +426,23 @@ def badge(value, bg_class='secondary', show_empty=False):
     }
     }
 
 
 
 
+@register.inclusion_tag('helpers/checkmark.html')
+def checkmark(value, show_false=True, true='Yes', false='No'):
+    """
+    Display either a green checkmark or red X to indicate a boolean value.
+
+    :param show_false: Display a red X if the value is False
+    :param true: Text label for true value
+    :param false: Text label for false value
+    """
+    return {
+        'value': bool(value),
+        'show_false': show_false,
+        'true_label': true,
+        'false_label': false,
+    }
+
+
 @register.inclusion_tag('helpers/table_config_form.html')
 @register.inclusion_tag('helpers/table_config_form.html')
 def table_config_form(table, table_name=None):
 def table_config_form(table, table_name=None):
     return {
     return {

+ 15 - 30
netbox/virtualization/forms/filtersets.py

@@ -40,20 +40,17 @@ class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
     type_id = DynamicModelMultipleChoiceField(
     type_id = DynamicModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         required=False,
         required=False,
-        label=_('Type'),
-        fetch_trigger='open'
+        label=_('Type')
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -63,15 +60,13 @@ class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
             'region_id': '$region_id',
             'region_id': '$region_id',
             'site_group_id': '$site_group_id',
             'site_group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     group_id = DynamicModelMultipleChoiceField(
     group_id = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Group'),
-        fetch_trigger='open'
+        label=_('Group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -89,33 +84,28 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
         queryset=ClusterGroup.objects.all(),
         queryset=ClusterGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Cluster group'),
-        fetch_trigger='open'
+        label=_('Cluster group')
     )
     )
     cluster_type_id = DynamicModelMultipleChoiceField(
     cluster_type_id = DynamicModelMultipleChoiceField(
         queryset=ClusterType.objects.all(),
         queryset=ClusterType.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Cluster type'),
-        fetch_trigger='open'
+        label=_('Cluster type')
     )
     )
     cluster_id = DynamicModelMultipleChoiceField(
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
-        label=_('Cluster'),
-        fetch_trigger='open'
+        label=_('Cluster')
     )
     )
     region_id = DynamicModelMultipleChoiceField(
     region_id = DynamicModelMultipleChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         required=False,
         required=False,
-        label=_('Region'),
-        fetch_trigger='open'
+        label=_('Region')
     )
     )
     site_group_id = DynamicModelMultipleChoiceField(
     site_group_id = DynamicModelMultipleChoiceField(
         queryset=SiteGroup.objects.all(),
         queryset=SiteGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Site group'),
-        fetch_trigger='open'
+        label=_('Site group')
     )
     )
     site_id = DynamicModelMultipleChoiceField(
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
@@ -125,8 +115,7 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
             'region_id': '$region_id',
             'region_id': '$region_id',
             'group_id': '$site_group_id',
             'group_id': '$site_group_id',
         },
         },
-        label=_('Site'),
-        fetch_trigger='open'
+        label=_('Site')
     )
     )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
@@ -135,8 +124,7 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
         query_params={
         query_params={
             'vm_role': "True"
             'vm_role': "True"
         },
         },
-        label=_('Role'),
-        fetch_trigger='open'
+        label=_('Role')
     )
     )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=VirtualMachineStatusChoices,
         choices=VirtualMachineStatusChoices,
@@ -147,8 +135,7 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Platform'),
-        fetch_trigger='open'
+        label=_('Platform')
     )
     )
     mac_address = forms.CharField(
     mac_address = forms.CharField(
         required=False,
         required=False,
@@ -174,8 +161,7 @@ class VMInterfaceFilterForm(CustomFieldModelFilterForm):
     cluster_id = DynamicModelMultipleChoiceField(
     cluster_id = DynamicModelMultipleChoiceField(
         queryset=Cluster.objects.all(),
         queryset=Cluster.objects.all(),
         required=False,
         required=False,
-        label=_('Cluster'),
-        fetch_trigger='open'
+        label=_('Cluster')
     )
     )
     virtual_machine_id = DynamicModelMultipleChoiceField(
     virtual_machine_id = DynamicModelMultipleChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
@@ -183,8 +169,7 @@ class VMInterfaceFilterForm(CustomFieldModelFilterForm):
         query_params={
         query_params={
             'cluster_id': '$cluster_id'
             'cluster_id': '$cluster_id'
         },
         },
-        label=_('Virtual machine'),
-        fetch_trigger='open'
+        label=_('Virtual machine')
     )
     )
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(
         required=False,
         required=False,

+ 2 - 4
netbox/wireless/forms/filtersets.py

@@ -19,8 +19,7 @@ class WirelessLANGroupFilterForm(CustomFieldModelFilterForm):
     parent_id = DynamicModelMultipleChoiceField(
     parent_id = DynamicModelMultipleChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
-        label=_('Parent group'),
-        fetch_trigger='open'
+        label=_('Parent group')
     )
     )
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
@@ -39,8 +38,7 @@ class WirelessLANFilterForm(CustomFieldModelFilterForm):
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False,
         required=False,
         null_option='None',
         null_option='None',
-        label=_('Group'),
-        fetch_trigger='open'
+        label=_('Group')
     )
     )
     auth_type = forms.ChoiceField(
     auth_type = forms.ChoiceField(
         required=False,
         required=False,

+ 2 - 2
requirements.txt

@@ -6,7 +6,7 @@ django-graphiql-debug-toolbar==0.2.0
 django-mptt==0.13.4
 django-mptt==0.13.4
 django-pglocks==1.0.4
 django-pglocks==1.0.4
 django-prometheus==2.2.0
 django-prometheus==2.2.0
-django-redis==5.1.0
+django-redis==5.2.0
 django-rq==2.5.1
 django-rq==2.5.1
 django-tables2==2.4.1
 django-tables2==2.4.1
 django-taggit==2.0.0
 django-taggit==2.0.0
@@ -21,7 +21,7 @@ markdown-include==0.6.0
 mkdocs-material==8.1.3
 mkdocs-material==8.1.3
 netaddr==0.8.0
 netaddr==0.8.0
 Pillow==8.4.0
 Pillow==8.4.0
-psycopg2-binary==2.9.2
+psycopg2-binary==2.9.3
 PyYAML==6.0
 PyYAML==6.0
 social-auth-app-django==5.0.0
 social-auth-app-django==5.0.0
 social-auth-core==4.1.0
 social-auth-core==4.1.0

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików