Fixes #8645; Allow filtering on core models in the UI and API for contact assignments

This commit is contained in:
Alex Gittings 2022-02-24 17:08:38 +00:00
parent 4913d7ee39
commit 36d6dd1ca9
13 changed files with 264 additions and 144 deletions

View File

@ -6,7 +6,7 @@ from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSeriali
from dcim.api.serializers import LinkTerminationSerializer from dcim.api.serializers import LinkTerminationSerializer
from netbox.api import ChoiceField from netbox.api import ChoiceField
from netbox.api.serializers import PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer from netbox.api.serializers import PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.nested_serializers import NestedTenantSerializer, NestedContactAssignmentSerializer
from .nested_serializers import * from .nested_serializers import *
@ -17,12 +17,17 @@ from .nested_serializers import *
class ProviderSerializer(PrimaryModelSerializer): class ProviderSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail') url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
circuit_count = serializers.IntegerField(read_only=True) circuit_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Provider model = Provider
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'id', 'url', 'display', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count', 'comments', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
] ]
@ -78,12 +83,17 @@ class CircuitSerializer(PrimaryModelSerializer):
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
termination_a = CircuitCircuitTerminationSerializer(read_only=True) termination_a = CircuitCircuitTerminationSerializer(read_only=True)
termination_z = CircuitCircuitTerminationSerializer(read_only=True) termination_z = CircuitCircuitTerminationSerializer(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Circuit model = Circuit
fields = [ fields = [
'id', 'url', 'display', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'id', 'url', 'display', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate',
'description', 'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'description', 'termination_a', 'termination_z', 'comments', 'contacts', 'tags', 'custom_fields', 'created',
'last_updated', 'last_updated',
] ]

View File

@ -5,7 +5,7 @@ from dcim.filtersets import CableTerminationFilterSet
from dcim.models import Region, Site, SiteGroup from dcim.models import Region, Site, SiteGroup
from extras.filters import TagFilter from extras.filters import TagFilter
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import (TenancyFilterSet, ContactModelFilterSet)
from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filters import TreeNodeMultipleChoiceFilter
from .choices import * from .choices import *
from .models import * from .models import *
@ -19,7 +19,7 @@ __all__ = (
) )
class ProviderFilterSet(PrimaryModelFilterSet): class ProviderFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -118,7 +118,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet): class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',

View File

