Initial work on #5892

This commit is contained in:
Jeremy Stretch 2021-03-08 11:11:48 -05:00
parent 1c66733b8a
commit 07e412e06c
29 changed files with 1117 additions and 184 deletions

View File

@ -2,7 +2,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from dcim.filters import CableTerminationFilterSet, PathEndpointFilterSet from dcim.filters import CableTerminationFilterSet, PathEndpointFilterSet
from dcim.models import Region, Site from dcim.models import Region, Site, SiteGroup
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from utilities.filters import ( from utilities.filters import (
@ -37,6 +37,19 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='circuits__terminations__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='circuits__terminations__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='circuits__terminations__site', field_name='circuits__terminations__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
@ -102,17 +115,6 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe
choices=CircuitStatusChoices, choices=CircuitStatusChoices,
null_value=None null_value=None
) )
site_id = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='terminations__site__region', field_name='terminations__site__region',
@ -126,6 +128,30 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='terminations__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='terminations__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
tag = TagFilter() tag = TagFilter()
class Meta: class Meta:

View File

@ -3,7 +3,7 @@ from django.test import TestCase
from circuits.choices import * from circuits.choices import *
from circuits.filters import * from circuits.filters import *
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.models import Cable, Region, Site from dcim.models import Cable, Region, Site, SiteGroup
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
@ -27,13 +27,20 @@ class ProviderTestCase(TestCase):
Region(name='Test Region 1', slug='test-region-1'), Region(name='Test Region 1', slug='test-region-1'),
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -74,13 +81,6 @@ class ProviderTestCase(TestCase):
params = {'account': ['1234', '2345']} params = {'account': ['1234', '2345']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self):
sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_region(self): def test_region(self):
regions = Region.objects.all()[:2] regions = Region.objects.all()[:2]
params = {'region_id': [regions[0].pk, regions[1].pk]} params = {'region_id': [regions[0].pk, regions[1].pk]}
@ -88,6 +88,20 @@ class ProviderTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self):
sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class CircuitTypeTestCase(TestCase): class CircuitTypeTestCase(TestCase):
queryset = CircuitType.objects.all() queryset = CircuitType.objects.all()
@ -127,14 +141,21 @@ class CircuitTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -223,6 +244,13 @@ class CircuitTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}

View File

@ -35,6 +35,7 @@ __all__ = [
'NestedRearPortTemplateSerializer', 'NestedRearPortTemplateSerializer',
'NestedRegionSerializer', 'NestedRegionSerializer',
'NestedSiteSerializer', 'NestedSiteSerializer',
'NestedSiteGroupSerializer',
'NestedVirtualChassisSerializer', 'NestedVirtualChassisSerializer',
] ]
@ -53,6 +54,16 @@ class NestedRegionSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'name', 'slug', 'site_count', '_depth'] fields = ['id', 'url', 'name', 'slug', 'site_count', '_depth']
class NestedSiteGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
site_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = models.SiteGroup
fields = ['id', 'url', 'name', 'slug', 'site_count', '_depth']
class NestedSiteSerializer(WritableNestedSerializer): class NestedSiteSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')

View File

@ -6,13 +6,7 @@ from rest_framework.validators import UniqueTogetherValidator
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import ( from dcim.models import *
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from netbox.api.serializers import CustomFieldModelSerializer from netbox.api.serializers import CustomFieldModelSerializer
from extras.api.serializers import TaggedObjectSerializer from extras.api.serializers import TaggedObjectSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
@ -94,10 +88,24 @@ class RegionSerializer(NestedGroupModelSerializer):
] ]
class SiteGroupSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:sitegroup-detail')
parent = NestedRegionSerializer(required=False, allow_null=True)
site_count = serializers.IntegerField(read_only=True)
class Meta:
model = SiteGroup
fields = [
'id', 'url', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
'site_count', '_depth',
]
class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
status = ChoiceField(choices=SiteStatusChoices, required=False) status = ChoiceField(choices=SiteStatusChoices, required=False)
region = NestedRegionSerializer(required=False, allow_null=True) region = NestedRegionSerializer(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)
time_zone = TimeZoneField(required=False) time_zone = TimeZoneField(required=False)
circuit_count = serializers.IntegerField(read_only=True) circuit_count = serializers.IntegerField(read_only=True)
@ -110,10 +118,10 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
class Meta: class Meta:
model = Site model = Site
fields = [ fields = [
'id', 'url', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'id', 'url', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count', 'contact_phone', 'contact_email', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count', 'circuit_count', 'device_count', 'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
] ]

