Просмотр исходного кода

Fixes #2781: Fixes filter by regions on site and device list

* Add Device filter
dansheps 7 лет назад
Родитель
Сommit
b4d7f9ea43

+ 2 - 24
netbox/circuits/filters.py

@@ -3,7 +3,7 @@ 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.models import Tenant, TenantGroup
+from tenancy.filters 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
@@ -54,7 +54,7 @@ class CircuitTypeFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -87,28 +87,6 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
         choices=CIRCUIT_STATUS_CHOICES,
         choices=CIRCUIT_STATUS_CHOICES,
         null_value=None
         null_value=None
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         field_name='terminations__site',
         field_name='terminations__site',
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),

+ 5 - 26
netbox/circuits/forms.py

@@ -3,8 +3,8 @@ from taggit.forms import TagField
 
 
 from dcim.models import Site
 from dcim.models import Site
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
-from tenancy.forms import TenancyForm
-from tenancy.models import Tenant, TenantGroup
+from tenancy.forms import TenancyForm, TenancyFilterForm
+from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
     APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
     APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
     FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
     FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
@@ -265,8 +265,10 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
         ]
         ]
 
 
 
 
-class CircuitFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -292,29 +294,6 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False,
         required=False,
         widget=StaticSelect2Multiple()
         widget=StaticSelect2Multiple()
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     site = FilterChoiceField(
     site = FilterChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         to_field_name='slug',
         to_field_name='slug',

+ 2 - 2
netbox/circuits/tables.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
-from tenancy.tables import COL_TENANTGROUP_TENANT
+from tenancy.tables import COL_TENANT
 from utilities.tables import BaseTable, ToggleColumn
 from utilities.tables import BaseTable, ToggleColumn
 from .models import Circuit, CircuitType, Provider
 from .models import Circuit, CircuitType, Provider
 
 
@@ -76,7 +76,7 @@ class CircuitTable(BaseTable):
     cid = tables.LinkColumn(verbose_name='ID')
     cid = tables.LinkColumn(verbose_name='ID')
     provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
     provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
     termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
     termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
     termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')
     termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')
 
 

+ 6 - 105
netbox/dcim/filters.py

@@ -6,7 +6,7 @@ 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.models import Tenant, TenantGroup
+from tenancy.filters import TenancyFilterSet
 from utilities.constants import COLOR_CHOICES
 from utilities.constants import COLOR_CHOICES
 from utilities.filters import NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter
 from utilities.filters import NameSlugSearchFilterSet, NullableCharFieldFilter, NumericInFilter, TagFilter
 from virtualization.models import Cluster
 from virtualization.models import Cluster
@@ -36,7 +36,7 @@ class RegionFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -59,28 +59,6 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
         field_name='slug',
         field_name='slug',
         label='Region (slug)',
         label='Region (slug)',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     tag = TagFilter()
     tag = TagFilter()
 
 
     class Meta:
     class Meta:
@@ -142,7 +120,7 @@ class RackRoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug', 'color']
         fields = ['name', 'slug', 'color']
 
 
 
 
-class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class RackFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -172,28 +150,6 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Group',
         label='Group',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(
         choices=RACK_STATUS_CHOICES,
         choices=RACK_STATUS_CHOICES,
         null_value=None
         null_value=None
@@ -230,7 +186,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
         )
         )
 
 
 
 