@ -5,7 +5,7 @@ from circuits.choices import CircuitStatusChoices
from circuits.models import * from circuits.models import *
from dcim.models import Region, Site, SiteGroup from dcim.models import Region, Site, SiteGroup
from extras.forms import CustomFieldModelFilterForm from extras.forms import CustomFieldModelFilterForm
from tenancy.forms import TenancyFilterForm from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
__all__ = ( __all__ = (
@ -16,12 +16,13 @@ __all__ = (
) )
class ProviderFilterForm(CustomFieldModelFilterForm): class ProviderFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = Provider model = Provider
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['region_id', 'site_group_id', 'site_id'], ['region_id', 'site_group_id', 'site_id'],
['asn'], ['asn'],
['contact', 'contact_role']
] ]
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -68,7 +69,7 @@ class CircuitTypeFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = Circuit model = Circuit
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
@ -76,6 +77,7 @@ class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
['type_id', 'status', 'commit_rate'], ['type_id', 'status', 'commit_rate'],
['region_id', 'site_group_id', 'site_id'], ['region_id', 'site_group_id', 'site_id'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['contact', 'contact_role']
] ]
type_id = DynamicModelMultipleChoiceField( type_id = DynamicModelMultipleChoiceField(
queryset=CircuitType.objects.all(), queryset=CircuitType.objects.all(),

View File

@ -6,6 +6,7 @@ from timezone_field.rest_framework import TimeZoneSerializerField
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from tenancy.models import ContactAssignment
from ipam.api.nested_serializers import NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer from ipam.api.nested_serializers import NestedASNSerializer, NestedIPAddressSerializer, NestedVLANSerializer
from ipam.models import ASN, VLAN from ipam.models import ASN, VLAN
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
@ -13,7 +14,7 @@ from netbox.api.serializers import (
NestedGroupModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer, NestedGroupModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer,
) )
from netbox.config import ConfigItem from netbox.config import ConfigItem
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.nested_serializers import NestedTenantSerializer, NestedContactAssignmentSerializer
from users.api.nested_serializers import NestedUserSerializer from users.api.nested_serializers import NestedUserSerializer
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from virtualization.api.nested_serializers import NestedClusterSerializer from virtualization.api.nested_serializers import NestedClusterSerializer
@ -85,11 +86,16 @@ class RegionSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
parent = NestedRegionSerializer(required=False, allow_null=True, default=None) parent = NestedRegionSerializer(required=False, allow_null=True, default=None)
site_count = serializers.IntegerField(read_only=True) site_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Region model = Region
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields', 'created', 'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'contacts', 'tags', 'custom_fields', 'created',
'last_updated', 'site_count', '_depth', 'last_updated', 'site_count', '_depth',
] ]
@ -98,11 +104,16 @@ class SiteGroupSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
parent = NestedSiteGroupSerializer(required=False, allow_null=True, default=None) parent = NestedSiteGroupSerializer(required=False, allow_null=True, default=None)
site_count = serializers.IntegerField(read_only=True) site_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = SiteGroup model = SiteGroup
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields', 'created', 'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'contacts', 'tags', 'custom_fields', 'created',
'last_updated', 'site_count', '_depth', 'last_updated', 'site_count', '_depth',
] ]
@ -113,6 +124,13 @@ class SiteSerializer(PrimaryModelSerializer):
region = NestedRegionSerializer(required=False, allow_null=True) region = NestedRegionSerializer(required=False, allow_null=True)
group = NestedSiteGroupSerializer(required=False, allow_null=True) group = NestedSiteGroupSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
contacts = SerializedPKRelatedField(
queryset=ContactAssignment.objects.all(),
serializer=NestedContactAssignmentSerializer,
required=False,
allow_null=True,
many=True
)
time_zone = TimeZoneSerializerField(required=False) time_zone = TimeZoneSerializerField(required=False)
asns = SerializedPKRelatedField( asns = SerializedPKRelatedField(
queryset=ASN.objects.all(), queryset=ASN.objects.all(),
@ -126,7 +144,7 @@ class SiteSerializer(PrimaryModelSerializer):
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
prefix_count = serializers.IntegerField(read_only=True) prefix_count = serializers.IntegerField(read_only=True)
rack_count = serializers.IntegerField(read_only=True) rack_count = serializers.IntegerField(read_only=True)
virtualmachine_count = serializers.IntegerField(read_only=True) virtualmachine_count = serializers.IntegerField(read_only=True,)
vlan_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
@ -134,7 +152,7 @@ class SiteSerializer(PrimaryModelSerializer):
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'asns', 'id', 'url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'asns',
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'contact_phone', 'contact_email', 'comments', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated',
'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count', 'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
] ]
@ -150,11 +168,16 @@ class LocationSerializer(NestedGroupModelSerializer):
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
rack_count = serializers.IntegerField(read_only=True) rack_count = serializers.IntegerField(read_only=True)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Location model = Location
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'tenant', 'description', 'tags', 'custom_fields', 'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'tenant', 'description', 'contacts', 'tags', 'custom_fields',
'created', 'last_updated', 'rack_count', 'device_count', '_depth', 'created', 'last_updated', 'rack_count', 'device_count', '_depth',
] ]
@ -185,13 +208,18 @@ class RackSerializer(PrimaryModelSerializer):
outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False) outer_unit = ChoiceField(choices=RackDimensionUnitChoices, allow_blank=True, required=False)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
powerfeed_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Rack model = Rack
fields = [ fields = [
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial', 'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count', 'comments', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
] ]
@ -269,11 +297,17 @@ class ManufacturerSerializer(PrimaryModelSerializer):
devicetype_count = serializers.IntegerField(read_only=True) devicetype_count = serializers.IntegerField(read_only=True)
inventoryitem_count = serializers.IntegerField(read_only=True) inventoryitem_count = serializers.IntegerField(read_only=True)
platform_count = serializers.IntegerField(read_only=True) platform_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Manufacturer model = Manufacturer
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'id', 'url', 'display', 'name', 'slug', 'description', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated',
'devicetype_count', 'inventoryitem_count', 'platform_count', 'devicetype_count', 'inventoryitem_count', 'platform_count',
] ]
@ -469,6 +503,11 @@ class DeviceSerializer(PrimaryModelSerializer):
cluster = NestedClusterSerializer(required=False, allow_null=True) cluster = NestedClusterSerializer(required=False, allow_null=True)
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None) virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)
vc_position = serializers.IntegerField(allow_null=True, max_value=255, min_value=0, default=None) vc_position = serializers.IntegerField(allow_null=True, max_value=255, min_value=0, default=None)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Device model = Device
@ -476,7 +515,7 @@ class DeviceSerializer(PrimaryModelSerializer):
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments',
'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', 'local_context_data', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated',
] ]
@swagger_serializer_method(serializer_or_field=NestedDeviceSerializer) @swagger_serializer_method(serializer_or_field=NestedDeviceSerializer)
@ -498,7 +537,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
fields = [ fields = [
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'contacts',
'tags', 'custom_fields', 'config_context', 'created', 'last_updated', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
] ]
@ -875,11 +914,16 @@ class PowerPanelSerializer(PrimaryModelSerializer):
default=None default=None
) )
powerfeed_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = PowerPanel model = PowerPanel
fields = [ fields = [
'id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count', 'id', 'url', 'display', 'site', 'location', 'name', 'contacts', 'tags', 'custom_fields', 'powerfeed_count',
'created', 'last_updated', 'created', 'last_updated',
] ]

