Jeremy Stretch 6 лет назад
Родитель
Сommit
fbde6282b2

+ 1 - 0
CHANGELOG.md

@@ -2,6 +2,7 @@
 
 
 ## Enhancements
 ## Enhancements
 
 
+* [#2813](https://github.com/digitalocean/netbox/issues/2813) - Add tenant group filters
 * [#3150](https://github.com/digitalocean/netbox/issues/3150) - Fix formatting of cable length during cable trace
 * [#3150](https://github.com/digitalocean/netbox/issues/3150) - Fix formatting of cable length during cable trace
 * [#3085](https://github.com/digitalocean/netbox/issues/3085) - Catch all exceptions during export template rendering
 * [#3085](https://github.com/digitalocean/netbox/issues/3085) - Catch all exceptions during export template rendering
 
 

+ 3 - 3
netbox/circuits/filters.py

@@ -3,13 +3,13 @@ from django.db.models import Q
 
 
 from dcim.models import Site
 from dcim.models import Site
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
-from tenancy.filterset import TenancyFilterSet
+from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 from .constants import CIRCUIT_STATUS_CHOICES
 from .constants import CIRCUIT_STATUS_CHOICES
 from .models import Provider, Circuit, CircuitTermination, CircuitType
 from .models import Provider, Circuit, CircuitTermination, CircuitType
 
 
 
 
-class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class ProviderFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -54,7 +54,7 @@ class CircuitTypeFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, django_filters.FilterSet):
+class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'

+ 1 - 2
netbox/circuits/forms.py

@@ -268,8 +268,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
 
 
 class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Circuit
     model = Circuit
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'type', 'provider', 'status']
+    field_order = ['q', 'type', 'provider', 'status', 'site', 'tenant_group', 'tenant', 'commit_rate']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'

+ 17 - 6
netbox/dcim/filters.py

@@ -1,13 +1,13 @@
 import django_filters
 import django_filters
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
 from netaddr import EUI
 from netaddr import EUI
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
 
 
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
-from tenancy.filterset import TenancyFilterSet
+from tenancy.filtersets import TenancyFilterSet
+from tenancy.models import Tenant
 from utilities.constants import COLOR_CHOICES
 from utilities.constants import COLOR_CHOICES
 from utilities.filters import (
 from utilities.filters import (
     NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
     NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
@@ -39,7 +39,7 @@ class RegionFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class SiteFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -114,7 +114,7 @@ class RackRoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug', 'color']
         fields = ['name', 'slug', 'color']
 
 
 
 
-class RackFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class RackFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -180,7 +180,7 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSe
         )
         )
 
 
 
 
-class RackReservationFilter(TenancyFilterSet, django_filters.FilterSet):
+class RackReservationFilter(TenancyFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -875,7 +875,7 @@ class InventoryItemFilter(DeviceComponentFilterSet):
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class VirtualChassisFilter(TenancyFilterSet, django_filters.FilterSet):
+class VirtualChassisFilter(django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -891,6 +891,17 @@ class VirtualChassisFilter(TenancyFilterSet, django_filters.FilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Site name (slug)',
         label='Site name (slug)',
     )
     )
+    tenant_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='master__tenant',
+        queryset=Tenant.objects.all(),
+        label='Tenant (ID)',
+    )
+    tenant = django_filters.ModelMultipleChoiceFilter(
+        field_name='master__tenant__slug',
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        label='Tenant (slug)',
+    )
     tag = TagFilter()
     tag = TagFilter()
 
 
     class Meta:
     class Meta:

+ 54 - 33
netbox/dcim/forms.py

@@ -14,7 +14,7 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
 from ipam.models import IPAddress, VLAN, VLANGroup
 from ipam.models import IPAddress, VLAN, VLANGroup
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
 from tenancy.formset import TenancyFilterForm
 from tenancy.formset import TenancyFilterForm
-from tenancy.models import Tenant
+from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
     APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
     BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField,
     BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, ColorSelect, CommentField,
@@ -259,8 +259,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
 
 
 class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Site
     model = Site
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'status', 'region']
+    field_order = ['q', 'status', 'region', 'tenant_group', 'tenant']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -591,8 +590,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
 
 
 class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Rack
     model = Rack
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'site', 'group_id']
+    field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -674,32 +672,6 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
         return unit_choices
         return unit_choices
 
 
 
 
-class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, forms.Form):
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'site', 'group_id']
-    q = forms.CharField(
-        required=False,
-        label='Search'
-    )
-    site = FilterChoiceField(
-        queryset=Site.objects.all(),
-        to_field_name='slug',
-        widget=APISelectMultiple(
-            api_url="/api/dcim/sites/",
-            value_field="slug",
-        )
-    )
-    group_id = FilterChoiceField(
-        queryset=RackGroup.objects.select_related('site'),
-        label='Rack group',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/dcim/rack-groups/",
-            null_option=True,
-        )
-    )
-
-
 class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
 class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=RackReservation.objects.all(),
         queryset=RackReservation.objects.all(),