-class RackReservationFilter(django_filters.FilterSet):
+class RackReservationFilter(TenancyFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -265,28 +221,6 @@ class RackReservationFilter(django_filters.FilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Group',
         label='Group',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     user_id = django_filters.ModelMultipleChoiceFilter(
     user_id = django_filters.ModelMultipleChoiceFilter(
         queryset=User.objects.all(),
         queryset=User.objects.all(),
         label='User (ID)',
         label='User (ID)',
@@ -492,7 +426,7 @@ class PlatformFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class DeviceFilter(CustomFieldFilterSet):
+class DeviceFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -527,28 +461,6 @@ class DeviceFilter(CustomFieldFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Role (slug)',
         label='Role (slug)',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     platform_id = django_filters.ModelMultipleChoiceFilter(
     platform_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         label='Platform (ID)',
         label='Platform (ID)',
@@ -978,7 +890,7 @@ class InventoryItemFilter(DeviceComponentFilterSet):
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class VirtualChassisFilter(django_filters.FilterSet):
+class VirtualChassisFilter(TenancyFilterSet, django_filters.FilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -994,17 +906,6 @@ class VirtualChassisFilter(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:

+ 14 - 98
netbox/dcim/forms.py

@@ -12,8 +12,8 @@ from timezone_field import TimeZoneFormField
 
 
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from ipam.models import IPAddress, VLAN, VLANGroup
 from ipam.models import IPAddress, VLAN, VLANGroup
-from tenancy.forms import TenancyForm
-from tenancy.models import Tenant, TenantGroup
+from tenancy.forms import TenancyForm, TenancyFilterForm
+from tenancy.models import Tenant
 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,
@@ -256,8 +256,10 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
         ]
         ]
 
 
 
 
-class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
+class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Site
     model = Site
+    # Order the form fields, fields not listed are appended
+    field_order = ['q', 'status', 'region']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -276,29 +278,6 @@ class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
             value_field="slug",
             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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
 
 
 
 
 #
 #
@@ -609,8 +588,10 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
         ]
         ]
 
 
 
 
-class RackFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -632,29 +613,6 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=RACK_STATUS_CHOICES,
         choices=RACK_STATUS_CHOICES,
         required=False,
         required=False,
@@ -715,7 +673,9 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
         return unit_choices
         return unit_choices
 
 
 
 
-class RackReservationFilterForm(BootstrapMixin, forms.Form):
+class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, forms.Form):
+    # Order the form fields, fields not listed are appended
+    field_order = ['q', 'site', 'group_id']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -737,29 +697,6 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
 
 
 
 
 class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
 class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
@@ -1682,8 +1619,10 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
         ]
         ]
 
 
 
 
-class DeviceFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -1742,29 +1681,6 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     manufacturer_id = FilterChoiceField(
     manufacturer_id = FilterChoiceField(
         queryset=Manufacturer.objects.all(),
         queryset=Manufacturer.objects.all(),
         label='Manufacturer',
         label='Manufacturer',

+ 5 - 5
netbox/dcim/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
-from tenancy.tables import COL_TENANT, COL_TENANTGROUP_TENANT
+from tenancy.tables import COL_TENANT
 from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
 from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
 from .models import (
 from .models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@@ -214,7 +214,7 @@ class SiteTable(BaseTable):
     name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
     name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
     region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
     region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(BaseTable.Meta):
     class Meta(BaseTable.Meta):
         model = Site
         model = Site
@@ -275,7 +275,7 @@ class RackTable(BaseTable):
     name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
     name = tables.LinkColumn(order_by=('_nat1', '_nat2', '_nat3'))
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
     status = tables.TemplateColumn(STATUS_LABEL)
     status = tables.TemplateColumn(STATUS_LABEL)
     role = tables.TemplateColumn(RACK_ROLE)
     role = tables.TemplateColumn(RACK_ROLE)
     u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
     u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
@@ -305,7 +305,7 @@ class RackDetailTable(RackTable):
 
 
 class RackReservationTable(BaseTable):
 class RackReservationTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
     unit_list = tables.Column(orderable=False, verbose_name='Units')
     unit_list = tables.Column(orderable=False, verbose_name='Units')
     actions = tables.TemplateColumn(
     actions = tables.TemplateColumn(
@@ -512,7 +512,7 @@ class DeviceTable(BaseTable):
         template_code=DEVICE_LINK
         template_code=DEVICE_LINK
     )
     )
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
     status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
     device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
     device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')

+ 5 - 93
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.models import Tenant, TenantGroup
+from tenancy.filters 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(CustomFieldFilterSet, django_filters.FilterSet):
+class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -22,28 +22,6 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
         method='search',
         method='search',
         label='Search',
         label='Search',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     tag = TagFilter()
     tag = TagFilter()
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
@@ -119,7 +97,7 @@ class RoleFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -158,28 +136,6 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
         to_field_name='rd',
         to_field_name='rd',
         label='VRF (RD)',
         label='VRF (RD)',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     site_id = django_filters.ModelMultipleChoiceFilter(
     site_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         label='Site (ID)',
         label='Site (ID)',
@@ -278,7 +234,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
         return queryset.filter(prefix__net_mask_length=value)
         return queryset.filter(prefix__net_mask_length=value)
 
 
 
 
-class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -309,28 +265,6 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
         to_field_name='rd',
         to_field_name='rd',
         label='VRF (RD)',
         label='VRF (RD)',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     device = django_filters.CharFilter(
     device = django_filters.CharFilter(
         method='filter_device',
         method='filter_device',
         field_name='name',
         field_name='name',
@@ -430,7 +364,7 @@ class VLANGroupFilter(NameSlugSearchFilterSet):
         fields = ['name', 'slug']
         fields = ['name', 'slug']
 
 
 
 
-class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
+class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, django_filters.FilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -459,28 +393,6 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Group',
         label='Group',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Role.objects.all(),
         queryset=Role.objects.all(),
         label='Role (ID)',
         label='Role (ID)',

+ 14 - 98
netbox/ipam/forms.py

@@ -5,8 +5,8 @@ from taggit.forms import TagField
 
 
 from dcim.models import Site, Rack, Device, Interface
 from dcim.models import Site, Rack, Device, Interface
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
 from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
-from tenancy.forms import TenancyForm
-from tenancy.models import Tenant, TenantGroup
+from tenancy.forms import TenancyForm, TenancyFilterForm
+from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
     CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
     CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
@@ -97,35 +97,14 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
         ]
         ]
 
 
 
 
-class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
+class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = VRF
     model = VRF
+    # Order the form fields, fields not listed are appended
+    field_order = ['q']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
 
 
 
 
 #
 #
@@ -510,8 +489,10 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
         ]
         ]
 
 
 
 
-class PrefixFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -548,29 +529,6 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=PREFIX_STATUS_CHOICES,
         choices=PREFIX_STATUS_CHOICES,
         required=False,
         required=False,
@@ -972,8 +930,10 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
     )
     )
 
 
 
 
-class IPAddressFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -1010,29 +970,6 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=IPADDRESS_STATUS_CHOICES,
         choices=IPADDRESS_STATUS_CHOICES,
         required=False,
         required=False,
@@ -1264,8 +1201,10 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
         ]
         ]
 
 
 
 