View File

@ -7,8 +7,8 @@ from ipam.models import ASN
from netbox.filtersets import ( from netbox.filtersets import (
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
) )
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from tenancy.models import Tenant from tenancy.models import *
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.filters import ( from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter, ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
@ -62,7 +62,7 @@ __all__ = (
) )
class RegionFilterSet(OrganizationalModelFilterSet): class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Parent region (ID)', label='Parent region (ID)',
@ -80,7 +80,7 @@ class RegionFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description']
class SiteGroupFilterSet(OrganizationalModelFilterSet): class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=SiteGroup.objects.all(), queryset=SiteGroup.objects.all(),
label='Parent site group (ID)', label='Parent site group (ID)',
@ -98,7 +98,7 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description']
class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet): class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -167,7 +167,7 @@ class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
return queryset.filter(qs_filter) return queryset.filter(qs_filter)
class LocationFilterSet(TenancyFilterSet, OrganizationalModelFilterSet): class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalModelFilterSet):
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='site__region', field_name='site__region',
@ -240,7 +240,7 @@ class RackRoleFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'color'] fields = ['id', 'name', 'slug', 'color']
class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet): class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -398,7 +398,7 @@ class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
) )
class ManufacturerFilterSet(OrganizationalModelFilterSet): class ManufacturerFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
tag = TagFilter() tag = TagFilter()
class Meta: class Meta:
@ -608,7 +608,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'napalm_driver', 'description'] fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet): class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -1289,7 +1289,7 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
return queryset return queryset
class PowerPanelFilterSet(PrimaryModelFilterSet): class PowerPanelFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',

View File