View File

@ -7,6 +7,7 @@ router.APIRootView = views.DCIMRootView
# Sites # Sites
router.register('regions', views.RegionViewSet) router.register('regions', views.RegionViewSet)
router.register('site-groups', views.SiteGroupViewSet)
router.register('sites', views.SiteViewSet) router.register('sites', views.SiteViewSet)
# Racks # Racks

View File

@ -16,13 +16,7 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
from circuits.models import Circuit from circuits.models import Circuit
from dcim import filters from dcim import filters
from dcim.models import ( from dcim.models import *
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
from ipam.models import Prefix, VLAN from ipam.models import Prefix, VLAN
from netbox.api.views import ModelViewSet from netbox.api.views import ModelViewSet
@ -111,6 +105,22 @@ class RegionViewSet(CustomFieldModelViewSet):
filterset_class = filters.RegionFilterSet filterset_class = filters.RegionFilterSet
#
# Site groups
#
class SiteGroupViewSet(CustomFieldModelViewSet):
queryset = SiteGroup.objects.add_related_count(
SiteGroup.objects.all(),
Site,
'group',
'site_count',
cumulative=True
)
serializer_class = serializers.SiteGroupSerializer
filterset_class = filters.SiteGroupFilterSet
# #
# Sites # Sites
# #

View File

@ -12,13 +12,7 @@ from utilities.filters import (
from virtualization.models import Cluster from virtualization.models import Cluster
from .choices import * from .choices import *
from .constants import * from .constants import *
from .models import ( from .models import *
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
__all__ = ( __all__ = (
@ -58,6 +52,7 @@ __all__ = (
'RearPortTemplateFilterSet', 'RearPortTemplateFilterSet',
'RegionFilterSet', 'RegionFilterSet',
'SiteFilterSet', 'SiteFilterSet',
'SiteGroupFilterSet',
'VirtualChassisFilterSet', 'VirtualChassisFilterSet',
) )
@ -79,6 +74,23 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description']
class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
label='Parent site group (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=SiteGroup.objects.all(),
to_field_name='slug',
label='Parent site group (slug)',
)
class Meta:
model = SiteGroup
fields = ['id', 'name', 'slug', 'description']
class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
@ -96,11 +108,22 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
) )
region = TreeNodeMultipleChoiceFilter( region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='region',
lookup_expr='in', lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='group',
lookup_expr='in',
label='Group (ID)',
)
group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
lookup_expr='in',
to_field_name='slug',
label='Group (slug)',
)
tag = TagFilter() tag = TagFilter()
class Meta: class Meta:
@ -145,6 +168,19 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -196,6 +232,19 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -565,6 +614,19 @@ class DeviceFilterSet(
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -721,6 +783,19 @@ class DeviceComponentFilterSet(CustomFieldModelFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='device__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='device__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__site', field_name='device__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
@ -1043,6 +1118,19 @@ class VirtualChassisFilterSet(BaseFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='master__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='master__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='master__site', field_name='master__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),
@ -1231,6 +1319,19 @@ class PowerPanelFilterSet(BaseFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -1286,6 +1387,19 @@ class PowerFeedFilterSet(
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='power_panel__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='power_panel__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='power_panel__site', field_name='power_panel__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),

View File

@ -31,12 +31,7 @@ from utilities.forms import (
from virtualization.models import Cluster, ClusterGroup from virtualization.models import Cluster, ClusterGroup
from .choices import * from .choices import *
from .constants import * from .constants import *
from .models import ( from .models import *
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
DEVICE_BY_PK_RE = r'{\d+\}' DEVICE_BY_PK_RE = r'{\d+\}'
@ -61,7 +56,7 @@ def get_device_by_name_or_pk(name):
class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm): class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm):
field_order = [ field_order = [
'q', 'region_id', 'site_id' 'q', 'region_id', 'site_group_id', 'site_id'
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
@ -72,6 +67,11 @@ class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm):
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -209,6 +209,45 @@ class RegionFilterForm(BootstrapMixin, forms.Form):
) )
#
# Site groups
#
class SiteGroupForm(BootstrapMixin, CustomFieldModelForm):
parent = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False
)
slug = SlugField()
class Meta:
model = SiteGroup
fields = (
'parent', 'name', 'slug', 'description',
)
class SiteGroupCSVForm(CustomFieldModelCSVForm):
parent = CSVModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
to_field_name='name',
help_text='Name of parent site group'
)
class Meta:
model = SiteGroup
fields = SiteGroup.csv_headers
class SiteGroupFilterForm(BootstrapMixin, forms.Form):
model = Site
q = forms.CharField(
required=False,
label=_('Search')
)
# #
# Sites # Sites
# #
@ -218,6 +257,10 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False required=False
) )
group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False
)
slug = SlugField() slug = SlugField()
comments = CommentField() comments = CommentField()
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
@ -228,12 +271,14 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class Meta: class Meta:
model = Site model = Site
fields = [ fields = [
'name', 'slug', 'status', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asn', 'time_zone',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
'contact_email', 'comments', 'tags', 'contact_phone', 'contact_email', 'comments', 'tags',
] ]
fieldsets = ( fieldsets = (
('Site', ('name', 'slug', 'status', 'region', 'facility', 'asn', 'time_zone', 'description', 'tags')), ('Site', (
'name', 'slug', 'status', 'region', 'group', 'facility', 'asn', 'time_zone', 'description', 'tags',
)),
('Tenancy', ('tenant_group', 'tenant')), ('Tenancy', ('tenant_group', 'tenant')),
('Contact Info', ( ('Contact Info', (
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
@ -279,6 +324,12 @@ class SiteCSVForm(CustomFieldModelCSVForm):
to_field_name='name', to_field_name='name',
help_text='Assigned region' help_text='Assigned region'
) )
group = CSVModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned group'
)
tenant = CSVModelChoiceField( tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
@ -311,6 +362,10 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False required=False
) )
group = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
required=False
)
tenant = DynamicModelChoiceField( tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False required=False
@ -333,7 +388,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
class Meta: class Meta:
nullable_fields = [ nullable_fields = [
'region', 'tenant', 'asn', 'description', 'time_zone', 'region', 'group', 'tenant', 'asn', 'description', 'time_zone',
] ]
@ -354,6 +409,11 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
required=False, required=False,
label=_('Region') label=_('Region')
) )
group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Group')
)
tag = TagFilterField(model) tag = TagFilterField(model)
@ -4451,7 +4511,7 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm):
class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
model = VirtualChassis model = VirtualChassis
field_order = ['q', 'region_id', 'site_id', 'tenant_group_id', 'tenant_id'] field_order = ['q', 'region_id', 'site_group_id', 'site_id', 'tenant_group_id', 'tenant_id']
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label=_('Search') label=_('Search')
@ -4461,6 +4521,11 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -4580,6 +4645,11 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -4803,6 +4873,11 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,