@@ -728,6 +700,31 @@ class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
         nullable_fields = []
         nullable_fields = []
 
 
 
 
+class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
+    field_order = ['q', 'site', 'group_id', 'tenant_group', 'tenant']
+    q = forms.CharField(
+        required=False,
+        label='Search'
+    )
+    site = FilterChoiceField(
+        queryset=Site.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+        )
+    )
+    group_id = FilterChoiceField(
+        queryset=RackGroup.objects.select_related('site'),
+        label='Rack group',
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/rack-groups/",
+            null_option=True,
+        )
+    )
+
+
 #
 #
 # Manufacturers
 # Manufacturers
 #
 #
@@ -1622,8 +1619,10 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
 
 
 class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Device
     model = Device
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'region', 'site', 'rack_group_id', 'rack_id', 'role']
+    field_order = [
+        'q', 'region', 'site', 'rack_group_id', 'rack_id', 'status', 'role', 'tenant_group', 'tenant',
+        'manufacturer_id', 'device_type_id', 'mac_address', 'has_primary_ip',
+    ]
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -3074,9 +3073,31 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
     site = FilterChoiceField(
     site = FilterChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/dcim/sites/",
+            value_field="slug",
+        )
+    )
+    tenant_group = FilterChoiceField(
+        queryset=TenantGroup.objects.all(),
+        to_field_name='slug',
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenant-groups/",
+            value_field="slug",
+            null_option=True,
+            filter_for={
+                'tenant': 'group'
+            }
+        )
     )
     )
     tenant = FilterChoiceField(
     tenant = FilterChoiceField(
         queryset=Tenant.objects.all(),
         queryset=Tenant.objects.all(),
         to_field_name='slug',
         to_field_name='slug',
         null_label='-- None --',
         null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
     )
     )

+ 24 - 2
netbox/extras/filters.py

@@ -4,7 +4,7 @@ from django.db.models import Q
 from taggit.models import Tag
 from taggit.models import Tag
 
 
 from dcim.models import DeviceRole, Platform, Region, Site
 from dcim.models import DeviceRole, Platform, Region, Site
-from tenancy.filterset import TenancyFilterSet
+from tenancy.models import Tenant, TenantGroup
 from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
 from .constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_TYPE_BOOLEAN, CF_TYPE_SELECT
 from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, TopologyMap
 from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, TopologyMap
 
 
@@ -122,7 +122,7 @@ class TopologyMapFilter(django_filters.FilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class ConfigContextFilter(TenancyFilterSet, django_filters.FilterSet):
+class ConfigContextFilter(django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -171,6 +171,28 @@ class ConfigContextFilter(TenancyFilterSet, django_filters.FilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Platform (slug)',
         label='Platform (slug)',
     )
     )
+    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant_groups',
+        queryset=TenantGroup.objects.all(),
+        label='Tenant group',
+    )
+    tenant_group = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant_groups__slug',
+        queryset=TenantGroup.objects.all(),
+        to_field_name='slug',
+        label='Tenant group (slug)',
+    )
+    tenant_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenants',
+        queryset=Tenant.objects.all(),
+        label='Tenant',
+    )
+    tenant = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenants__slug',
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        label='Tenant (slug)',
+    )
 
 
     class Meta:
     class Meta:
         model = ConfigContext
         model = ConfigContext

+ 18 - 6
netbox/extras/forms.py

@@ -8,7 +8,7 @@ from taggit.forms import TagField
 from taggit.models import Tag
 from taggit.models import Tag
 
 
 from dcim.models import DeviceRole, Platform, Region, Site
 from dcim.models import DeviceRole, Platform, Region, Site
-from tenancy.formset import TenancyFilterForm
+from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
     add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
     FilterChoiceField, LaxURLField, JSONField, SlugField,
     FilterChoiceField, LaxURLField, JSONField, SlugField,
@@ -274,7 +274,7 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
         ]
         ]
 
 
 
 
-class ConfigContextFilterForm(TenancyFilterForm, BootstrapMixin, forms.Form):
+class ConfigContextFilterForm(BootstrapMixin, forms.Form):
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -311,10 +311,22 @@ class ConfigContextFilterForm(TenancyFilterForm, BootstrapMixin, forms.Form):
             value_field="slug",
             value_field="slug",
         )
         )
     )
     )