@ -5,9 +5,12 @@ from django.utils.translation import gettext as _
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import * from dcim.models import *
from tenancy.models import *
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
from ipam.models import ASN from ipam.models import ASN
from tenancy.forms import TenancyFilterForm from tenancy.forms import (
TenancyFilterForm, ContactModelFilterForm
)
from utilities.forms import ( from utilities.forms import (
APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect, APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
@ -98,7 +101,7 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
) )
class RegionFilterForm(CustomFieldModelFilterForm): class RegionFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = Region model = Region
parent_id = DynamicModelMultipleChoiceField( parent_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -108,7 +111,7 @@ class RegionFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class SiteGroupFilterForm(CustomFieldModelFilterForm): class SiteGroupFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = SiteGroup model = SiteGroup
parent_id = DynamicModelMultipleChoiceField( parent_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(), queryset=SiteGroup.objects.all(),
@ -118,13 +121,14 @@ class SiteGroupFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = Site model = Site
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['status', 'region_id', 'group_id'], ['status', 'region_id', 'group_id'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['asn_id'] ['asn_id'],
['contact', 'contact_role'],
] ]
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=SiteStatusChoices, choices=SiteStatusChoices,
@ -148,13 +152,13 @@ class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
) )
tag = TagFilterField(model) tag = TagFilterField(model)
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
model = Location model = Location
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['region_id', 'site_group_id', 'site_id', 'parent_id'], ['region_id', 'site_group_id', 'site_id', 'parent_id'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['contact', 'contact_role'],
] ]
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -192,7 +196,7 @@ class RackRoleFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = Rack model = Rack
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
@ -200,6 +204,7 @@ class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
['status', 'role_id'], ['status', 'role_id'],
['type', 'width', 'serial', 'asset_tag'], ['type', 'width', 'serial', 'asset_tag'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['contact', 'contact_role']
] ]
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -303,7 +308,7 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class ManufacturerFilterForm(CustomFieldModelFilterForm): class ManufacturerFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = Manufacturer model = Manufacturer
tag = TagFilterField(model) tag = TagFilterField(model)
@ -390,7 +395,7 @@ class PlatformFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm): class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = Device model = Device
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
@ -402,6 +407,7 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports', 'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports',
'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data', 'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data',
], ],
['contact', 'contact_role'],
] ]
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
@ -636,11 +642,12 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class PowerPanelFilterForm(CustomFieldModelFilterForm): class PowerPanelFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = PowerPanel model = PowerPanel
field_groups = ( field_groups = (
('q', 'tag'), ('q', 'tag'),
('region_id', 'site_group_id', 'site_id', 'location_id') ('region_id', 'site_group_id', 'site_id', 'location_id'),
('contact', 'contact_role')
) )
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),

View File

@ -40,11 +40,16 @@ class TenantSerializer(PrimaryModelSerializer):
vlan_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True)
vrf_count = serializers.IntegerField(read_only=True) vrf_count = serializers.IntegerField(read_only=True)
cluster_count = serializers.IntegerField(read_only=True) cluster_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Tenant model = Tenant
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'id', 'url', 'display', 'name', 'slug', 'group', 'description', 'comments', 'contacts', 'tags', 'custom_fields',
'created', 'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count', 'created', 'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count',
'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count', 'cluster_count', 'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count', 'cluster_count',
] ]

View File