-class VLANFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -1289,29 +1228,6 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
             null_option=True,
             null_option=True,
         )
         )
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url="/api/tenancy/tenants/",
-            value_field="slug",
-            null_option=True,
-        )
-    )
     status = forms.MultipleChoiceField(
     status = forms.MultipleChoiceField(
         choices=VLAN_STATUS_CHOICES,
         choices=VLAN_STATUS_CHOICES,
         required=False,
         required=False,

+ 6 - 6
netbox/ipam/tables.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
 from dcim.models import Interface
 from dcim.models import Interface
-from tenancy.tables import COL_TENANT,COL_TENANTGROUP_TENANT
+from tenancy.tables import COL_TENANT
 from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
 from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 
 
@@ -191,7 +191,7 @@ class VRFTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
     name = tables.LinkColumn()
     name = tables.LinkColumn()
     rd = tables.Column(verbose_name='RD')
     rd = tables.Column(verbose_name='RD')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(BaseTable.Meta):
     class Meta(BaseTable.Meta):
         model = VRF
         model = VRF
@@ -323,7 +323,7 @@ class PrefixTable(BaseTable):
 
 
 class PrefixDetailTable(PrefixTable):
 class PrefixDetailTable(PrefixTable):
     utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
     utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(PrefixTable.Meta):
     class Meta(PrefixTable.Meta):
         fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
         fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
@@ -354,7 +354,7 @@ class IPAddressDetailTable(IPAddressTable):
     nat_inside = tables.LinkColumn(
     nat_inside = tables.LinkColumn(
         'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
         'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
     )
     )
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(IPAddressTable.Meta):
     class Meta(IPAddressTable.Meta):
         fields = (
         fields = (
@@ -415,7 +415,7 @@ class VLANTable(BaseTable):
     vid = tables.TemplateColumn(VLAN_LINK, verbose_name='ID')
     vid = tables.TemplateColumn(VLAN_LINK, verbose_name='ID')
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     group = tables.LinkColumn('ipam:vlangroup_vlans', args=[Accessor('group.pk')], verbose_name='Group')
     group = tables.LinkColumn('ipam:vlangroup_vlans', args=[Accessor('group.pk')], verbose_name='Group')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
     status = tables.TemplateColumn(STATUS_LABEL)
     status = tables.TemplateColumn(STATUS_LABEL)
     role = tables.TemplateColumn(VLAN_ROLE_LINK)
     role = tables.TemplateColumn(VLAN_ROLE_LINK)
 
 
@@ -429,7 +429,7 @@ class VLANTable(BaseTable):
 
 
 class VLANDetailTable(VLANTable):
 class VLANDetailTable(VLANTable):
     prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
     prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(VLANTable.Meta):
     class Meta(VLANTable.Meta):
         fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
         fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')

+ 25 - 0
netbox/tenancy/filters.py

@@ -46,3 +46,28 @@ class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet):
             Q(description__icontains=value) |
             Q(description__icontains=value) |
             Q(comments__icontains=value)
             Q(comments__icontains=value)
         )
         )
+
+
+class TenancyFilterSet(django_filters.FilterSet):
+    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant__group__id',
+        queryset=TenantGroup.objects.all(),
+        to_field_name='id',
+        label='Tenant Group (ID)',
+    )
+    tenant_group = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant__group__slug',
+        queryset=TenantGroup.objects.all(),
+        to_field_name='slug',
+        label='Tenant Group (slug)',
+    )
+    tenant_id = django_filters.ModelMultipleChoiceFilter(
+        queryset=Tenant.objects.all(),
+        label='Tenant (ID)',
+    )
+    tenant = django_filters.ModelMultipleChoiceFilter(
+        field_name='tenant__slug',
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        label='Tenant (slug)',
+    )