-
-    class Meta:
-        # Order the form fields, fields not listed are appended
-        field_order = ['q', 'type', 'provider', 'status']
+    tenant_group = FilterChoiceField(
+        queryset=TenantGroup.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenant-groups/",
+            value_field="slug",
+        )
+    )
+    tenant = FilterChoiceField(
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+        )
+    )
 
 
 
 
 #
 #

+ 6 - 6
netbox/ipam/filters.py

@@ -6,14 +6,14 @@ from netaddr.core import AddrFormatError
 
 
 from dcim.models import Site, Device, Interface
 from dcim.models import Site, Device, Interface
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
-from tenancy.filterset import TenancyFilterSet
+from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
 from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
 from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 
 
 
 
-class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class VRFFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -49,7 +49,7 @@ class RIRFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug', 'is_private']
         fields = ['name', 'slug', 'is_private']
 
 
 
 
-class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class AggregateFilter(CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -97,7 +97,7 @@ class RoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -234,7 +234,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.Filter
         return queryset.filter(prefix__net_mask_length=value)
         return queryset.filter(prefix__net_mask_length=value)
 
 
 
 
-class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -364,7 +364,7 @@ class VLANGroupFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
+class VLANFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'

+ 9 - 8
netbox/ipam/forms.py

@@ -100,8 +100,7 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
 
 
 class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = VRF
     model = VRF
-    # Order the form fields, fields not listed are appended
-    field_order = ['q']
+    field_order = ['q', 'tenant_group', 'tenant']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -492,8 +491,10 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
 
 
 class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Prefix
     model = Prefix
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'within_include', 'family', 'mask_length', 'vrf']
+    field_order = [
+        'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'site', 'role', 'tenant_group', 'tenant',
+        'is_pool', 'expand',
+    ]
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -931,8 +932,9 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
 
 
 class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = IPAddress
     model = IPAddress
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'parent', 'family', 'mask_length', 'vrf']
+    field_order = [
+        'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'tenant_group', 'tenant',
+    ]
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -1200,8 +1202,7 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
 
 
 class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = VLAN
     model = VLAN
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'site', 'group_id']
+    field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'

+ 0 - 0
netbox/tenancy/filterset.py → netbox/tenancy/filtersets.py


+ 1 - 0
netbox/tenancy/forms.py

@@ -118,6 +118,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
 #
 #
 # Tenancy form extension
 # Tenancy form extension
 #
 #
+
 class TenancyForm(ChainedFieldsMixin, forms.Form):
 class TenancyForm(ChainedFieldsMixin, forms.Form):
     tenant_group = forms.ModelChoiceField(
     tenant_group = forms.ModelChoiceField(
         queryset=TenantGroup.objects.all(),
         queryset=TenantGroup.objects.all(),

+ 1 - 2
netbox/virtualization/filters.py

@@ -1,12 +1,11 @@
 import django_filters
 import django_filters
-from django.core.exceptions import ObjectDoesNotExist
 from django.db.models import Q
 from django.db.models import Q
 from netaddr import EUI
 from netaddr import EUI
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
 
 
 from dcim.models import DeviceRole, Interface, Platform, Region, Site
 from dcim.models import DeviceRole, Interface, Platform, Region, Site
 from extras.filters import CustomFieldFilterSet
 from extras.filters import CustomFieldFilterSet
-from tenancy.filterset import TenancyFilterSet
+from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
 from .constants import VM_STATUS_CHOICES
 from .constants import VM_STATUS_CHOICES
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine

+ 7 - 5
netbox/virtualization/forms.py

@@ -9,7 +9,7 @@ from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomField
 from ipam.models import IPAddress
 from ipam.models import IPAddress
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
 from tenancy.formset import TenancyFilterForm
 from tenancy.formset import TenancyFilterForm
-from tenancy.models import Tenant, TenantGroup
+from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
     ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
@@ -337,8 +337,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     class Meta:
     class Meta:
         model = VirtualMachine
         model = VirtualMachine
         fields = [
         fields = [
-            'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
-            'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
+            'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
+            'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
         ]
         ]
         help_texts = {
         help_texts = {
             'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
             'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
@@ -523,8 +523,10 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
 
 
 class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
 class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = VirtualMachine
     model = VirtualMachine
-    # Order the form fields, fields not listed are appended
-    field_order = ['q', 'cluster_group', 'cluster_type', 'cluster_id', 'region', 'site']
+    field_order = [
+        'q', 'cluster_group', 'cluster_type', 'cluster_id', 'status', 'role', 'region', 'site', 'tenant_group',
+        'tenant', 'platform',
+    ]
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'