@ -15,95 +15,10 @@ __all__ = (
'TenancyFilterSet', 'TenancyFilterSet',
'TenantFilterSet', 'TenantFilterSet',
'TenantGroupFilterSet', 'TenantGroupFilterSet',
'ContactModelFilterSet'
) )
#
# Tenancy
#
class TenantGroupFilterSet(OrganizationalModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
label='Tenant group (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=TenantGroup.objects.all(),
to_field_name='slug',
label='Tenant group (slug)',
)
tag = TagFilter()
class Meta:
model = TenantGroup
fields = ['id', 'name', 'slug', 'description']
class TenantFilterSet(PrimaryModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='group',
lookup_expr='in',
label='Tenant group (ID)',
)
group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='group',
lookup_expr='in',
to_field_name='slug',
label='Tenant group (slug)',
)
tag = TagFilter()
class Meta:
model = Tenant
fields = ['id', 'name', 'slug']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(slug__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
)
class TenancyFilterSet(django_filters.FilterSet):
"""
An inheritable FilterSet for models which support Tenant assignment.
"""
tenant_group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='tenant__group',
lookup_expr='in',
label='Tenant Group (ID)',
)
tenant_group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='tenant__group',
to_field_name='slug',
lookup_expr='in',
label='Tenant Group (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
field_name='tenant__slug',
to_field_name='slug',
label='Tenant (slug)',
)
# #
# Contacts # Contacts
# #
@ -191,3 +106,102 @@ class ContactAssignmentFilterSet(ChangeLoggedModelFilterSet):
class Meta: class Meta:
model = ContactAssignment model = ContactAssignment
fields = ['id', 'content_type_id', 'object_id', 'priority'] fields = ['id', 'content_type_id', 'object_id', 'priority']
class ContactModelFilterSet(django_filters.FilterSet):
contact = django_filters.ModelMultipleChoiceFilter(
field_name='contacts__contact',
queryset=Contact.objects.all(),
label='Contact',
)
contact_role = django_filters.ModelMultipleChoiceFilter(
field_name='contacts__role',
queryset=ContactRole.objects.all(),
label='Contact Role'
)
#
# Tenancy
#
class TenantGroupFilterSet(OrganizationalModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
label='Tenant group (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=TenantGroup.objects.all(),
to_field_name='slug',
label='Tenant group (slug)',
)
tag = TagFilter()
class Meta:
model = TenantGroup
fields = ['id', 'name', 'slug', 'description']
class TenantFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='group',
lookup_expr='in',
label='Tenant group (ID)',
)
group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='group',
lookup_expr='in',
to_field_name='slug',
label='Tenant group (slug)',
)
tag = TagFilter()
class Meta:
model = Tenant
fields = ['id', 'name', 'slug']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(slug__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
)
class TenancyFilterSet(django_filters.FilterSet):
"""
An inheritable FilterSet for models which support Tenant assignment.
"""
tenant_group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='tenant__group',
lookup_expr='in',
label='Tenant Group (ID)',
)
tenant_group = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
field_name='tenant__group',
to_field_name='slug',
lookup_expr='in',
label='Tenant Group (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
field_name='tenant__slug',
to_field_name='slug',
label='Tenant (slug)',
)

View File

@ -2,6 +2,7 @@ from django.utils.translation import gettext as _
from extras.forms import CustomFieldModelFilterForm from extras.forms import CustomFieldModelFilterForm
from tenancy.models import * from tenancy.models import *
from tenancy.forms import ContactModelFilterForm
from utilities.forms import DynamicModelMultipleChoiceField, TagFilterField from utilities.forms import DynamicModelMultipleChoiceField, TagFilterField
__all__ = ( __all__ = (
@ -27,11 +28,12 @@ class TenantGroupFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class TenantFilterForm(CustomFieldModelFilterForm): class TenantFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = Tenant model = Tenant
field_groups = ( field_groups = (
('q', 'tag'), ('q', 'tag'),
('group_id',), ('group_id',),
('contact', 'contact_role')
) )
group_id = DynamicModelMultipleChoiceField( group_id = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),

View File

@ -1,12 +1,13 @@
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from tenancy.models import Tenant, TenantGroup from tenancy.models import *
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
__all__ = ( __all__ = (
'TenancyForm', 'TenancyForm',
'TenancyFilterForm', 'TenancyFilterForm',
'ContactModelFilterForm'
) )
@ -44,3 +45,16 @@ class TenancyFilterForm(forms.Form):
}, },
label=_('Tenant') label=_('Tenant')
) )
class ContactModelFilterForm(forms.Form):
contact = DynamicModelMultipleChoiceField(
queryset=Contact.objects.all(),
required=False,
label=_('Contact')
)
contact_role = DynamicModelMultipleChoiceField(
queryset=ContactRole.objects.all(),
required=False,
label=_('Contact Role')
)

View File