+ 28 - 0
netbox/tenancy/forms.py

@@ -115,6 +115,34 @@ class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
         )
         )
     )
     )
 
 
+#
+# Tenancy filtering form extension
+#
+class TenancyFilterForm(forms.Form):
+    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(
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        null_label='-- None --',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field="slug",
+            null_option=True,
+        )
+    )
+
 
 
 #
 #
 # Tenancy form extension
 # Tenancy form extension

+ 2 - 24
netbox/virtualization/filters.py

@@ -6,7 +6,7 @@ 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.models import Tenant, TenantGroup
+from tenancy.filters import TenancyFilterSet
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
 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
@@ -80,7 +80,7 @@ class ClusterFilter(CustomFieldFilterSet):
         )
         )
 
 
 
 
-class VirtualMachineFilter(CustomFieldFilterSet):
+class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet):
     id__in = NumericInFilter(
     id__in = NumericInFilter(
         field_name='id',
         field_name='id',
         lookup_expr='in'
         lookup_expr='in'
@@ -150,28 +150,6 @@ class VirtualMachineFilter(CustomFieldFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Role (slug)',
         label='Role (slug)',
     )
     )
-    tenant_group_id = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__id',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='id',
-        label='Tenant Group (ID)',
-    )
-    tenant_group = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__group__slug',
-        queryset=TenantGroup.objects.all(),
-        to_field_name='slug',
-        label='Tenant Group (slug)',
-    )
-    tenant_id = django_filters.ModelMultipleChoiceFilter(
-        queryset=Tenant.objects.all(),
-        label='Tenant (ID)',
-    )
-    tenant = django_filters.ModelMultipleChoiceFilter(
-        field_name='tenant__slug',
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        label='Tenant (slug)',
-    )
     platform_id = django_filters.ModelMultipleChoiceFilter(
     platform_id = django_filters.ModelMultipleChoiceFilter(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         label='Platform (ID)',
         label='Platform (ID)',

+ 5 - 26
netbox/virtualization/forms.py

@@ -7,7 +7,7 @@ from dcim.forms import INTERFACE_MODE_HELP_TEXT
 from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
 from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
 from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from ipam.models import IPAddress
 from ipam.models import IPAddress
-from tenancy.forms import TenancyForm
+from tenancy.forms import TenancyForm, TenancyFilterForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
@@ -336,7 +336,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     class Meta:
     class Meta:
         model = VirtualMachine
         model = VirtualMachine
         fields = [
         fields = [
-            'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
+            'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
             'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
             'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
         ]
         ]
         help_texts = {
         help_texts = {
@@ -520,8 +520,10 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
         ]
         ]
 
 
 
 
-class VirtualMachineFilterForm(BootstrapMixin, 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']
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
         label='Search'
         label='Search'
@@ -591,29 +593,6 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
         required=False,
         required=False,
         widget=StaticSelect2Multiple()
         widget=StaticSelect2Multiple()
     )
     )
-    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(
-        queryset=Tenant.objects.all(),
-        to_field_name='slug',
-        null_label='-- None --',
-        widget=APISelectMultiple(
-            api_url='/api/tenancy/tenants/',
-            value_field="slug",
-            null_option=True,
-        )
-    )
     platform = FilterChoiceField(
     platform = FilterChoiceField(
         queryset=Platform.objects.all(),
         queryset=Platform.objects.all(),
         to_field_name='slug',
         to_field_name='slug',

+ 2 - 2
netbox/virtualization/tables.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
 from dcim.models import Interface
 from dcim.models import Interface
-from tenancy.tables import COL_TENANTGROUP_TENANT
+from tenancy.tables import COL_TENANT
 from utilities.tables import BaseTable, ToggleColumn
 from utilities.tables import BaseTable, ToggleColumn
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
@@ -103,7 +103,7 @@ class VirtualMachineTable(BaseTable):
     status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
     status = tables.TemplateColumn(template_code=VIRTUALMACHINE_STATUS)
     cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
     cluster = tables.LinkColumn('virtualization:cluster', args=[Accessor('cluster.pk')])
     role = tables.TemplateColumn(VIRTUALMACHINE_ROLE)
     role = tables.TemplateColumn(VIRTUALMACHINE_ROLE)
-    tenant = tables.TemplateColumn(template_code=COL_TENANTGROUP_TENANT)
+    tenant = tables.TemplateColumn(template_code=COL_TENANT)
 
 
     class Meta(BaseTable.Meta):
     class Meta(BaseTable.Meta):
         model = VirtualMachine
         model = VirtualMachine