View File

@ -0,0 +1,39 @@
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('dcim', '0129_interface_parent'),
]
operations = [
migrations.CreateModel(
name='SiteGroup',
fields=[
('created', models.DateField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('lft', models.PositiveIntegerField(editable=False)),
('rght', models.PositiveIntegerField(editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.sitegroup')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='site',
name='group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.sitegroup'),
),
]

View File

@ -41,5 +41,6 @@ __all__ = (
'RearPortTemplate', 'RearPortTemplate',
'Region', 'Region',
'Site', 'Site',
'SiteGroup',
'VirtualChassis', 'VirtualChassis',
) )

View File

@ -17,6 +17,7 @@ from utilities.querysets import RestrictedQuerySet
__all__ = ( __all__ = (
'Region', 'Region',
'Site', 'Site',
'SiteGroup',
) )
@ -27,7 +28,9 @@ __all__ = (
@extras_features('custom_fields', 'export_templates', 'webhooks') @extras_features('custom_fields', 'export_templates', 'webhooks')
class Region(NestedGroupModel): class Region(NestedGroupModel):
""" """
Sites can be grouped within geographic Regions. A region represents a geographic collection of sites. For example, you might create regions representing countries,
states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are
also considered to be members of its parent and ancestor region(s).
""" """
parent = TreeForeignKey( parent = TreeForeignKey(
to='self', to='self',
@ -70,6 +73,58 @@ class Region(NestedGroupModel):
).count() ).count()
#
# Site groups
#
@extras_features('custom_fields', 'export_templates', 'webhooks')
class SiteGroup(NestedGroupModel):
"""
A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be
nested recursively to form a hierarchy.
"""
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
db_index=True
)
name = models.CharField(
max_length=100,
unique=True
)
slug = models.SlugField(
max_length=100,
unique=True
)
description = models.CharField(
max_length=200,
blank=True
)
csv_headers = ['name', 'slug', 'parent', 'description']
def get_absolute_url(self):
return "{}?group={}".format(reverse('dcim:site_list'), self.slug)
def to_csv(self):
return (
self.name,
self.slug,
self.parent.name if self.parent else None,
self.description,
)
def get_site_count(self):
return Site.objects.filter(
Q(group=self) |
Q(group__in=self.get_descendants())
).count()
# #
# Sites # Sites
# #
@ -105,6 +160,13 @@ class Site(PrimaryModel):
blank=True, blank=True,
null=True null=True
) )
group = models.ForeignKey(
to='dcim.SiteGroup',
on_delete=models.SET_NULL,
related_name='sites',
blank=True,
null=True
)
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -175,11 +237,12 @@ class Site(PrimaryModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = [ csv_headers = [
'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone', 'description',
'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email', 'comments',
] ]
clone_fields = [ clone_fields = [
'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'status', 'region', 'group', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address',
'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email',
] ]
@ -198,6 +261,7 @@ class Site(PrimaryModel):
self.slug, self.slug,
self.get_status_display(), self.get_status_display(),
self.region.name if self.region else None, self.region.name if self.region else None,
self.group.name if self.group else None,
self.tenant.name if self.tenant else None, self.tenant.name if self.tenant else None,
self.facility, self.facility,
self.asn, self.asn,

View File

@ -1,12 +1,13 @@
import django_tables2 as tables import django_tables2 as tables
from dcim.models import Region, Site from dcim.models import Region, Site, SiteGroup
from tenancy.tables import TenantColumn from tenancy.tables import TenantColumn
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, MPTTColumn, TagColumn, ToggleColumn from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, MPTTColumn, TagColumn, ToggleColumn
__all__ = ( __all__ = (
'RegionTable', 'RegionTable',
'SiteTable', 'SiteTable',
'SiteGroupTable',
) )
@ -28,6 +29,24 @@ class RegionTable(BaseTable):
default_columns = ('pk', 'name', 'site_count', 'description', 'actions') default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
#
# Site groups
#
class SiteGroupTable(BaseTable):
pk = ToggleColumn()
name = MPTTColumn()
site_count = tables.Column(
verbose_name='Sites'
)
actions = ButtonsColumn(SiteGroup)
class Meta(BaseTable.Meta):
model = SiteGroup
fields = ('pk', 'name', 'slug', 'site_count', 'description', 'actions')
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
# #
# Sites # Sites
# #
@ -41,6 +60,9 @@ class SiteTable(BaseTable):
region = tables.Column( region = tables.Column(
linkify=True linkify=True
) )
group = tables.Column(
linkify=True
)
tenant = TenantColumn() tenant = TenantColumn()
tags = TagColumn( tags = TagColumn(
url_name='dcim:site_list' url_name='dcim:site_list'
@ -49,8 +71,8 @@ class SiteTable(BaseTable):
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = Site model = Site
fields = ( fields = (
'pk', 'name', 'slug', 'status', 'facility', 'region', 'tenant', 'asn', 'time_zone', 'description', 'pk', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn', 'time_zone', 'description',
'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
'contact_email', 'tags', 'contact_email', 'tags',
) )
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn', 'description') default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'asn', 'description')

View File

@ -4,12 +4,7 @@ from rest_framework import status
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import ( from dcim.models import *
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
from ipam.models import VLAN from ipam.models import VLAN
from utilities.testing import APITestCase, APIViewTestCases from utilities.testing import APITestCase, APIViewTestCases
from virtualization.models import Cluster, ClusterType from virtualization.models import Cluster, ClusterType
@ -102,14 +97,19 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
def setUpTestData(cls): def setUpTestData(cls):
regions = ( regions = (
Region.objects.create(name='Test Region 1', slug='test-region-1'), Region.objects.create(name='Region 1', slug='region-1'),
Region.objects.create(name='Test Region 2', slug='test-region-2'), Region.objects.create(name='Region 2', slug='region-2'),
)
groups = (
SiteGroup.objects.create(name='Site Group 1', slug='site-group-1'),
SiteGroup.objects.create(name='Site Group 2', slug='site-group-2'),
) )
sites = ( sites = (
Site(region=regions[0], name='Site 1', slug='site-1'), Site(region=regions[0], group=groups[0], name='Site 1', slug='site-1'),
Site(region=regions[0], name='Site 2', slug='site-2'), Site(region=regions[0], group=groups[0], name='Site 2', slug='site-2'),
Site(region=regions[0], name='Site 3', slug='site-3'), Site(region=regions[0], group=groups[0], name='Site 3', slug='site-3'),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -118,18 +118,21 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
'name': 'Site 4', 'name': 'Site 4',
'slug': 'site-4', 'slug': 'site-4',
'region': regions[1].pk, 'region': regions[1].pk,
'group': groups[1].pk,
'status': SiteStatusChoices.STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
{ {
'name': 'Site 5', 'name': 'Site 5',
'slug': 'site-5', 'slug': 'site-5',
'region': regions[1].pk, 'region': regions[1].pk,
'group': groups[1].pk,
'status': SiteStatusChoices.STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
{ {
'name': 'Site 6', 'name': 'Site 6',
'slug': 'site-6', 'slug': 'site-6',
'region': regions[1].pk, 'region': regions[1].pk,
'group': groups[1].pk,
'status': SiteStatusChoices.STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
] ]

View File

@ -3,13 +3,7 @@ from django.test import TestCase
from dcim.choices import * from dcim.choices import *
from dcim.filters import * from dcim.filters import *
from dcim.models import ( from dcim.models import *
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
PowerOutletTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from ipam.models import IPAddress from ipam.models import IPAddress
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from virtualization.models import Cluster, ClusterType from virtualization.models import Cluster, ClusterType
@ -80,6 +74,14 @@ class SiteTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
tenant_groups = ( tenant_groups = (
TenantGroup(name='Tenant group 1', slug='tenant-group-1'), TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
@ -96,9 +98,9 @@ class SiteTestCase(TestCase):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0], tenant=tenants[0], status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10, contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0], tenant=tenants[0], status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10, contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'),
Site(name='Site 2', slug='site-2', region=regions[1], tenant=tenants[1], status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20, contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1], tenant=tenants[1], status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20, contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'),
Site(name='Site 3', slug='site-3', region=regions[2], tenant=tenants[2], status=SiteStatusChoices.STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30, contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2], tenant=tenants[2], status=SiteStatusChoices.STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30, contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -153,6 +155,13 @@ class SiteTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
groups = SiteGroup.objects.all()[:2]
params = {'group_id': [groups[0].pk, groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'group': [groups[0].slug, groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tenant(self): def test_tenant(self):
tenants = Tenant.objects.all()[:2] tenants = Tenant.objects.all()[:2]
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]} params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
@ -183,10 +192,18 @@ class LocationTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -229,6 +246,13 @@ class LocationTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -290,10 +314,18 @@ class RackTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -388,6 +420,13 @@ class RackTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1161,10 +1200,18 @@ class DeviceTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -1324,6 +1371,13 @@ class DeviceTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1463,10 +1517,19 @@ class ConsolePortTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -1524,6 +1587,13 @@ class ConsolePortTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1559,10 +1629,19 @@ class ConsoleServerPortTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -1620,6 +1699,13 @@ class ConsoleServerPortTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1655,10 +1741,19 @@ class PowerPortTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -1724,6 +1819,13 @@ class PowerPortTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1759,10 +1861,19 @@ class PowerOutletTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -1825,6 +1936,13 @@ class PowerOutletTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -1860,10 +1978,19 @@ class InterfaceTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -1966,6 +2093,13 @@ class InterfaceTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2015,10 +2149,19 @@ class FrontPortTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -2082,6 +2225,13 @@ class FrontPortTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2117,10 +2267,19 @@ class RearPortTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -2178,6 +2337,13 @@ class RearPortTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2213,10 +2379,19 @@ class DeviceBayTestCase(TestCase):
) )
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = Site.objects.bulk_create(( sites = Site.objects.bulk_create((
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
@ -2256,6 +2431,13 @@ class DeviceBayTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2296,10 +2478,18 @@ class InventoryItemTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -2356,6 +2546,13 @@ class InventoryItemTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2409,10 +2606,18 @@ class VirtualChassisTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -2463,6 +2668,13 @@ class VirtualChassisTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2610,10 +2822,18 @@ class PowerPanelTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -2647,6 +2867,13 @@ class PowerPanelTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -2675,10 +2902,18 @@ class PowerFeedTestCase(TestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for group in groups:
group.save()
sites = ( sites = (
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0]),
Site(name='Site 2', slug='site-2', region=regions[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -2759,6 +2994,13 @@ class PowerFeedTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}

View File

@ -71,10 +71,17 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
for region in regions: for region in regions:
region.save() region.save()
groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
)
for group in groups:
group.save()
Site.objects.bulk_create([ Site.objects.bulk_create([
Site(name='Site 1', slug='site-1', region=regions[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=groups[1]),
Site(name='Site 2', slug='site-2', region=regions[0]), Site(name='Site 2', slug='site-2', region=regions[0], group=groups[1]),
Site(name='Site 3', slug='site-3', region=regions[0]), Site(name='Site 3', slug='site-3', region=regions[0], group=groups[1]),
]) ])
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie') tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
@ -84,6 +91,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'slug': 'site-x', 'slug': 'site-x',
'status': SiteStatusChoices.STATUS_PLANNED, 'status': SiteStatusChoices.STATUS_PLANNED,
'region': regions[1].pk, 'region': regions[1].pk,
'group': groups[1].pk,
'tenant': None, 'tenant': None,
'facility': 'Facility X', 'facility': 'Facility X',
'asn': 65001, 'asn': 65001,
@ -110,6 +118,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.bulk_edit_data = { cls.bulk_edit_data = {
'status': SiteStatusChoices.STATUS_PLANNED, 'status': SiteStatusChoices.STATUS_PLANNED,
'region': regions[1].pk, 'region': regions[1].pk,
'group': groups[1].pk,
'tenant': None, 'tenant': None,
'asn': 65009, 'asn': 65009,
'time_zone': pytz.timezone('US/Eastern'), 'time_zone': pytz.timezone('US/Eastern'),

View File

@ -3,11 +3,7 @@ from django.urls import path
from extras.views import ObjectChangeLogView, ImageAttachmentEditView from extras.views import ObjectChangeLogView, ImageAttachmentEditView
from ipam.views import ServiceEditView from ipam.views import ServiceEditView
from . import views from . import views
from .models import ( from .models import *
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, FrontPort, Interface,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, Location,
RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
)
app_name = 'dcim' app_name = 'dcim'
urlpatterns = [ urlpatterns = [
@ -21,6 +17,15 @@ urlpatterns = [
path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'), path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'),
path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}), path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
# Site groups
path('site-groups/', views.SiteGroupListView.as_view(), name='sitegroup_list'),
path('site-groups/add/', views.SiteGroupEditView.as_view(), name='sitegroup_add'),
path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'),
path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'),
path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'),
path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'),
path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}),
# Sites # Sites
path('sites/', views.SiteListView.as_view(), name='site_list'), path('sites/', views.SiteListView.as_view(), name='site_list'),
path('sites/add/', views.SiteEditView.as_view(), name='site_add'), path('sites/add/', views.SiteEditView.as_view(), name='site_add'),

View File

@ -31,7 +31,7 @@ from .models import (
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, PathEndpoint, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, InventoryItem, Manufacturer, PathEndpoint, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel,
PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis, SiteGroup, VirtualChassis,
) )
@ -138,6 +138,50 @@ class RegionBulkDeleteView(generic.BulkDeleteView):
table = tables.RegionTable table = tables.RegionTable
#
# Site groups
#
class SiteGroupListView(generic.ObjectListView):
queryset = SiteGroup.objects.add_related_count(
SiteGroup.objects.all(),
Site,
'group',
'site_count',
cumulative=True
)
filterset = filters.SiteGroupFilterSet
filterset_form = forms.SiteGroupFilterForm
table = tables.SiteGroupTable
class SiteGroupEditView(generic.ObjectEditView):
queryset = SiteGroup.objects.all()
model_form = forms.SiteGroupForm
class SiteGroupDeleteView(generic.ObjectDeleteView):
queryset = SiteGroup.objects.all()
class SiteGroupBulkImportView(generic.BulkImportView):
queryset = SiteGroup.objects.all()
model_form = forms.SiteGroupCSVForm
table = tables.SiteGroupTable
class SiteGroupBulkDeleteView(generic.BulkDeleteView):
queryset = SiteGroup.objects.add_related_count(
SiteGroup.objects.all(),
Site,
'group',
'site_count',
cumulative=True
)
filterset = filters.SiteGroupFilterSet
table = tables.SiteGroupTable
# #
# Sites # Sites
# #

View File

@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models import Q from django.db.models import Q
from django.forms import DateField, IntegerField, NullBooleanField from django.forms import DateField, IntegerField, NullBooleanField
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.filters import BaseFilterSet, ContentTypeFilter from utilities.filters import BaseFilterSet, ContentTypeFilter
from virtualization.models import Cluster, ClusterGroup from virtualization.models import Cluster, ClusterGroup
@ -129,6 +129,17 @@ class ConfigContextFilterSet(BaseFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group = django_filters.ModelMultipleChoiceFilter(
field_name='site_groups__slug',
queryset=SiteGroup.objects.all(),
to_field_name='slug',
label='Site group (slug)',
)
site_group_id = django_filters.ModelMultipleChoiceFilter(
field_name='site_groups',
queryset=SiteGroup.objects.all(),
label='Site group',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='sites', field_name='sites',
queryset=Site.objects.all(), queryset=Site.objects.all(),

View File

@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import ( from utilities.forms import (
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect, add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
@ -210,6 +210,10 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False required=False
) )
site_groups = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False
)
sites = DynamicModelMultipleChoiceField( sites = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False required=False
@ -249,8 +253,8 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = ConfigContext model = ConfigContext
fields = ( fields = (
'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'cluster_groups', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites', 'roles', 'platforms',
'clusters', 'tenant_groups', 'tenants', 'tags', 'data', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
) )
@ -280,8 +284,8 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
class ConfigContextFilterForm(BootstrapMixin, forms.Form): class ConfigContextFilterForm(BootstrapMixin, forms.Form):
field_order = [ field_order = [
'q', 'region_id', 'site_id', 'role_id', 'platform_id', 'cluster_group_id', 'cluster_id', 'tenant_group_id', 'q', 'region_id', 'site_group_id', 'site_id', 'role_id', 'platform_id', 'cluster_group_id', 'cluster_id',
'tenant_id', 'tenant_group_id', 'tenant_id',
] ]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
@ -292,6 +296,11 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
required=False, required=False,
label=_('Regions') label=_('Regions')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site groups')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,

View File

@ -0,0 +1,17 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0130_sitegroup'),
('extras', '0055_objectchange_data'),
]
operations = [
migrations.AddField(
model_name='configcontext',
name='site_groups',
field=models.ManyToManyField(blank=True, related_name='_extras_configcontext_site_groups_+', to='dcim.SiteGroup'),
),
]

View File

@ -386,6 +386,11 @@ class ConfigContext(ChangeLoggingMixin, BigIDModel):
related_name='+', related_name='+',
blank=True blank=True
) )
site_groups = models.ManyToManyField(
to='dcim.SiteGroup',
related_name='+',
blank=True
)
sites = models.ManyToManyField( sites = models.ManyToManyField(
to='dcim.Site', to='dcim.Site',
related_name='+', related_name='+',

View File

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import TestCase from django.test import TestCase
from dcim.models import DeviceRole, Platform, Rack, Region, Site from dcim.models import DeviceRole, Platform, Rack, Region, Site, SiteGroup
from extras.choices import ObjectChangeActionChoices from extras.choices import ObjectChangeActionChoices
from extras.filters import * from extras.filters import *
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, Tag from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, Tag
@ -132,10 +132,17 @@ class ConfigContextTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1'), Site(name='Test Site 1', slug='test-site-1'),
Site(name='Test Site 2', slug='test-site-2'), Site(name='Test Site 2', slug='test-site-2'),
@ -195,6 +202,7 @@ class ConfigContextTestCase(TestCase):
data='{"foo": 123}' data='{"foo": 123}'
) )
c.regions.set([regions[i]]) c.regions.set([regions[i]])
c.site_groups.set([site_groups[i]])
c.sites.set([sites[i]]) c.sites.set([sites[i]])
c.roles.set([device_roles[i]]) c.roles.set([device_roles[i]])
c.platforms.set([platforms[i]]) c.platforms.set([platforms[i]])
@ -224,6 +232,13 @@ class ConfigContextTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}

View File

@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from netaddr.core import AddrFormatError from netaddr.core import AddrFormatError
from dcim.models import Device, Interface, Region, Site from dcim.models import Device, Interface, Region, Site, SiteGroup
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from utilities.filters import ( from utilities.filters import (
@ -254,6 +254,19 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -535,6 +548,19 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -569,6 +595,19 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dcim.models import Device, Interface, Rack, Region, Site from dcim.models import Device, Interface, Rack, Region, Site, SiteGroup
from extras.forms import ( from extras.forms import (
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm, AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm,
) )
@ -547,8 +547,8 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
model = Prefix model = Prefix
field_order = [ field_order = [
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'region_id', 'site_id', 'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'region_id',
'role_id', 'tenant_group_id', 'tenant_id', 'is_pool', 'site_group_id', 'site_id', 'role_id', 'tenant_group_id', 'tenant_id', 'is_pool',
] ]
mask_length__lte = forms.IntegerField( mask_length__lte = forms.IntegerField(
widget=forms.HiddenInput() widget=forms.HiddenInput()
@ -599,6 +599,11 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -1125,6 +1130,11 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
@ -1292,7 +1302,9 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
model = VLAN model = VLAN
field_order = ['q', 'region_id', 'site_id', 'group_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id'] field_order = [
'q', 'region_id', 'site_group_id', 'site_id', 'group_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
]
q = forms.CharField( q = forms.CharField(
required=False, required=False,
label='Search' label='Search'
@ -1302,6 +1314,11 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField( site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,

View File

@ -1,6 +1,6 @@
from django.test import TestCase from django.test import TestCase
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site, SiteGroup
from ipam.choices import * from ipam.choices import *
from ipam.filters import * from ipam.filters import *
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
@ -343,14 +343,21 @@ class PrefixTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -468,6 +475,13 @@ class PrefixTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -701,14 +715,21 @@ class VLANGroupTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -743,6 +764,13 @@ class VLANGroupTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -763,14 +791,21 @@ class VLANTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -832,6 +867,13 @@ class VLANTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}

View File

@ -41,6 +41,19 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Group</td>
<td>
{% if object.group %}
{% for group in object.group.get_ancestors %}
<a href="{{ group.get_absolute_url }}">{{ group }}</a> /
{% endfor %}
<a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr> <tr>
<td>Tenant</td> <td>Tenant</td>
<td> <td>

View File

@ -38,6 +38,15 @@
{% endif %} {% endif %}
<a href="{% url 'dcim:region_list' %}">Regions</a> <a href="{% url 'dcim:region_list' %}">Regions</a>
</li> </li>
<li{% if not perms.dcim.view_sitegroup %} class="disabled"{% endif %}>
{% if perms.dcim.add_sitegroup %}
<div class="buttons pull-right">
<a href="{% url 'dcim:sitegroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a>
<a href="{% url 'dcim:sitegroup_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:sitegroup_list' %}">Site Groups</a>
</li>
<li class="divider"></li> <li class="divider"></li>
<li class="dropdown-header">Racks</li> <li class="dropdown-header">Racks</li>
<li{% if not perms.dcim.view_rack %} class="disabled"{% endif %}> <li{% if not perms.dcim.view_rack %} class="disabled"{% endif %}>

View File

@ -1,7 +1,7 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from utilities.filters import ( from utilities.filters import (
@ -52,6 +52,19 @@ class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSe
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (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)',
@ -151,6 +164,19 @@ class VirtualMachineFilterSet(
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='cluster__site__group',
lookup_expr='in',
label='Site group (ID)',
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='cluster__site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__site', field_name='cluster__site',
queryset=Site.objects.all(), queryset=Site.objects.all(),

View File

@ -1,6 +1,6 @@
from django.test import TestCase from django.test import TestCase
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from ipam.models import IPAddress from ipam.models import IPAddress
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from virtualization.choices import * from virtualization.choices import *
@ -96,14 +96,21 @@ class ClusterTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -144,6 +151,13 @@ class ClusterTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}
@ -206,14 +220,21 @@ class VirtualMachineTestCase(TestCase):
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
Region(name='Test Region 3', slug='test-region-3'), Region(name='Test Region 3', slug='test-region-3'),
) )
# Can't use bulk_create for models with MPTT fields
for r in regions: for r in regions:
r.save() r.save()
site_groups = (
SiteGroup(name='Site Group 1', slug='site-group-1'),
SiteGroup(name='Site Group 2', slug='site-group-2'),
SiteGroup(name='Site Group 3', slug='site-group-3'),
)
for site_group in site_groups:
site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0]), Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1]), Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2]), Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -329,6 +350,13 @@ class VirtualMachineTestCase(TestCase):
params = {'region': [regions[0].slug, regions[1].slug]} params = {'region': [regions[0].slug, regions[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site_group(self):
site_groups = SiteGroup.objects.all()[:2]
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_site(self): def test_site(self):
sites = Site.objects.all()[:2] sites = Site.objects.all()[:2]
params = {'site_id': [sites[0].pk, sites[1].pk]} params = {'site_id': [sites[0].pk, sites[1].pk]}