@ -7,7 +7,7 @@ from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSer
from ipam.models import VLAN from ipam.models import VLAN
from netbox.api import ChoiceField, SerializedPKRelatedField from netbox.api import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import PrimaryModelSerializer from netbox.api.serializers import PrimaryModelSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.nested_serializers import NestedTenantSerializer, NestedContactAssignmentSerializer
from virtualization.choices import * from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
from .nested_serializers import * from .nested_serializers import *
@ -32,11 +32,16 @@ class ClusterTypeSerializer(PrimaryModelSerializer):
class ClusterGroupSerializer(PrimaryModelSerializer): class ClusterGroupSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail') url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail')
cluster_count = serializers.IntegerField(read_only=True) cluster_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = ClusterGroup model = ClusterGroup
fields = [ fields = [
'id', 'url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'id', 'url', 'display', 'name', 'slug', 'description', 'contacts', 'tags', 'custom_fields', 'created', 'last_updated',
'cluster_count', 'cluster_count',
] ]
@ -49,11 +54,16 @@ class ClusterSerializer(PrimaryModelSerializer):
site = NestedSiteSerializer(required=False, allow_null=True, default=None) site = NestedSiteSerializer(required=False, allow_null=True, default=None)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
virtualmachine_count = serializers.IntegerField(read_only=True) virtualmachine_count = serializers.IntegerField(read_only=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = Cluster model = Cluster
fields = [ fields = [
'id', 'url', 'display', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'tags', 'custom_fields', 'id', 'url', 'display', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'contacts', 'tags', 'custom_fields',
'created', 'last_updated', 'device_count', 'virtualmachine_count', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
] ]
@ -73,12 +83,17 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip',
'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'contacts', 'tags',
'custom_fields', 'created', 'last_updated', 'custom_fields', 'created', 'last_updated',
] ]
validators = [] validators = []
@ -86,11 +101,16 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer): class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
config_context = serializers.SerializerMethodField() config_context = serializers.SerializerMethodField()
contacts = NestedContactAssignmentSerializer(
required=False,
allow_null=True,
many=True
)
class Meta(VirtualMachineSerializer.Meta): class Meta(VirtualMachineSerializer.Meta):
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip',
'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'contacts', 'tags',
'custom_fields', 'config_context', 'created', 'last_updated', 'custom_fields', 'config_context', 'created', 'last_updated',
] ]

View File

@ -5,7 +5,7 @@ from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.filters import TagFilter from extras.filters import TagFilter
from extras.filtersets import LocalConfigContextFilterSet from extras.filtersets import LocalConfigContextFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
from .choices import * from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -27,7 +27,7 @@ class ClusterTypeFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description']
class ClusterGroupFilterSet(OrganizationalModelFilterSet): class ClusterGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
tag = TagFilter() tag = TagFilter()
class Meta: class Meta:
@ -35,7 +35,7 @@ class ClusterGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description']
class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet): class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',
@ -111,7 +111,7 @@ class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
) )
class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet): class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
from tenancy.forms import TenancyFilterForm from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
from utilities.forms import ( from utilities.forms import (
DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
) )
@ -24,18 +24,19 @@ class ClusterTypeFilterForm(CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class ClusterGroupFilterForm(CustomFieldModelFilterForm): class ClusterGroupFilterForm(ContactModelFilterForm, CustomFieldModelFilterForm):
model = ClusterGroup model = ClusterGroup
tag = TagFilterField(model) tag = TagFilterField(model)
class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = Cluster model = Cluster
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['group_id', 'type_id'], ['group_id', 'type_id'],
['region_id', 'site_group_id', 'site_id'], ['region_id', 'site_group_id', 'site_id'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['contact', 'contact_role'],
] ]
type_id = DynamicModelMultipleChoiceField( type_id = DynamicModelMultipleChoiceField(
queryset=ClusterType.objects.all(), queryset=ClusterType.objects.all(),
@ -71,7 +72,7 @@ class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm): class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, ContactModelFilterForm, CustomFieldModelFilterForm):
model = VirtualMachine model = VirtualMachine
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
@ -79,6 +80,7 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
['region_id', 'site_group_id', 'site_id'], ['region_id', 'site_group_id', 'site_id'],
['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'], ['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
['contact', 'contact_role'],
] ]
cluster_group_id = DynamicModelMultipleChoiceField( cluster_group_id = DynamicModelMultipleChoiceField(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),