mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Closes #5895: Rename RackGroup to Location
This commit is contained in:
parent
a17018a875
commit
fdb3e3f9a4
@ -19,6 +19,7 @@ In addition to the new `mark_connected` boolean field, the REST API representati
|
|||||||
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
|
* [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
|
||||||
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
|
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
|
||||||
* [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI
|
* [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI
|
||||||
|
* [#5895](https://github.com/netbox-community/netbox/issues/5895) - Rename RackGroup to Location
|
||||||
* [#5901](https://github.com/netbox-community/netbox/issues/5901) - Add `created` and `last_updated` fields to device component models
|
* [#5901](https://github.com/netbox-community/netbox/issues/5901) - Add `created` and `last_updated` fields to device component models
|
||||||
|
|
||||||
### Other Changes
|
### Other Changes
|
||||||
@ -39,5 +40,11 @@ In addition to the new `mark_connected` boolean field, the REST API representati
|
|||||||
* All cable termination models (cabled device components, power feeds, and circuit terminations)
|
* All cable termination models (cabled device components, power feeds, and circuit terminations)
|
||||||
* Added `mark_connected` boolean field to force connection status
|
* Added `mark_connected` boolean field to force connection status
|
||||||
* Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied
|
* Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied
|
||||||
|
* Renamed RackGroup to Location
|
||||||
|
* The `/dcim/rack-groups/` endpoint is now `/dcim/locations/`
|
||||||
|
* dcim.PowerPanel
|
||||||
|
* Renamed `rack_group` field to `location`
|
||||||
|
* dcim.Rack
|
||||||
|
* Renamed `group` field to `location`
|
||||||
* extras.CustomField
|
* extras.CustomField
|
||||||
* Added new custom field type: `multi-select`
|
* Added new custom field type: `multi-select`
|
||||||
|
@ -27,7 +27,7 @@ __all__ = [
|
|||||||
'NestedPowerPanelSerializer',
|
'NestedPowerPanelSerializer',
|
||||||
'NestedPowerPortSerializer',
|
'NestedPowerPortSerializer',
|
||||||
'NestedPowerPortTemplateSerializer',
|
'NestedPowerPortTemplateSerializer',
|
||||||
'NestedRackGroupSerializer',
|
'NestedLocationSerializer',
|
||||||
'NestedRackReservationSerializer',
|
'NestedRackReservationSerializer',
|
||||||
'NestedRackRoleSerializer',
|
'NestedRackRoleSerializer',
|
||||||
'NestedRackSerializer',
|
'NestedRackSerializer',
|
||||||
@ -65,13 +65,13 @@ class NestedSiteSerializer(WritableNestedSerializer):
|
|||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
|
||||||
class NestedRackGroupSerializer(WritableNestedSerializer):
|
class NestedLocationSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
|
||||||
rack_count = serializers.IntegerField(read_only=True)
|
rack_count = serializers.IntegerField(read_only=True)
|
||||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.RackGroup
|
model = models.Location
|
||||||
fields = ['id', 'url', 'name', 'slug', 'rack_count', '_depth']
|
fields = ['id', 'url', 'name', 'slug', 'rack_count', '_depth']
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from dcim.models import (
|
|||||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||||
VirtualChassis,
|
VirtualChassis,
|
||||||
)
|
)
|
||||||
from netbox.api.serializers import CustomFieldModelSerializer
|
from netbox.api.serializers import CustomFieldModelSerializer
|
||||||
@ -121,14 +121,14 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
|||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupSerializer(NestedGroupModelSerializer):
|
class LocationSerializer(NestedGroupModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
parent = NestedRackGroupSerializer(required=False, allow_null=True)
|
parent = NestedLocationSerializer(required=False, allow_null=True)
|
||||||
rack_count = serializers.IntegerField(read_only=True)
|
rack_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackGroup
|
model = Location
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
|
'id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
|
||||||
'rack_count', '_depth',
|
'rack_count', '_depth',
|
||||||
@ -150,7 +150,7 @@ class RackRoleSerializer(OrganizationalModelSerializer):
|
|||||||
class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
location = NestedLocationSerializer(required=False, allow_null=True, default=None)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=RackStatusChoices, required=False)
|
status = ChoiceField(choices=RackStatusChoices, required=False)
|
||||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||||
@ -163,21 +163,22 @@ class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
|
'id', 'url', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status', 'role',
|
||||||
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
|
'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||||
|
'powerfeed_count',
|
||||||
]
|
]
|
||||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
# Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This
|
||||||
# prevents facility_id from being interpreted as a required field.
|
# prevents facility_id from being interpreted as a required field.
|
||||||
validators = [
|
validators = [
|
||||||
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'name'))
|
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'name'))
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
|
||||||
# Validate uniqueness of (group, facility_id) since we omitted the automatically-created validator from Meta.
|
# Validate uniqueness of (location, facility_id) since we omitted the automatically-created validator from Meta.
|
||||||
if data.get('facility_id', None):
|
if data.get('facility_id', None):
|
||||||
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'facility_id'))
|
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'facility_id'))
|
||||||
validator(data, self)
|
validator(data, self)
|
||||||
|
|
||||||
# Enforce model validation
|
# Enforce model validation
|
||||||
@ -856,7 +857,7 @@ class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerialize
|
|||||||
class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
rack_group = NestedRackGroupSerializer(
|
location = NestedLocationSerializer(
|
||||||
required=False,
|
required=False,
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
default=None
|
default=None
|
||||||
@ -865,7 +866,7 @@ class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = ['id', 'url', 'site', 'rack_group', 'name', 'tags', 'custom_fields', 'powerfeed_count']
|
fields = ['id', 'url', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count']
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedSerializer(
|
class PowerFeedSerializer(
|
||||||
|
@ -10,7 +10,7 @@ router.register('regions', views.RegionViewSet)
|
|||||||
router.register('sites', views.SiteViewSet)
|
router.register('sites', views.SiteViewSet)
|
||||||
|
|
||||||
# Racks
|
# Racks
|
||||||
router.register('rack-groups', views.RackGroupViewSet)
|
router.register('locations', views.LocationViewSet)
|
||||||
router.register('rack-roles', views.RackRoleViewSet)
|
router.register('rack-roles', views.RackRoleViewSet)
|
||||||
router.register('racks', views.RackViewSet)
|
router.register('racks', views.RackViewSet)
|
||||||
router.register('rack-reservations', views.RackReservationViewSet)
|
router.register('rack-reservations', views.RackReservationViewSet)
|
||||||
|
@ -20,7 +20,7 @@ from dcim.models import (
|
|||||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||||
VirtualChassis,
|
VirtualChassis,
|
||||||
)
|
)
|
||||||
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
|
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
|
||||||
@ -134,16 +134,16 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
# Rack groups
|
# Rack groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupViewSet(CustomFieldModelViewSet):
|
class LocationViewSet(CustomFieldModelViewSet):
|
||||||
queryset = RackGroup.objects.add_related_count(
|
queryset = Location.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
Location.objects.all(),
|
||||||
Rack,
|
Rack,
|
||||||
'group',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site')
|
).prefetch_related('site')
|
||||||
serializer_class = serializers.RackGroupSerializer
|
serializer_class = serializers.LocationSerializer
|
||||||
filterset_class = filters.RackGroupFilterSet
|
filterset_class = filters.LocationFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -164,7 +164,7 @@ class RackRoleViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class RackViewSet(CustomFieldModelViewSet):
|
class RackViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Rack.objects.prefetch_related(
|
queryset = Rack.objects.prefetch_related(
|
||||||
'site', 'group__site', 'role', 'tenant', 'tags'
|
'site', 'location__site', 'role', 'tenant', 'tags'
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=count_related(Device, 'rack'),
|
device_count=count_related(Device, 'rack'),
|
||||||
powerfeed_count=count_related(PowerFeed, 'rack')
|
powerfeed_count=count_related(PowerFeed, 'rack')
|
||||||
@ -619,7 +619,7 @@ class VirtualChassisViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class PowerPanelViewSet(ModelViewSet):
|
class PowerPanelViewSet(ModelViewSet):
|
||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'location'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,7 @@ from .models import (
|
|||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
|
||||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||||
VirtualChassis,
|
VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ __all__ = (
|
|||||||
'InterfaceFilterSet',
|
'InterfaceFilterSet',
|
||||||
'InterfaceTemplateFilterSet',
|
'InterfaceTemplateFilterSet',
|
||||||
'InventoryItemFilterSet',
|
'InventoryItemFilterSet',
|
||||||
|
'LocationFilterSet',
|
||||||
'ManufacturerFilterSet',
|
'ManufacturerFilterSet',
|
||||||
'PathEndpointFilterSet',
|
'PathEndpointFilterSet',
|
||||||
'PlatformFilterSet',
|
'PlatformFilterSet',
|
||||||
@ -51,7 +52,6 @@ __all__ = (
|
|||||||
'PowerPortFilterSet',
|
'PowerPortFilterSet',
|
||||||
'PowerPortTemplateFilterSet',
|
'PowerPortTemplateFilterSet',
|
||||||
'RackFilterSet',
|
'RackFilterSet',
|
||||||
'RackGroupFilterSet',
|
|
||||||
'RackReservationFilterSet',
|
'RackReservationFilterSet',
|
||||||
'RackRoleFilterSet',
|
'RackRoleFilterSet',
|
||||||
'RearPortFilterSet',
|
'RearPortFilterSet',
|
||||||
@ -131,7 +131,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
||||||
region_id = TreeNodeMultipleChoiceFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
field_name='site__region',
|
field_name='site__region',
|
||||||
@ -156,18 +156,18 @@ class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
label='Rack group (ID)',
|
label='Rack group (ID)',
|
||||||
)
|
)
|
||||||
parent = django_filters.ModelMultipleChoiceFilter(
|
parent = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='parent__slug',
|
field_name='parent__slug',
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Rack group (slug)',
|
label='Rack group (slug)',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackGroup
|
model = Location
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
@ -206,18 +206,18 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
group_id = TreeNodeMultipleChoiceFilter(
|
location_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='group',
|
field_name='location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label='Rack group (ID)',
|
label='Location (ID)',
|
||||||
)
|
)
|
||||||
group = TreeNodeMultipleChoiceFilter(
|
location = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='group',
|
field_name='location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Rack group (slug)',
|
label='Location (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=RackStatusChoices,
|
choices=RackStatusChoices,
|
||||||
@ -283,18 +283,18 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
group_id = TreeNodeMultipleChoiceFilter(
|
location_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='rack__group',
|
field_name='rack__location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label='Rack group (ID)',
|
label='Location (ID)',
|
||||||
)
|
)
|
||||||
group = TreeNodeMultipleChoiceFilter(
|
location = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='rack__group',
|
field_name='rack__location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Rack group (slug)',
|
label='Location (slug)',
|
||||||
)
|
)
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
@ -575,11 +575,11 @@ class DeviceFilterSet(
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site name (slug)',
|
label='Site name (slug)',
|
||||||
)
|
)
|
||||||
rack_group_id = TreeNodeMultipleChoiceFilter(
|
location_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='rack__group',
|
field_name='rack__location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label='Rack group (ID)',
|
label='Location (ID)',
|
||||||
)
|
)
|
||||||
rack_id = django_filters.ModelMultipleChoiceFilter(
|
rack_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='rack',
|
field_name='rack',
|
||||||
@ -1236,9 +1236,9 @@ class PowerPanelFilterSet(BaseFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site name (slug)',
|
label='Site name (slug)',
|
||||||
)
|
)
|
||||||
rack_group_id = TreeNodeMultipleChoiceFilter(
|
location_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='rack_group',
|
field_name='location',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label='Rack group (ID)',
|
label='Rack group (ID)',
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,7 @@ from .models import (
|
|||||||
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||||
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
||||||
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
|
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
|
||||||
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEVICE_BY_PK_RE = r'{\d+\}'
|
DEVICE_BY_PK_RE = r'{\d+\}'
|
||||||
@ -358,10 +358,10 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rack groups
|
# Locations
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupForm(BootstrapMixin, CustomFieldModelForm):
|
class LocationForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -376,7 +376,7 @@ class RackGroupForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -385,20 +385,20 @@ class RackGroupForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackGroup
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'region', 'site', 'parent', 'name', 'slug', 'description',
|
'region', 'site', 'parent', 'name', 'slug', 'description',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackGroupCSVForm(CustomFieldModelCSVForm):
|
class LocationCSVForm(CustomFieldModelCSVForm):
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Assigned site'
|
help_text='Assigned site'
|
||||||
)
|
)
|
||||||
parent = CSVModelChoiceField(
|
parent = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Parent rack group',
|
help_text='Parent rack group',
|
||||||
@ -408,11 +408,11 @@ class RackGroupCSVForm(CustomFieldModelCSVForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackGroup
|
model = Location
|
||||||
fields = RackGroup.csv_headers
|
fields = Location.csv_headers
|
||||||
|
|
||||||
|
|
||||||
class RackGroupFilterForm(BootstrapMixin, forms.Form):
|
class LocationFilterForm(BootstrapMixin, forms.Form):
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -427,7 +427,7 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form):
|
|||||||
label=_('Site')
|
label=_('Site')
|
||||||
)
|
)
|
||||||
parent = DynamicModelMultipleChoiceField(
|
parent = DynamicModelMultipleChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'region_id': '$region_id',
|
'region_id': '$region_id',
|
||||||
@ -480,8 +480,8 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -500,7 +500,7 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'region', 'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
'region', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
||||||
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||||
'comments', 'tags',
|
'comments', 'tags',
|
||||||
]
|
]
|
||||||
@ -523,8 +523,8 @@ class RackCSVForm(CustomFieldModelCSVForm):
|
|||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
group = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
@ -569,9 +569,9 @@ class RackCSVForm(CustomFieldModelCSVForm):
|
|||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
# Limit group queryset by assigned site
|
# Limit location queryset by assigned site
|
||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['group'].queryset = self.fields['group'].queryset.filter(**params)
|
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||||
|
|
||||||
|
|
||||||
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
@ -593,8 +593,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -662,13 +662,13 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = [
|
nullable_fields = [
|
||||||
'group', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Rack
|
model = Rack
|
||||||
field_order = ['q', 'region_id', 'site_id', 'group_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id']
|
field_order = ['q', 'region_id', 'site_id', 'location_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Search')
|
label=_('Search')
|
||||||
@ -686,14 +686,14 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
},
|
},
|
||||||
label=_('Site')
|
label=_('Site')
|
||||||
)
|
)
|
||||||
group_id = DynamicModelMultipleChoiceField(
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None',
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id'
|
'site_id': '$site_id'
|
||||||
},
|
},
|
||||||
label=_('Rack group')
|
label=_('Location')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=RackStatusChoices,
|
choices=RackStatusChoices,
|
||||||
@ -724,7 +724,9 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackElevationFilterForm(RackFilterForm):
|
class RackElevationFilterForm(RackFilterForm):
|
||||||
field_order = ['q', 'region_id', 'site_id', 'group_id', 'id', 'status', 'role_id', 'tenant_group_id', 'tenant_id']
|
field_order = [
|
||||||
|
'q', 'region_id', 'site_id', 'location_id', 'id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
|
||||||
|
]
|
||||||
id = DynamicModelMultipleChoiceField(
|
id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
label=_('Rack'),
|
label=_('Rack'),
|
||||||
@ -732,7 +734,7 @@ class RackElevationFilterForm(RackFilterForm):
|
|||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id',
|
'site_id': '$site_id',
|
||||||
'group_id_id': '$group_id_id',
|
'location_id': '$location_id',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -756,8 +758,8 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack_group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -768,7 +770,7 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site',
|
'site_id': '$site',
|
||||||
'group_id': '$rack_group',
|
'location_id': 'location',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
units = NumericArrayField(
|
units = NumericArrayField(
|
||||||
@ -789,10 +791,10 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = [
|
fields = [
|
||||||
'region', 'site', 'rack_group', 'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'tags',
|
'region', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'tags',
|
||||||
]
|
]
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Reservation', ('region', 'site', 'rack_group', 'rack', 'units', 'user', 'description', 'tags')),
|
('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
|
||||||
('Tenancy', ('tenant_group', 'tenant')),
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -803,11 +805,11 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Parent site'
|
help_text='Parent site'
|
||||||
)
|
)
|
||||||
rack_group = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text="Rack's group (if any)"
|
help_text="Rack's location (if any)"
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
@ -828,21 +830,21 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ('site', 'rack_group', 'rack', 'units', 'tenant', 'description')
|
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description')
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
# Limit rack_group queryset by assigned site
|
# Limit location queryset by assigned site
|
||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
|
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||||
|
|
||||||
# Limit rack queryset by assigned site and group
|
# Limit rack queryset by assigned site and group
|
||||||
params = {
|
params = {
|
||||||
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
||||||
f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
|
f"location__{self.fields['location'].to_field_name}": data.get('location'),
|
||||||
}
|
}
|
||||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||||
|
|
||||||
@ -874,7 +876,7 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField
|
|||||||
|
|
||||||
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
field_order = ['q', 'region_id', 'site_id', 'group_id', 'user_id', 'tenant_group_id', 'tenant_id']
|
field_order = ['q', 'region_id', 'site_id', 'location_id', 'user_id', 'tenant_group_id', 'tenant_id']
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Search')
|
label=_('Search')
|
||||||
@ -892,10 +894,10 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
|
|||||||
},
|
},
|
||||||
label=_('Region')
|
label=_('Region')
|
||||||
)
|
)
|
||||||
group_id = DynamicModelMultipleChoiceField(
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RackGroup.objects.prefetch_related('site'),
|
queryset=Location.objects.prefetch_related('site'),
|
||||||
required=False,
|
required=False,
|
||||||
label='Rack group',
|
label='Location',
|
||||||
null_option='None'
|
null_option='None'
|
||||||
)
|
)
|
||||||
user_id = DynamicModelMultipleChoiceField(
|
user_id = DynamicModelMultipleChoiceField(
|
||||||
@ -1782,8 +1784,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack_group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
query_params={
|
query_params={
|
||||||
@ -1799,7 +1801,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
display_field='display_name',
|
display_field='display_name',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site',
|
'site_id': '$site',
|
||||||
'group_id': '$rack_group',
|
'location_id': 'location',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
position = forms.IntegerField(
|
position = forms.IntegerField(
|
||||||
@ -2003,11 +2005,11 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Assigned site'
|
help_text='Assigned site'
|
||||||
)
|
)
|
||||||
rack_group = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text="Rack's group (if any)"
|
help_text="Rack's location (if any)"
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
@ -2024,7 +2026,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
class Meta(BaseDeviceCSVForm.Meta):
|
class Meta(BaseDeviceCSVForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||||
'site', 'rack_group', 'rack', 'position', 'face', 'cluster', 'comments',
|
'site', 'location', 'rack', 'position', 'face', 'cluster', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -2032,14 +2034,14 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
# Limit rack_group queryset by assigned site
|
# Limit location queryset by assigned site
|
||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
|
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||||
|
|
||||||
# Limit rack queryset by assigned site and group
|
# Limit rack queryset by assigned site and group
|
||||||
params = {
|
params = {
|
||||||
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
||||||
f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
|
f"location__{self.fields['location'].to_field_name}": data.get('location'),
|
||||||
}
|
}
|
||||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||||
|
|
||||||
@ -2135,7 +2137,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
|
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
|
||||||
model = Device
|
model = Device
|
||||||
field_order = [
|
field_order = [
|
||||||
'q', 'region_id', 'site_id', 'rack_group_id', 'rack_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
|
'q', 'region_id', 'site_id', 'location_id', 'rack_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id',
|
||||||
'manufacturer_id', 'device_type_id', 'mac_address', 'has_primary_ip',
|
'manufacturer_id', 'device_type_id', 'mac_address', 'has_primary_ip',
|
||||||
]
|
]
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
@ -2153,10 +2155,10 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
|||||||
'region_id': '$region_id'
|
'region_id': '$region_id'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack_group_id = DynamicModelMultipleChoiceField(
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Rack group'),
|
label=_('Location'),
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id'
|
'site_id': '$site_id'
|
||||||
}
|
}
|
||||||
@ -2167,7 +2169,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
|||||||
null_option='None',
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id',
|
'site_id': '$site_id',
|
||||||
'group_id': '$rack_group_id',
|
'location_id': '$location_id',
|
||||||
},
|
},
|
||||||
label=_('Rack')
|
label=_('Rack')
|
||||||
)
|
)
|
||||||
@ -3834,9 +3836,9 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
'region_id': '$termination_b_region'
|
'region_id': '$termination_b_region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
termination_b_rackgroup = DynamicModelChoiceField(
|
termination_b_location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
label='Rack Group',
|
label='Location',
|
||||||
required=False,
|
required=False,
|
||||||
display_field='cid',
|
display_field='cid',
|
||||||
query_params={
|
query_params={
|
||||||
@ -3849,7 +3851,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$termination_b_site',
|
'site_id': '$termination_b_site',
|
||||||
'rack_group_id': '$termination_b_rackgroup',
|
'location_id': '$termination_b_location',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_id = DynamicModelChoiceField(
|
||||||
@ -3868,7 +3870,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = [
|
fields = [
|
||||||
'termination_b_rackgroup', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'label',
|
'termination_b_location', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'label',
|
||||||
'color', 'length', 'length_unit', 'tags',
|
'color', 'length', 'length_unit', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4450,8 +4452,8 @@ class PowerPanelForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack_group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -4465,10 +4467,10 @@ class PowerPanelForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = [
|
fields = [
|
||||||
'region', 'site', 'rack_group', 'name', 'tags',
|
'region', 'site', 'location', 'name', 'tags',
|
||||||
]
|
]
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Power Panel', ('region', 'site', 'rack_group', 'name', 'tags')),
|
('Power Panel', ('region', 'site', 'location', 'name', 'tags')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -4478,8 +4480,8 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Name of parent site'
|
help_text='Name of parent site'
|
||||||
)
|
)
|
||||||
rack_group = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name'
|
to_field_name='name'
|
||||||
)
|
)
|
||||||
@ -4495,7 +4497,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
|
|||||||
|
|
||||||
# Limit group queryset by assigned site
|
# Limit group queryset by assigned site
|
||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
|
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
@ -4517,8 +4519,8 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rack_group = DynamicModelChoiceField(
|
location = DynamicModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site'
|
'site_id': '$site'
|
||||||
@ -4526,7 +4528,7 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['rack_group']
|
nullable_fields = ['location']
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
@ -4548,14 +4550,14 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
},
|
},
|
||||||
label=_('Site')
|
label=_('Site')
|
||||||
)
|
)
|
||||||
rack_group_id = DynamicModelMultipleChoiceField(
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
null_option='None',
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id'
|
'site_id': '$site_id'
|
||||||
},
|
},
|
||||||
label=_('Rack group')
|
label=_('Location')
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@ -4632,11 +4634,11 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Upstream power panel'
|
help_text='Upstream power panel'
|
||||||
)
|
)
|
||||||
rack_group = CSVModelChoiceField(
|
location = CSVModelChoiceField(
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text="Rack's group (if any)"
|
help_text="Rack's location (if any)"
|
||||||
)
|
)
|
||||||
rack = CSVModelChoiceField(
|
rack = CSVModelChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
@ -4678,14 +4680,14 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
|
|||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['power_panel'].queryset = self.fields['power_panel'].queryset.filter(**params)
|
self.fields['power_panel'].queryset = self.fields['power_panel'].queryset.filter(**params)
|
||||||
|
|
||||||
# Limit rack_group queryset by site
|
# Limit location queryset by site
|
||||||
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
|
||||||
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)
|
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||||
|
|
||||||
# Limit rack queryset by site and group
|
# Limit rack queryset by site and group
|
||||||
params = {
|
params = {
|
||||||
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
f"site__{self.fields['site'].to_field_name}": data.get('site'),
|
||||||
f"group__{self.fields['rack_group'].to_field_name}": data.get('rack_group'),
|
f"location__{self.fields['location'].to_field_name}": data.get('location'),
|
||||||
}
|
}
|
||||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||||
|
|
||||||
@ -4748,7 +4750,7 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = [
|
nullable_fields = [
|
||||||
'rackgroup', 'comments',
|
'location', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
39
netbox/dcim/migrations/0126_rename_rackgroup_location.py
Normal file
39
netbox/dcim/migrations/0126_rename_rackgroup_location.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0125_console_port_speed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='RackGroup',
|
||||||
|
new_name='Location',
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='rack',
|
||||||
|
options={'ordering': ('site', 'location', '_name', 'pk')},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='location',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='dcim.site'),
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='powerpanel',
|
||||||
|
old_name='rack_group',
|
||||||
|
new_name='location',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='rack',
|
||||||
|
old_name='group',
|
||||||
|
new_name='location',
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='rack',
|
||||||
|
unique_together={('location', 'facility_id'), ('location', 'name')},
|
||||||
|
),
|
||||||
|
]
|
@ -34,7 +34,7 @@ __all__ = (
|
|||||||
'PowerPort',
|
'PowerPort',
|
||||||
'PowerPortTemplate',
|
'PowerPortTemplate',
|
||||||
'Rack',
|
'Rack',
|
||||||
'RackGroup',
|
'Location',
|
||||||
'RackReservation',
|
'RackReservation',
|
||||||
'RackRole',
|
'RackRole',
|
||||||
'RearPort',
|
'RearPort',
|
||||||
|
@ -600,7 +600,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||||
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
|
'site', 'location', 'rack_name', 'position', 'face', 'comments',
|
||||||
]
|
]
|
||||||
clone_fields = [
|
clone_fields = [
|
||||||
'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster',
|
'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster',
|
||||||
@ -799,7 +799,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
self.asset_tag,
|
self.asset_tag,
|
||||||
self.get_status_display(),
|
self.get_status_display(),
|
||||||
self.site.name,
|
self.site.name,
|
||||||
self.rack.group.name if self.rack and self.rack.group else None,
|
self.rack.location.name if self.rack and self.rack.location else None,
|
||||||
self.rack.name if self.rack else None,
|
self.rack.name if self.rack else None,
|
||||||
self.position,
|
self.position,
|
||||||
self.get_face_display(),
|
self.get_face_display(),
|
||||||
|
@ -32,8 +32,8 @@ class PowerPanel(PrimaryModel):
|
|||||||
to='Site',
|
to='Site',
|
||||||
on_delete=models.PROTECT
|
on_delete=models.PROTECT
|
||||||
)
|
)
|
||||||
rack_group = models.ForeignKey(
|
location = models.ForeignKey(
|
||||||
to='RackGroup',
|
to='dcim.Location',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
@ -45,7 +45,7 @@ class PowerPanel(PrimaryModel):
|
|||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
csv_headers = ['site', 'rack_group', 'name']
|
csv_headers = ['site', 'location', 'name']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['site', 'name']
|
ordering = ['site', 'name']
|
||||||
@ -60,17 +60,17 @@ class PowerPanel(PrimaryModel):
|
|||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
self.site.name,
|
self.site.name,
|
||||||
self.rack_group.name if self.rack_group else None,
|
self.location.name if self.location else None,
|
||||||
self.name,
|
self.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# RackGroup must belong to assigned Site
|
# Location must belong to assigned Site
|
||||||
if self.rack_group and self.rack_group.site != self.site:
|
if self.location and self.location.site != self.site:
|
||||||
raise ValidationError("Rack group {} ({}) is in a different site than {}".format(
|
raise ValidationError("Rack group {} ({}) is in a different site than {}".format(
|
||||||
self.rack_group, self.rack_group.site, self.site
|
self.location, self.location.site, self.site
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
|
|||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
||||||
'voltage', 'amperage', 'max_utilization', 'comments',
|
'voltage', 'amperage', 'max_utilization', 'comments',
|
||||||
]
|
]
|
||||||
clone_fields = [
|
clone_fields = [
|
||||||
@ -160,7 +160,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
|
|||||||
return (
|
return (
|
||||||
self.power_panel.site.name,
|
self.power_panel.site.name,
|
||||||
self.power_panel.name,
|
self.power_panel.name,
|
||||||
self.rack.group.name if self.rack and self.rack.group else None,
|
self.rack.location.name if self.rack and self.rack.location else None,
|
||||||
self.rack.name if self.rack else None,
|
self.rack.name if self.rack else None,
|
||||||
self.name,
|
self.name,
|
||||||
self.get_status_display(),
|
self.get_status_display(),
|
||||||
|
@ -16,21 +16,20 @@ from taggit.managers import TaggableManager
|
|||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.elevations import RackElevationSVG
|
from dcim.elevations import RackElevationSVG
|
||||||
from extras.models import ObjectChange, TaggedItem
|
from extras.models import TaggedItem
|
||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
|
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.mptt import TreeManager
|
from utilities.utils import array_to_string
|
||||||
from utilities.utils import array_to_string, serialize_object
|
|
||||||
from .device_components import PowerOutlet, PowerPort
|
from .device_components import PowerOutlet, PowerPort
|
||||||
from .devices import Device
|
from .devices import Device
|
||||||
from .power import PowerFeed
|
from .power import PowerFeed
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Rack',
|
'Rack',
|
||||||
'RackGroup',
|
'Location',
|
||||||
'RackReservation',
|
'RackReservation',
|
||||||
'RackRole',
|
'RackRole',
|
||||||
)
|
)
|
||||||
@ -41,11 +40,10 @@ __all__ = (
|
|||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
class RackGroup(NestedGroupModel):
|
class Location(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
|
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
||||||
example, if a Site spans a corporate campus, a RackGroup might be defined to represent each building within that
|
site, or a room within a building, for example.
|
||||||
campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor.
|
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=100
|
max_length=100
|
||||||
@ -56,7 +54,7 @@ class RackGroup(NestedGroupModel):
|
|||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
to='dcim.Site',
|
to='dcim.Site',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='rack_groups'
|
related_name='locations'
|
||||||
)
|
)
|
||||||
parent = TreeForeignKey(
|
parent = TreeForeignKey(
|
||||||
to='self',
|
to='self',
|
||||||
@ -81,7 +79,7 @@ class RackGroup(NestedGroupModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
return "{}?location_id={}".format(reverse('dcim:rack_list'), self.pk)
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
@ -95,9 +93,9 @@ class RackGroup(NestedGroupModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Parent RackGroup (if any) must belong to the same Site
|
# Parent Location (if any) must belong to the same Site
|
||||||
if self.parent and self.parent.site != self.site:
|
if self.parent and self.parent.site != self.site:
|
||||||
raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})")
|
raise ValidationError(f"Parent location ({self.parent}) must belong to the same site ({self.site})")
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
||||||
@ -147,7 +145,7 @@ class RackRole(OrganizationalModel):
|
|||||||
class Rack(PrimaryModel):
|
class Rack(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
|
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
|
||||||
Each Rack is assigned to a Site and (optionally) a RackGroup.
|
Each Rack is assigned to a Site and (optionally) a Location.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=100
|
max_length=100
|
||||||
@ -169,13 +167,12 @@ class Rack(PrimaryModel):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='racks'
|
related_name='racks'
|
||||||
)
|
)
|
||||||
group = models.ForeignKey(
|
location = models.ForeignKey(
|
||||||
to='dcim.RackGroup',
|
to='dcim.Location',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='racks',
|
related_name='racks',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True
|
||||||
help_text='Assigned group'
|
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
@ -259,20 +256,20 @@ class Rack(PrimaryModel):
|
|||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'site', 'group', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
|
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
|
||||||
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||||
]
|
]
|
||||||
clone_fields = [
|
clone_fields = [
|
||||||
'site', 'group', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
|
'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
|
||||||
'outer_depth', 'outer_unit',
|
'outer_depth', 'outer_unit',
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('site', 'group', '_name', 'pk') # (site, group, name) may be non-unique
|
ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
|
||||||
unique_together = (
|
unique_together = (
|
||||||
# Name and facility_id must be unique *only* within a RackGroup
|
# Name and facility_id must be unique *only* within a Location
|
||||||
('group', 'name'),
|
('location', 'name'),
|
||||||
('group', 'facility_id'),
|
('location', 'facility_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -284,9 +281,9 @@ class Rack(PrimaryModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate group/site assignment
|
# Validate location/site assignment
|
||||||
if self.site and self.group and self.group.site != self.site:
|
if self.site and self.location and self.location.site != self.site:
|
||||||
raise ValidationError(f"Assigned rack group must belong to parent site ({self.site}).")
|
raise ValidationError(f"Assigned location must belong to parent site ({self.site}).")
|
||||||
|
|
||||||
# Validate outer dimensions and unit
|
# Validate outer dimensions and unit
|
||||||
if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
|
if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
|
||||||
@ -309,17 +306,17 @@ class Rack(PrimaryModel):
|
|||||||
min_height
|
min_height
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
# Validate that Rack was assigned a group of its same site, if applicable
|
# Validate that Rack was assigned a Location of its same site, if applicable
|
||||||
if self.group:
|
if self.location:
|
||||||
if self.group.site != self.site:
|
if self.location.site != self.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'group': "Rack group must be from the same site, {}.".format(self.site)
|
'location': f"Location must be from the same site, {self.site}."
|
||||||
})
|
})
|
||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
self.site.name,
|
self.site.name,
|
||||||
self.group.name if self.group else None,
|
self.location.name if self.location else None,
|
||||||
self.name,
|
self.name,
|
||||||
self.facility_id,
|
self.facility_id,
|
||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
@ -565,7 +562,7 @@ class RackReservation(PrimaryModel):
|
|||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
csv_headers = ['site', 'rack_group', 'rack', 'units', 'tenant', 'user', 'description']
|
csv_headers = ['site', 'location', 'rack', 'units', 'tenant', 'user', 'description']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['created', 'pk']
|
ordering = ['created', 'pk']
|
||||||
@ -606,7 +603,7 @@ class RackReservation(PrimaryModel):
|
|||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
self.rack.site.name,
|
self.rack.site.name,
|
||||||
self.rack.group if self.rack.group else None,
|
self.rack.location if self.rack.location else None,
|
||||||
self.rack.name,
|
self.rack.name,
|
||||||
','.join([str(u) for u in self.units]),
|
','.join([str(u) for u in self.units]),
|
||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
|
@ -7,7 +7,7 @@ from django.db import transaction
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from .choices import CableStatusChoices
|
from .choices import CableStatusChoices
|
||||||
from .models import Cable, CablePath, Device, PathEndpoint, PowerPanel, Rack, RackGroup, VirtualChassis
|
from .models import Cable, CablePath, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
|
||||||
|
|
||||||
|
|
||||||
def create_cablepath(node):
|
def create_cablepath(node):
|
||||||
@ -40,20 +40,20 @@ def rebuild_paths(obj):
|
|||||||
# Site/rack/device assignment
|
# Site/rack/device assignment
|
||||||
#
|
#
|
||||||
|
|
||||||
@receiver(post_save, sender=RackGroup)
|
@receiver(post_save, sender=Location)
|
||||||
def handle_rackgroup_site_change(instance, created, **kwargs):
|
def handle_location_site_change(instance, created, **kwargs):
|
||||||
"""
|
"""
|
||||||
Update child RackGroups and Racks if Site assignment has changed. We intentionally recurse through each child
|
Update child Locations and Racks if Site assignment has changed. We intentionally recurse through each child
|
||||||
object instead of calling update() on the QuerySet to ensure the proper change records get created for each.
|
object instead of calling update() on the QuerySet to ensure the proper change records get created for each.
|
||||||
"""
|
"""
|
||||||
if not created:
|
if not created:
|
||||||
for rackgroup in instance.get_children():
|
for location in instance.get_children():
|
||||||
rackgroup.site = instance.site
|
location.site = instance.site
|
||||||
rackgroup.save()
|
location.save()
|
||||||
for rack in Rack.objects.filter(group=instance).exclude(site=instance.site):
|
for rack in Rack.objects.filter(location=instance).exclude(site=instance.site):
|
||||||
rack.site = instance.site
|
rack.site = instance.site
|
||||||
rack.save()
|
rack.save()
|
||||||
for powerpanel in PowerPanel.objects.filter(rack_group=instance).exclude(site=instance.site):
|
for powerpanel in PowerPanel.objects.filter(location=instance).exclude(site=instance.site):
|
||||||
powerpanel.site = instance.site
|
powerpanel.site = instance.site
|
||||||
powerpanel.save()
|
powerpanel.save()
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ class PowerPanelTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count', 'tags')
|
fields = ('pk', 'name', 'site', 'location', 'powerfeed_count', 'tags')
|
||||||
default_columns = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
|
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
|
|
||||||
from dcim.models import Rack, RackGroup, RackReservation, RackRole
|
from dcim.models import Rack, Location, RackReservation, RackRole
|
||||||
from tenancy.tables import COL_TENANT
|
from tenancy.tables import COL_TENANT
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, TagColumn,
|
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, TagColumn,
|
||||||
ToggleColumn,
|
ToggleColumn,
|
||||||
)
|
)
|
||||||
from .template_code import MPTT_LINK, RACKGROUP_ELEVATIONS, UTILIZATION_GRAPH
|
from .template_code import MPTT_LINK, LOCATION_ELEVATIONS, UTILIZATION_GRAPH
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'RackTable',
|
'RackTable',
|
||||||
'RackDetailTable',
|
'RackDetailTable',
|
||||||
'RackGroupTable',
|
'LocationTable',
|
||||||
'RackReservationTable',
|
'RackReservationTable',
|
||||||
'RackRoleTable',
|
'RackRoleTable',
|
||||||
)
|
)
|
||||||
@ -22,7 +22,7 @@ __all__ = (
|
|||||||
# Rack groups
|
# Rack groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupTable(BaseTable):
|
class LocationTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code=MPTT_LINK,
|
template_code=MPTT_LINK,
|
||||||
@ -36,12 +36,12 @@ class RackGroupTable(BaseTable):
|
|||||||
verbose_name='Racks'
|
verbose_name='Racks'
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
model=RackGroup,
|
model=Location,
|
||||||
prepend_template=RACKGROUP_ELEVATIONS
|
prepend_template=LOCATION_ELEVATIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackGroup
|
model = Location
|
||||||
fields = ('pk', 'name', 'site', 'rack_count', 'description', 'slug', 'actions')
|
fields = ('pk', 'name', 'site', 'rack_count', 'description', 'slug', 'actions')
|
||||||
default_columns = ('pk', 'name', 'site', 'rack_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site', 'rack_count', 'description', 'actions')
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ POWERFEED_CABLETERMINATION = """
|
|||||||
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RACKGROUP_ELEVATIONS = """
|
LOCATION_ELEVATIONS = """
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&group_id={{ record.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&location_id={{ record.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
||||||
<i class="mdi mdi-server"></i>
|
<i class="mdi mdi-server"></i>
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
|
@ -8,7 +8,7 @@ from dcim.models import (
|
|||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
|
||||||
InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
|
InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
|
||||||
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
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
|
||||||
@ -135,8 +135,8 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RackGroupTest(APIViewTestCases.APIViewTestCase):
|
class LocationTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = RackGroup
|
model = Location
|
||||||
brief_fields = ['_depth', 'id', 'name', 'rack_count', 'slug', 'url']
|
brief_fields = ['_depth', 'id', 'name', 'rack_count', 'slug', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
@ -151,33 +151,33 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
parent_rack_groups = (
|
parent_locations = (
|
||||||
RackGroup.objects.create(site=sites[0], name='Parent Rack Group 1', slug='parent-rack-group-1'),
|
Location.objects.create(site=sites[0], name='Parent Location 1', slug='parent-location-1'),
|
||||||
RackGroup.objects.create(site=sites[1], name='Parent Rack Group 2', slug='parent-rack-group-2'),
|
Location.objects.create(site=sites[1], name='Parent Location 2', slug='parent-location-2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
RackGroup.objects.create(site=sites[0], name='Rack Group 1', slug='rack-group-1', parent=parent_rack_groups[0])
|
Location.objects.create(site=sites[0], name='Location 1', slug='location-1', parent=parent_locations[0])
|
||||||
RackGroup.objects.create(site=sites[0], name='Rack Group 2', slug='rack-group-2', parent=parent_rack_groups[0])
|
Location.objects.create(site=sites[0], name='Location 2', slug='location-2', parent=parent_locations[0])
|
||||||
RackGroup.objects.create(site=sites[0], name='Rack Group 3', slug='rack-group-3', parent=parent_rack_groups[0])
|
Location.objects.create(site=sites[0], name='Location 3', slug='location-3', parent=parent_locations[0])
|
||||||
|
|
||||||
cls.create_data = [
|
cls.create_data = [
|
||||||
{
|
{
|
||||||
'name': 'Test Rack Group 4',
|
'name': 'Test Location 4',
|
||||||
'slug': 'test-rack-group-4',
|
'slug': 'test-location-4',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'parent': parent_rack_groups[1].pk,
|
'parent': parent_locations[1].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Rack Group 5',
|
'name': 'Test Location 5',
|
||||||
'slug': 'test-rack-group-5',
|
'slug': 'test-location-5',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'parent': parent_rack_groups[1].pk,
|
'parent': parent_locations[1].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Rack Group 6',
|
'name': 'Test Location 6',
|
||||||
'slug': 'test-rack-group-6',
|
'slug': 'test-location-6',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'parent': parent_rack_groups[1].pk,
|
'parent': parent_locations[1].pk,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -233,9 +233,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup.objects.create(site=sites[0], name='Rack Group 1', slug='rack-group-1'),
|
Location.objects.create(site=sites[0], name='Location 1', slug='location-1'),
|
||||||
RackGroup.objects.create(site=sites[1], name='Rack Group 2', slug='rack-group-2'),
|
Location.objects.create(site=sites[1], name='Location 2', slug='location-2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
rack_roles = (
|
rack_roles = (
|
||||||
@ -245,9 +245,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|||||||
RackRole.objects.bulk_create(rack_roles)
|
RackRole.objects.bulk_create(rack_roles)
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 1'),
|
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 1'),
|
||||||
Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 2'),
|
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 2'),
|
||||||
Rack(site=sites[0], group=rack_groups[0], role=rack_roles[0], name='Rack 3'),
|
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 3'),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
@ -255,19 +255,19 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Test Rack 4',
|
'name': 'Test Rack 4',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'group': rack_groups[1].pk,
|
'location': locations[1].pk,
|
||||||
'role': rack_roles[1].pk,
|
'role': rack_roles[1].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Rack 5',
|
'name': 'Test Rack 5',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'group': rack_groups[1].pk,
|
'location': locations[1].pk,
|
||||||
'role': rack_roles[1].pk,
|
'role': rack_roles[1].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Rack 6',
|
'name': 'Test Rack 6',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'group': rack_groups[1].pk,
|
'location': locations[1].pk,
|
||||||
'role': rack_roles[1].pk,
|
'role': rack_roles[1].pk,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -1588,17 +1588,17 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
|
|||||||
Site.objects.create(name='Site 2', slug='site-2'),
|
Site.objects.create(name='Site 2', slug='site-2'),
|
||||||
)
|
)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup.objects.create(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location.objects.create(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup.objects.create(name='Rack Group 2', slug='rack-group-2', site=sites[0]),
|
Location.objects.create(name='Location 2', slug='location-2', site=sites[0]),
|
||||||
RackGroup.objects.create(name='Rack Group 3', slug='rack-group-3', site=sites[0]),
|
Location.objects.create(name='Location 3', slug='location-3', site=sites[0]),
|
||||||
RackGroup.objects.create(name='Rack Group 4', slug='rack-group-3', site=sites[1]),
|
Location.objects.create(name='Location 4', slug='location-3', site=sites[1]),
|
||||||
)
|
)
|
||||||
|
|
||||||
power_panels = (
|
power_panels = (
|
||||||
PowerPanel(site=sites[0], rack_group=rack_groups[0], name='Power Panel 1'),
|
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 1'),
|
||||||
PowerPanel(site=sites[0], rack_group=rack_groups[1], name='Power Panel 2'),
|
PowerPanel(site=sites[0], location=locations[1], name='Power Panel 2'),
|
||||||
PowerPanel(site=sites[0], rack_group=rack_groups[2], name='Power Panel 3'),
|
PowerPanel(site=sites[0], location=locations[2], name='Power Panel 3'),
|
||||||
)
|
)
|
||||||
PowerPanel.objects.bulk_create(power_panels)
|
PowerPanel.objects.bulk_create(power_panels)
|
||||||
|
|
||||||
@ -1606,23 +1606,23 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
|
|||||||
{
|
{
|
||||||
'name': 'Power Panel 4',
|
'name': 'Power Panel 4',
|
||||||
'site': sites[0].pk,
|
'site': sites[0].pk,
|
||||||
'rack_group': rack_groups[0].pk,
|
'location': locations[0].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Power Panel 5',
|
'name': 'Power Panel 5',
|
||||||
'site': sites[0].pk,
|
'site': sites[0].pk,
|
||||||
'rack_group': rack_groups[1].pk,
|
'location': locations[1].pk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Power Panel 6',
|
'name': 'Power Panel 6',
|
||||||
'site': sites[0].pk,
|
'site': sites[0].pk,
|
||||||
'rack_group': rack_groups[2].pk,
|
'location': locations[2].pk,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
cls.bulk_update_data = {
|
cls.bulk_update_data = {
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'rack_group': rack_groups[3].pk
|
'location': locations[3].pk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1636,20 +1636,20 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||||
rackgroup = RackGroup.objects.create(site=site, name='Rack Group 1', slug='rack-group-1')
|
location = Location.objects.create(site=site, name='Location 1', slug='location-1')
|
||||||
rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1', color='ff0000')
|
rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1', color='ff0000')
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 1'),
|
Rack(site=site, location=location, role=rackrole, name='Rack 1'),
|
||||||
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 2'),
|
Rack(site=site, location=location, role=rackrole, name='Rack 2'),
|
||||||
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 3'),
|
Rack(site=site, location=location, role=rackrole, name='Rack 3'),
|
||||||
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 4'),
|
Rack(site=site, location=location, role=rackrole, name='Rack 4'),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
power_panels = (
|
power_panels = (
|
||||||
PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 1'),
|
PowerPanel(site=site, location=location, name='Power Panel 1'),
|
||||||
PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 2'),
|
PowerPanel(site=site, location=location, name='Power Panel 2'),
|
||||||
)
|
)
|
||||||
PowerPanel.objects.bulk_create(power_panels)
|
PowerPanel.objects.bulk_create(power_panels)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from dcim.models import (
|
|||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
|
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
|
||||||
PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
PowerOutletTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||||
VirtualChassis,
|
VirtualChassis,
|
||||||
)
|
)
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
@ -168,9 +168,9 @@ class SiteTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RackGroupTestCase(TestCase):
|
class LocationTestCase(TestCase):
|
||||||
queryset = RackGroup.objects.all()
|
queryset = Location.objects.all()
|
||||||
filterset = RackGroupFilterSet
|
filterset = LocationFilterSet
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -190,32 +190,32 @@ class RackGroupTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
parent_rack_groups = (
|
parent_locations = (
|
||||||
RackGroup(name='Parent Rack Group 1', slug='parent-rack-group-1', site=sites[0]),
|
Location(name='Parent Location 1', slug='parent-location-1', site=sites[0]),
|
||||||
RackGroup(name='Parent Rack Group 2', slug='parent-rack-group-2', site=sites[1]),
|
Location(name='Parent Location 2', slug='parent-location-2', site=sites[1]),
|
||||||
RackGroup(name='Parent Rack Group 3', slug='parent-rack-group-3', site=sites[2]),
|
Location(name='Parent Location 3', slug='parent-location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for rackgroup in parent_rack_groups:
|
for location in parent_locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0], parent=parent_rack_groups[0], description='A'),
|
Location(name='Location 1', slug='location-1', site=sites[0], parent=parent_locations[0], description='A'),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1], parent=parent_rack_groups[1], description='B'),
|
Location(name='Location 2', slug='location-2', site=sites[1], parent=parent_locations[1], description='B'),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2], parent=parent_rack_groups[2], description='C'),
|
Location(name='Location 3', slug='location-3', site=sites[2], parent=parent_locations[2], description='C'),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Rack Group 1', 'Rack Group 2']}
|
params = {'name': ['Location 1', 'Location 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_slug(self):
|
def test_slug(self):
|
||||||
params = {'slug': ['rack-group-1', 'rack-group-2']}
|
params = {'slug': ['location-1', 'location-2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_description(self):
|
def test_description(self):
|
||||||
@ -237,7 +237,7 @@ class RackGroupTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
parent_groups = RackGroup.objects.filter(name__startswith='Parent')[:2]
|
parent_groups = Location.objects.filter(name__startswith='Parent')[:2]
|
||||||
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
|
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
|
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
|
||||||
@ -297,13 +297,13 @@ class RackTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
rack_roles = (
|
rack_roles = (
|
||||||
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
||||||
@ -328,9 +328,9 @@ class RackTestCase(TestCase):
|
|||||||
Tenant.objects.bulk_create(tenants)
|
Tenant.objects.bulk_create(tenants)
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
|
Rack(name='Rack 1', facility_id='rack-1', site=sites[0], location=locations[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
|
||||||
Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
|
Rack(name='Rack 2', facility_id='rack-2', site=sites[1], location=locations[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
|
||||||
Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH),
|
Rack(name='Rack 3', facility_id='rack-3', site=sites[2], location=locations[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
@ -395,11 +395,11 @@ class RackTestCase(TestCase):
|
|||||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_group(self):
|
def test_location(self):
|
||||||
groups = RackGroup.objects.all()[:2]
|
locations = Location.objects.all()[:2]
|
||||||
params = {'group_id': [groups[0].pk, groups[1].pk]}
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'group': [groups[0].slug, groups[1].slug]}
|
params = {'location': [locations[0].slug, locations[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_status(self):
|
def test_status(self):
|
||||||
@ -448,18 +448,18 @@ class RackReservationTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
|
Rack(name='Rack 1', site=sites[0], location=locations[0]),
|
||||||
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
|
Rack(name='Rack 2', site=sites[1], location=locations[1]),
|
||||||
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
|
Rack(name='Rack 3', site=sites[2], location=locations[2]),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
@ -503,11 +503,11 @@ class RackReservationTestCase(TestCase):
|
|||||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_group(self):
|
def test_location(self):
|
||||||
groups = RackGroup.objects.all()[:2]
|
locations = Location.objects.all()[:2]
|
||||||
params = {'group_id': [groups[0].pk, groups[1].pk]}
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'group': [groups[0].slug, groups[1].slug]}
|
params = {'location': [locations[0].slug, locations[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_user(self):
|
def test_user(self):
|
||||||
@ -1168,18 +1168,18 @@ class DeviceTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
|
Rack(name='Rack 1', site=sites[0], location=locations[0]),
|
||||||
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
|
Rack(name='Rack 2', site=sites[1], location=locations[1]),
|
||||||
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
|
Rack(name='Rack 3', site=sites[2], location=locations[2]),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
@ -1331,9 +1331,9 @@ class DeviceTestCase(TestCase):
|
|||||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_rackgroup(self):
|
def test_location(self):
|
||||||
rack_groups = RackGroup.objects.all()[:2]
|
locations = Location.objects.all()[:2]
|
||||||
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_rack(self):
|
def test_rack(self):
|
||||||
@ -2589,18 +2589,18 @@ class PowerPanelTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
power_panels = (
|
power_panels = (
|
||||||
PowerPanel(name='Power Panel 1', site=sites[0], rack_group=rack_groups[0]),
|
PowerPanel(name='Power Panel 1', site=sites[0], location=locations[0]),
|
||||||
PowerPanel(name='Power Panel 2', site=sites[1], rack_group=rack_groups[1]),
|
PowerPanel(name='Power Panel 2', site=sites[1], location=locations[1]),
|
||||||
PowerPanel(name='Power Panel 3', site=sites[2], rack_group=rack_groups[2]),
|
PowerPanel(name='Power Panel 3', site=sites[2], location=locations[2]),
|
||||||
)
|
)
|
||||||
PowerPanel.objects.bulk_create(power_panels)
|
PowerPanel.objects.bulk_create(power_panels)
|
||||||
|
|
||||||
@ -2626,9 +2626,9 @@ class PowerPanelTestCase(TestCase):
|
|||||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_rack_group(self):
|
def test_location(self):
|
||||||
rack_groups = RackGroup.objects.all()[:2]
|
locations = Location.objects.all()[:2]
|
||||||
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,37 +7,37 @@ from dcim.models import *
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
|
|
||||||
|
|
||||||
class RackGroupTestCase(TestCase):
|
class LocationTestCase(TestCase):
|
||||||
|
|
||||||
def test_change_rackgroup_site(self):
|
def test_change_location_site(self):
|
||||||
"""
|
"""
|
||||||
Check that all child RackGroups and Racks get updated when a RackGroup is moved to a new Site. Topology:
|
Check that all child Locations and Racks get updated when a Location is moved to a new Site. Topology:
|
||||||
Site A
|
Site A
|
||||||
- RackGroup A1
|
- Location A1
|
||||||
- RackGroup A2
|
- Location A2
|
||||||
- Rack 2
|
- Rack 2
|
||||||
- Rack 1
|
- Rack 1
|
||||||
"""
|
"""
|
||||||
site_a = Site.objects.create(name='Site A', slug='site-a')
|
site_a = Site.objects.create(name='Site A', slug='site-a')
|
||||||
site_b = Site.objects.create(name='Site B', slug='site-b')
|
site_b = Site.objects.create(name='Site B', slug='site-b')
|
||||||
|
|
||||||
rackgroup_a1 = RackGroup(site=site_a, name='RackGroup A1', slug='rackgroup-a1')
|
location_a1 = Location(site=site_a, name='Location A1', slug='location-a1')
|
||||||
rackgroup_a1.save()
|
location_a1.save()
|
||||||
rackgroup_a2 = RackGroup(site=site_a, parent=rackgroup_a1, name='RackGroup A2', slug='rackgroup-a2')
|
location_a2 = Location(site=site_a, parent=location_a1, name='Location A2', slug='location-a2')
|
||||||
rackgroup_a2.save()
|
location_a2.save()
|
||||||
|
|
||||||
rack1 = Rack.objects.create(site=site_a, group=rackgroup_a1, name='Rack 1')
|
rack1 = Rack.objects.create(site=site_a, location=location_a1, name='Rack 1')
|
||||||
rack2 = Rack.objects.create(site=site_a, group=rackgroup_a2, name='Rack 2')
|
rack2 = Rack.objects.create(site=site_a, location=location_a2, name='Rack 2')
|
||||||
|
|
||||||
powerpanel1 = PowerPanel.objects.create(site=site_a, rack_group=rackgroup_a1, name='Power Panel 1')
|
powerpanel1 = PowerPanel.objects.create(site=site_a, location=location_a1, name='Power Panel 1')
|
||||||
|
|
||||||
# Move RackGroup A1 to Site B
|
# Move Location A1 to Site B
|
||||||
rackgroup_a1.site = site_b
|
location_a1.site = site_b
|
||||||
rackgroup_a1.save()
|
location_a1.save()
|
||||||
|
|
||||||
# Check that all objects within RackGroup A1 now belong to Site B
|
# Check that all objects within Location A1 now belong to Site B
|
||||||
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a1.pk).site, site_b)
|
self.assertEqual(Location.objects.get(pk=location_a1.pk).site, site_b)
|
||||||
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a2.pk).site, site_b)
|
self.assertEqual(Location.objects.get(pk=location_a2.pk).site, site_b)
|
||||||
self.assertEqual(Rack.objects.get(pk=rack1.pk).site, site_b)
|
self.assertEqual(Rack.objects.get(pk=rack1.pk).site, site_b)
|
||||||
self.assertEqual(Rack.objects.get(pk=rack2.pk).site, site_b)
|
self.assertEqual(Rack.objects.get(pk=rack2.pk).site, site_b)
|
||||||
self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b)
|
self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b)
|
||||||
@ -55,12 +55,12 @@ class RackTestCase(TestCase):
|
|||||||
name='TestSite2',
|
name='TestSite2',
|
||||||
slug='test-site-2'
|
slug='test-site-2'
|
||||||
)
|
)
|
||||||
self.group1 = RackGroup.objects.create(
|
self.location1 = Location.objects.create(
|
||||||
name='TestGroup1',
|
name='TestGroup1',
|
||||||
slug='test-group-1',
|
slug='test-group-1',
|
||||||
site=self.site1
|
site=self.site1
|
||||||
)
|
)
|
||||||
self.group2 = RackGroup.objects.create(
|
self.location2 = Location.objects.create(
|
||||||
name='TestGroup2',
|
name='TestGroup2',
|
||||||
slug='test-group-2',
|
slug='test-group-2',
|
||||||
site=self.site2
|
site=self.site2
|
||||||
@ -69,7 +69,7 @@ class RackTestCase(TestCase):
|
|||||||
name='TestRack1',
|
name='TestRack1',
|
||||||
facility_id='A101',
|
facility_id='A101',
|
||||||
site=self.site1,
|
site=self.site1,
|
||||||
group=self.group1,
|
location=self.location1,
|
||||||
u_height=42
|
u_height=42
|
||||||
)
|
)
|
||||||
self.manufacturer = Manufacturer.objects.create(
|
self.manufacturer = Manufacturer.objects.create(
|
||||||
@ -134,19 +134,19 @@ class RackTestCase(TestCase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
rack1.clean()
|
rack1.clean()
|
||||||
|
|
||||||
def test_rack_group_site(self):
|
def test_location_site(self):
|
||||||
|
|
||||||
rack_invalid_group = Rack(
|
rack_invalid_location = Rack(
|
||||||
name='TestRack2',
|
name='TestRack2',
|
||||||
facility_id='A102',
|
facility_id='A102',
|
||||||
site=self.site1,
|
site=self.site1,
|
||||||
u_height=42,
|
u_height=42,
|
||||||
group=self.group2
|
location=self.location2
|
||||||
)
|
)
|
||||||
rack_invalid_group.save()
|
rack_invalid_location.save()
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
rack_invalid_group.clean()
|
rack_invalid_location.clean()
|
||||||
|
|
||||||
def test_mount_single_device(self):
|
def test_mount_single_device(self):
|
||||||
|
|
||||||
|
@ -117,8 +117,8 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||||
model = RackGroup
|
model = Location
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -126,26 +126,26 @@ class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
site = Site(name='Site 1', slug='site-1')
|
site = Site(name='Site 1', slug='site-1')
|
||||||
site.save()
|
site.save()
|
||||||
|
|
||||||
rack_groups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=site),
|
Location(name='Location 1', slug='location-1', site=site),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=site),
|
Location(name='Location 2', slug='location-2', site=site),
|
||||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=site),
|
Location(name='Location 3', slug='location-3', site=site),
|
||||||
)
|
)
|
||||||
for rackgroup in rack_groups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'name': 'Rack Group X',
|
'name': 'Location X',
|
||||||
'slug': 'rack-group-x',
|
'slug': 'location-x',
|
||||||
'site': site.pk,
|
'site': site.pk,
|
||||||
'description': 'A new rack group',
|
'description': 'A new location',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"site,name,slug,description",
|
"site,name,slug,description",
|
||||||
"Site 1,Rack Group 4,rack-group-4,Fourth rack group",
|
"Site 1,Location 4,location-4,Fourth location",
|
||||||
"Site 1,Rack Group 5,rack-group-5,Fifth rack group",
|
"Site 1,Location 5,location-5,Fifth location",
|
||||||
"Site 1,Rack Group 6,rack-group-6,Sixth rack group",
|
"Site 1,Location 6,location-6,Sixth location",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -187,10 +187,10 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||||
|
|
||||||
rack_group = RackGroup(name='Rack Group 1', slug='rack-group-1', site=site)
|
location = Location(name='Location 1', slug='location-1', site=site)
|
||||||
rack_group.save()
|
location.save()
|
||||||
|
|
||||||
rack = Rack(name='Rack 1', site=site, group=rack_group)
|
rack = Rack(name='Rack 1', site=site, location=location)
|
||||||
rack.save()
|
rack.save()
|
||||||
|
|
||||||
RackReservation.objects.bulk_create([
|
RackReservation.objects.bulk_create([
|
||||||
@ -211,10 +211,10 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
'site,rack_group,rack,units,description',
|
'site,location,rack,units,description',
|
||||||
'Site 1,Rack Group 1,Rack 1,"10,11,12",Reservation 1',
|
'Site 1,Location 1,Rack 1,"10,11,12",Reservation 1',
|
||||||
'Site 1,Rack Group 1,Rack 1,"13,14,15",Reservation 2',
|
'Site 1,Location 1,Rack 1,"13,14,15",Reservation 2',
|
||||||
'Site 1,Rack Group 1,Rack 1,"16,17,18",Reservation 3',
|
'Site 1,Location 1,Rack 1,"16,17,18",Reservation 3',
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -236,12 +236,12 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rackgroups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1])
|
Location(name='Location 2', slug='location-2', site=sites[1])
|
||||||
)
|
)
|
||||||
for rackgroup in rackgroups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
rackroles = (
|
rackroles = (
|
||||||
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
||||||
@ -261,7 +261,7 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'name': 'Rack X',
|
'name': 'Rack X',
|
||||||
'facility_id': 'Facility X',
|
'facility_id': 'Facility X',
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'group': rackgroups[1].pk,
|
'location': locations[1].pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'status': RackStatusChoices.STATUS_PLANNED,
|
'status': RackStatusChoices.STATUS_PLANNED,
|
||||||
'role': rackroles[1].pk,
|
'role': rackroles[1].pk,
|
||||||
@ -279,15 +279,15 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"site,group,name,width,u_height",
|
"site,location,name,width,u_height",
|
||||||
"Site 1,,Rack 4,19,42",
|
"Site 1,,Rack 4,19,42",
|
||||||
"Site 1,Rack Group 1,Rack 5,19,42",
|
"Site 1,Location 1,Rack 5,19,42",
|
||||||
"Site 2,Rack Group 2,Rack 6,19,42",
|
"Site 2,Location 2,Rack 6,19,42",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'group': rackgroups[1].pk,
|
'location': locations[1].pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'status': RackStatusChoices.STATUS_DEPRECATED,
|
'status': RackStatusChoices.STATUS_DEPRECATED,
|
||||||
'role': rackroles[1].pk,
|
'role': rackroles[1].pk,
|
||||||
@ -929,11 +929,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rack_group = RackGroup(site=sites[0], name='Rack Group 1', slug='rack-group-1')
|
location = Location(site=sites[0], name='Location 1', slug='location-1')
|
||||||
rack_group.save()
|
location.save()
|
||||||
|
|
||||||
racks = (
|
racks = (
|
||||||
Rack(name='Rack 1', site=sites[0], group=rack_group),
|
Rack(name='Rack 1', site=sites[0], location=location),
|
||||||
Rack(name='Rack 2', site=sites[1]),
|
Rack(name='Rack 2', site=sites[1]),
|
||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
@ -991,10 +991,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"device_role,manufacturer,device_type,status,name,site,rack_group,rack,position,face",
|
"device_role,manufacturer,device_type,status,name,site,location,rack,position,face",
|
||||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 4,Site 1,Rack Group 1,Rack 1,10,front",
|
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 4,Site 1,Location 1,Rack 1,10,front",
|
||||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 5,Site 1,Rack Group 1,Rack 1,20,front",
|
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 5,Site 1,Location 1,Rack 1,20,front",
|
||||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 6,Site 1,Rack Group 1,Rack 1,30,front",
|
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 6,Site 1,Location 1,Rack 1,30,front",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1771,38 +1771,38 @@ class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
rackgroups = (
|
locations = (
|
||||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
)
|
)
|
||||||
for rackgroup in rackgroups:
|
for location in locations:
|
||||||
rackgroup.save()
|
location.save()
|
||||||
|
|
||||||
PowerPanel.objects.bulk_create((
|
PowerPanel.objects.bulk_create((
|
||||||
PowerPanel(site=sites[0], rack_group=rackgroups[0], name='Power Panel 1'),
|
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 1'),
|
||||||
PowerPanel(site=sites[0], rack_group=rackgroups[0], name='Power Panel 2'),
|
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 2'),
|
||||||
PowerPanel(site=sites[0], rack_group=rackgroups[0], name='Power Panel 3'),
|
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'rack_group': rackgroups[1].pk,
|
'location': locations[1].pk,
|
||||||
'name': 'Power Panel X',
|
'name': 'Power Panel X',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"site,rack_group,name",
|
"site,location,name",
|
||||||
"Site 1,Rack Group 1,Power Panel 4",
|
"Site 1,Location 1,Power Panel 4",
|
||||||
"Site 1,Rack Group 1,Power Panel 5",
|
"Site 1,Location 1,Power Panel 5",
|
||||||
"Site 1,Rack Group 1,Power Panel 6",
|
"Site 1,Location 1,Power Panel 6",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'site': sites[1].pk,
|
'site': sites[1].pk,
|
||||||
'rack_group': rackgroups[1].pk,
|
'location': locations[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ 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,
|
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, FrontPort, Interface,
|
||||||
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup,
|
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, Location,
|
||||||
RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
|
RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ urlpatterns = [
|
|||||||
path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
|
path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
|
||||||
path('sites/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}),
|
path('sites/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}),
|
||||||
|
|
||||||
# Rack groups
|
# Locations
|
||||||
path('rack-groups/', views.RackGroupListView.as_view(), name='rackgroup_list'),
|
path('locations/', views.LocationListView.as_view(), name='location_list'),
|
||||||
path('rack-groups/add/', views.RackGroupEditView.as_view(), name='rackgroup_add'),
|
path('locations/add/', views.LocationEditView.as_view(), name='location_add'),
|
||||||
path('rack-groups/import/', views.RackGroupBulkImportView.as_view(), name='rackgroup_import'),
|
path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
|
||||||
path('rack-groups/delete/', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
|
path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
|
||||||
path('rack-groups/<int:pk>/edit/', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
|
path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
|
||||||
path('rack-groups/<int:pk>/delete/', views.RackGroupDeleteView.as_view(), name='rackgroup_delete'),
|
path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
|
||||||
path('rack-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rackgroup_changelog', kwargs={'model': RackGroup}),
|
path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
|
||||||
|
|
||||||
# Rack roles
|
# Rack roles
|
||||||
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),
|
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),
|
||||||
|
@ -30,7 +30,7 @@ from .models import (
|
|||||||
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
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, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
|
||||||
VirtualChassis,
|
VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -161,17 +161,17 @@ class SiteView(generic.ObjectView):
|
|||||||
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(),
|
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(),
|
||||||
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
|
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
|
||||||
}
|
}
|
||||||
rack_groups = RackGroup.objects.add_related_count(
|
locations = Location.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
Location.objects.all(),
|
||||||
Rack,
|
Rack,
|
||||||
'group',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).restrict(request.user, 'view').filter(site=instance)
|
).restrict(request.user, 'view').filter(site=instance)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'rack_groups': rack_groups,
|
'locations': locations,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -207,44 +207,44 @@ class SiteBulkDeleteView(generic.BulkDeleteView):
|
|||||||
# Rack groups
|
# Rack groups
|
||||||
#
|
#
|
||||||
|
|
||||||
class RackGroupListView(generic.ObjectListView):
|
class LocationListView(generic.ObjectListView):
|
||||||
queryset = RackGroup.objects.add_related_count(
|
queryset = Location.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
Location.objects.all(),
|
||||||
Rack,
|
Rack,
|
||||||
'group',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.RackGroupFilterSet
|
filterset = filters.LocationFilterSet
|
||||||
filterset_form = forms.RackGroupFilterForm
|
filterset_form = forms.LocationFilterForm
|
||||||
table = tables.RackGroupTable
|
table = tables.LocationTable
|
||||||
|
|
||||||
|
|
||||||
class RackGroupEditView(generic.ObjectEditView):
|
class LocationEditView(generic.ObjectEditView):
|
||||||
queryset = RackGroup.objects.all()
|
queryset = Location.objects.all()
|
||||||
model_form = forms.RackGroupForm
|
model_form = forms.LocationForm
|
||||||
|
|
||||||
|
|
||||||
class RackGroupDeleteView(generic.ObjectDeleteView):
|
class LocationDeleteView(generic.ObjectDeleteView):
|
||||||
queryset = RackGroup.objects.all()
|
queryset = Location.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class RackGroupBulkImportView(generic.BulkImportView):
|
class LocationBulkImportView(generic.BulkImportView):
|
||||||
queryset = RackGroup.objects.all()
|
queryset = Location.objects.all()
|
||||||
model_form = forms.RackGroupCSVForm
|
model_form = forms.LocationCSVForm
|
||||||
table = tables.RackGroupTable
|
table = tables.LocationTable
|
||||||
|
|
||||||
|
|
||||||
class RackGroupBulkDeleteView(generic.BulkDeleteView):
|
class LocationBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackGroup.objects.add_related_count(
|
queryset = Location.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
Location.objects.all(),
|
||||||
Rack,
|
Rack,
|
||||||
'group',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site')
|
).prefetch_related('site')
|
||||||
filterset = filters.RackGroupFilterSet
|
filterset = filters.LocationFilterSet
|
||||||
table = tables.RackGroupTable
|
table = tables.LocationTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -286,7 +286,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RackListView(generic.ObjectListView):
|
class RackListView(generic.ObjectListView):
|
||||||
queryset = Rack.objects.prefetch_related(
|
queryset = Rack.objects.prefetch_related(
|
||||||
'site', 'group', 'tenant', 'role', 'devices__device_type'
|
'site', 'location', 'tenant', 'role', 'devices__device_type'
|
||||||
).annotate(
|
).annotate(
|
||||||
device_count=count_related(Device, 'rack')
|
device_count=count_related(Device, 'rack')
|
||||||
)
|
)
|
||||||
@ -338,7 +338,7 @@ class RackElevationListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class RackView(generic.ObjectView):
|
class RackView(generic.ObjectView):
|
||||||
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role')
|
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
# Get 0U and child devices located within the rack
|
# Get 0U and child devices located within the rack
|
||||||
@ -349,10 +349,10 @@ class RackView(generic.ObjectView):
|
|||||||
|
|
||||||
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
||||||
|
|
||||||
if instance.group:
|
if instance.location:
|
||||||
peer_racks = peer_racks.filter(group=instance.group)
|
peer_racks = peer_racks.filter(location=instance.location)
|
||||||
else:
|
else:
|
||||||
peer_racks = peer_racks.filter(group__isnull=True)
|
peer_racks = peer_racks.filter(location__isnull=True)
|
||||||
next_rack = peer_racks.filter(name__gt=instance.name).order_by('name').first()
|
next_rack = peer_racks.filter(name__gt=instance.name).order_by('name').first()
|
||||||
prev_rack = peer_racks.filter(name__lt=instance.name).order_by('-name').first()
|
prev_rack = peer_racks.filter(name__lt=instance.name).order_by('-name').first()
|
||||||
|
|
||||||
@ -390,14 +390,14 @@ class RackBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class RackBulkEditView(generic.BulkEditView):
|
class RackBulkEditView(generic.BulkEditView):
|
||||||
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
||||||
filterset = filters.RackFilterSet
|
filterset = filters.RackFilterSet
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
form = forms.RackBulkEditForm
|
form = forms.RackBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class RackBulkDeleteView(generic.BulkDeleteView):
|
class RackBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
||||||
filterset = filters.RackFilterSet
|
filterset = filters.RackFilterSet
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
|
|
||||||
@ -982,7 +982,7 @@ class DeviceListView(generic.ObjectListView):
|
|||||||
|
|
||||||
class DeviceView(generic.ObjectView):
|
class DeviceView(generic.ObjectView):
|
||||||
queryset = Device.objects.prefetch_related(
|
queryset = Device.objects.prefetch_related(
|
||||||
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
|
'site__region', 'rack__location', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
@ -2560,7 +2560,7 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerPanelListView(generic.ObjectListView):
|
class PowerPanelListView(generic.ObjectListView):
|
||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'location'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
@ -2570,7 +2570,7 @@ class PowerPanelListView(generic.ObjectListView):
|
|||||||
|
|
||||||
|
|
||||||
class PowerPanelView(generic.ObjectView):
|
class PowerPanelView(generic.ObjectView):
|
||||||
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
|
queryset = PowerPanel.objects.prefetch_related('site', 'location')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=instance).prefetch_related('rack')
|
power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=instance).prefetch_related('rack')
|
||||||
@ -2601,7 +2601,7 @@ class PowerPanelBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class PowerPanelBulkEditView(generic.BulkEditView):
|
class PowerPanelBulkEditView(generic.BulkEditView):
|
||||||
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
|
queryset = PowerPanel.objects.prefetch_related('site', 'location')
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filters.PowerPanelFilterSet
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
form = forms.PowerPanelBulkEditForm
|
form = forms.PowerPanelBulkEditForm
|
||||||
@ -2609,7 +2609,7 @@ class PowerPanelBulkEditView(generic.BulkEditView):
|
|||||||
|
|
||||||
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'location'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ from django_rq.queues import get_connection
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rq import Worker
|
from rq import Worker
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
||||||
from extras.api.views import ReportViewSet, ScriptViewSet
|
from extras.api.views import ReportViewSet, ScriptViewSet
|
||||||
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag
|
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag
|
||||||
from extras.reports import Report
|
from extras.reports import Report
|
||||||
@ -382,13 +382,13 @@ class CreatedUpdatedFilterTest(APITestCase):
|
|||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||||
self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
|
self.location1 = Location.objects.create(site=self.site1, name='Test Location 1', slug='test-location-1')
|
||||||
self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
|
self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
|
||||||
self.rack1 = Rack.objects.create(
|
self.rack1 = Rack.objects.create(
|
||||||
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42,
|
site=self.site1, location=self.location1, role=self.rackrole1, name='Test Rack 1', u_height=42,
|
||||||
)
|
)
|
||||||
self.rack2 = Rack.objects.create(
|
self.rack2 = Rack.objects.create(
|
||||||
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42,
|
site=self.site1, location=self.location1, role=self.rackrole1, name='Test Rack 2', u_height=42,
|
||||||
)
|
)
|
||||||
|
|
||||||
# change the created and last_updated of one
|
# change the created and last_updated of one
|
||||||
|
@ -6,12 +6,12 @@ from circuits.filters import CircuitFilterSet, ProviderFilterSet
|
|||||||
from circuits.models import Circuit, Provider
|
from circuits.models import Circuit, Provider
|
||||||
from circuits.tables import CircuitTable, ProviderTable
|
from circuits.tables import CircuitTable, ProviderTable
|
||||||
from dcim.filters import (
|
from dcim.filters import (
|
||||||
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, RackGroupFilterSet,
|
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
|
||||||
SiteFilterSet, VirtualChassisFilterSet,
|
SiteFilterSet, VirtualChassisFilterSet,
|
||||||
)
|
)
|
||||||
from dcim.models import Cable, Device, DeviceType, PowerFeed, Rack, RackGroup, Site, VirtualChassis
|
from dcim.models import Cable, Device, DeviceType, PowerFeed, Rack, Location, Site, VirtualChassis
|
||||||
from dcim.tables import (
|
from dcim.tables import (
|
||||||
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
|
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
|
||||||
VirtualChassisTable,
|
VirtualChassisTable,
|
||||||
)
|
)
|
||||||
from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
|
from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
|
||||||
@ -55,22 +55,22 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'dcim:site_list',
|
'url': 'dcim:site_list',
|
||||||
}),
|
}),
|
||||||
('rack', {
|
('rack', {
|
||||||
'queryset': Rack.objects.prefetch_related('site', 'group', 'tenant', 'role'),
|
'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'role'),
|
||||||
'filterset': RackFilterSet,
|
'filterset': RackFilterSet,
|
||||||
'table': RackTable,
|
'table': RackTable,
|
||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
('rackgroup', {
|
('location', {
|
||||||
'queryset': RackGroup.objects.add_related_count(
|
'queryset': Location.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
Location.objects.all(),
|
||||||
Rack,
|
Rack,
|
||||||
'group',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site'),
|
).prefetch_related('site'),
|
||||||
'filterset': RackGroupFilterSet,
|
'filterset': LocationFilterSet,
|
||||||
'table': RackGroupTable,
|
'table': LocationTable,
|
||||||
'url': 'dcim:rackgroup_list',
|
'url': 'dcim:location_list',
|
||||||
}),
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
|
@ -11,7 +11,7 @@ OBJ_TYPE_CHOICES = (
|
|||||||
('DCIM', (
|
('DCIM', (
|
||||||
('site', 'Sites'),
|
('site', 'Sites'),
|
||||||
('rack', 'Racks'),
|
('rack', 'Racks'),
|
||||||
('rackgroup', 'Rack Groups'),
|
('location', 'Locations'),
|
||||||
('devicetype', 'Device types'),
|
('devicetype', 'Device types'),
|
||||||
('device', 'Devices'),
|
('device', 'Devices'),
|
||||||
('virtualchassis', 'Virtual Chassis'),
|
('virtualchassis', 'Virtual Chassis'),
|
||||||
|
@ -428,7 +428,7 @@ CACHEOPS = {
|
|||||||
'circuits.*': {'ops': 'all'},
|
'circuits.*': {'ops': 'all'},
|
||||||
'dcim.inventoryitem': None, # MPTT models are exempt due to raw SQL
|
'dcim.inventoryitem': None, # MPTT models are exempt due to raw SQL
|
||||||
'dcim.region': None, # MPTT models are exempt due to raw SQL
|
'dcim.region': None, # MPTT models are exempt due to raw SQL
|
||||||
'dcim.rackgroup': None, # MPTT models are exempt due to raw SQL
|
'dcim.location': None, # MPTT models are exempt due to raw SQL
|
||||||
'dcim.*': {'ops': 'all'},
|
'dcim.*': {'ops': 'all'},
|
||||||
'ipam.*': {'ops': 'all'},
|
'ipam.*': {'ops': 'all'},
|
||||||
'extras.*': {'ops': 'all'},
|
'extras.*': {'ops': 'all'},
|
||||||
|
@ -123,8 +123,8 @@
|
|||||||
{% if 'termination_b_site' in form.fields %}
|
{% if 'termination_b_site' in form.fields %}
|
||||||
{% render_field form.termination_b_site %}
|
{% render_field form.termination_b_site %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'termination_b_rackgroup' in form.fields %}
|
{% if 'termination_b_location' in form.fields %}
|
||||||
{% render_field form.termination_b_rackgroup %}
|
{% render_field form.termination_b_location %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'termination_b_rack' in form.fields %}
|
{% if 'termination_b_rack' in form.fields %}
|
||||||
{% render_field form.termination_b_rack %}
|
{% render_field form.termination_b_rack %}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.region %}
|
{% render_field form.region %}
|
||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
{% render_field form.rack_group %}
|
{% render_field form.location %}
|
||||||
{% render_field form.rack %}
|
{% render_field form.rack %}
|
||||||
{% if obj.device_type.is_child_device and obj.parent_bay %}
|
{% if obj.device_type.is_child_device and obj.parent_bay %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></li>
|
<li><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></li>
|
||||||
<li><a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a></li>
|
<li><a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a></li>
|
||||||
{% if object.rack_group %}
|
{% if object.location %}
|
||||||
<li><a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a></li>
|
<li><a href="{{ object.location.get_absolute_url }}">{{ object.location }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ object }}</li>
|
<li>{{ object }}</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -26,10 +26,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rack Group</td>
|
<td>Location</td>
|
||||||
<td>
|
<td>
|
||||||
{% if object.rack_group %}
|
{% if object.location %}
|
||||||
<a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a>
|
<a href="{{ object.location.get_absolute_url }}">{{ object.location }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -61,13 +61,13 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Group</td>
|
<td>Location</td>
|
||||||
<td>
|
<td>
|
||||||
{% if object.group %}
|
{% if object.location %}
|
||||||
{% for group in object.group.get_ancestors %}
|
{% for location in object.location.get_ancestors %}
|
||||||
<a href="{{ group.get_absolute_url }}">{{ group }}</a> /
|
<a href="{{ location.get_absolute_url }}">{{ location }}</a> /
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
|
<a href="{{ object.location.get_absolute_url }}">{{ object.location }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.region %}
|
{% render_field form.region %}
|
||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
{% render_field form.group %}
|
{% render_field form.location %}
|
||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.status %}
|
{% render_field form.status %}
|
||||||
{% render_field form.role %}
|
{% render_field form.role %}
|
||||||
|
@ -194,15 +194,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Rack Groups</strong>
|
<strong>Locations</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for rg in rack_groups %}
|
{% for location in locations %}
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding-left: {{ rg.level }}8px"><i class="mdi mdi-folder-open"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td>
|
<td style="padding-left: {{ location.level }}8px">
|
||||||
<td>{{ rg.rack_count }}</td>
|
<i class="mdi mdi-folder-open"></i> <a href="{{ location.get_absolute_url }}">{{ location }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ location.rack_count }}</td>
|
||||||
<td class="text-right noprint">
|
<td class="text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}?group_id={{ rg.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
<a href="{% url 'dcim:rack_elevation_list' %}?location_id={{ location.pk }}" class="btn btn-xs btn-primary" title="View elevations">
|
||||||
<i class="mdi mdi-server"></i>
|
<i class="mdi mdi-server"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -49,14 +49,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:rack_list' %}">Racks</a>
|
<a href="{% url 'dcim:rack_list' %}">Racks</a>
|
||||||
</li>
|
</li>
|
||||||
<li{% if not perms.dcim.view_rackgroup %} class="disabled"{% endif %}>
|
<li{% if not perms.dcim.view_location %} class="disabled"{% endif %}>
|
||||||
{% if perms.dcim.add_rackgroup %}
|
{% if perms.dcim.add_location %}
|
||||||
<div class="buttons pull-right">
|
<div class="buttons pull-right">
|
||||||
<a href="{% url 'dcim:rackgroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a>
|
<a href="{% url 'dcim:location_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a>
|
||||||
<a href="{% url 'dcim:rackgroup_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
|
<a href="{% url 'dcim:location_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:rackgroup_list' %}">Rack Groups</a>
|
<a href="{% url 'dcim:location_list' %}">Locations</a>
|
||||||
</li>
|
</li>
|
||||||
<li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}>
|
<li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}>
|
||||||
{% if perms.dcim.add_rackrole %}
|
{% if perms.dcim.add_rackrole %}
|
||||||
|
Loading…
Reference in New Issue
Block a user