Closes #5895: Rename RackGroup to Location

This commit is contained in:
Jeremy Stretch 2021-03-03 13:30:33 -05:00
parent a17018a875
commit fdb3e3f9a4
33 changed files with 536 additions and 488 deletions

View File

@ -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
* [#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
* [#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
### 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)
* 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
* 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
* Added new custom field type: `multi-select`

View File

@ -27,7 +27,7 @@ __all__ = [
'NestedPowerPanelSerializer',
'NestedPowerPortSerializer',
'NestedPowerPortTemplateSerializer',
'NestedRackGroupSerializer',
'NestedLocationSerializer',
'NestedRackReservationSerializer',
'NestedRackRoleSerializer',
'NestedRackSerializer',
@ -65,13 +65,13 @@ class NestedSiteSerializer(WritableNestedSerializer):
# Racks
#
class NestedRackGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
class NestedLocationSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
rack_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = models.RackGroup
model = models.Location
fields = ['id', 'url', 'name', 'slug', 'rack_count', '_depth']

View File

@ -10,7 +10,7 @@ from dcim.models import (
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from netbox.api.serializers import CustomFieldModelSerializer
@ -121,14 +121,14 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
# Racks
#
class RackGroupSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
class LocationSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
site = NestedSiteSerializer()
parent = NestedRackGroupSerializer(required=False, allow_null=True)
parent = NestedLocationSerializer(required=False, allow_null=True)
rack_count = serializers.IntegerField(read_only=True)
class Meta:
model = RackGroup
model = Location
fields = [
'id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
'rack_count', '_depth',
@ -150,7 +150,7 @@ class RackRoleSerializer(OrganizationalModelSerializer):
class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
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)
status = ChoiceField(choices=RackStatusChoices, required=False)
role = NestedRackRoleSerializer(required=False, allow_null=True)
@ -163,21 +163,22 @@ class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
class Meta:
model = Rack
fields = [
'id', 'url', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
'id', 'url', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status', 'role',
'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
'powerfeed_count',
]
# 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.
validators = [
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'name'))
UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'name'))
]
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):
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('group', 'facility_id'))
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=('location', 'facility_id'))
validator(data, self)
# Enforce model validation
@ -856,7 +857,7 @@ class VirtualChassisSerializer(TaggedObjectSerializer, CustomFieldModelSerialize
class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
site = NestedSiteSerializer()
rack_group = NestedRackGroupSerializer(
location = NestedLocationSerializer(
required=False,
allow_null=True,
default=None
@ -865,7 +866,7 @@ class PowerPanelSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
class Meta:
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(

View File

@ -10,7 +10,7 @@ router.register('regions', views.RegionViewSet)
router.register('sites', views.SiteViewSet)
# Racks
router.register('rack-groups', views.RackGroupViewSet)
router.register('locations', views.LocationViewSet)
router.register('rack-roles', views.RackRoleViewSet)
router.register('racks', views.RackViewSet)
router.register('rack-reservations', views.RackReservationViewSet)

View File

@ -20,7 +20,7 @@ from dcim.models import (
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
@ -134,16 +134,16 @@ class SiteViewSet(CustomFieldModelViewSet):
# Rack groups
#
class RackGroupViewSet(CustomFieldModelViewSet):
queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
class LocationViewSet(CustomFieldModelViewSet):
queryset = Location.objects.add_related_count(
Location.objects.all(),
Rack,
'group',
'location',
'rack_count',
cumulative=True
).prefetch_related('site')
serializer_class = serializers.RackGroupSerializer
filterset_class = filters.RackGroupFilterSet
serializer_class = serializers.LocationSerializer
filterset_class = filters.LocationFilterSet
#
@ -164,7 +164,7 @@ class RackRoleViewSet(CustomFieldModelViewSet):
class RackViewSet(CustomFieldModelViewSet):
queryset = Rack.objects.prefetch_related(
'site', 'group__site', 'role', 'tenant', 'tags'
'site', 'location__site', 'role', 'tenant', 'tags'
).annotate(
device_count=count_related(Device, 'rack'),
powerfeed_count=count_related(PowerFeed, 'rack')
@ -619,7 +619,7 @@ class VirtualChassisViewSet(ModelViewSet):
class PowerPanelViewSet(ModelViewSet):
queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group'
'site', 'location'
).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel')
)

View File

@ -16,7 +16,7 @@ from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -40,6 +40,7 @@ __all__ = (
'InterfaceFilterSet',
'InterfaceTemplateFilterSet',
'InventoryItemFilterSet',
'LocationFilterSet',
'ManufacturerFilterSet',
'PathEndpointFilterSet',
'PlatformFilterSet',
@ -51,7 +52,6 @@ __all__ = (
'PowerPortFilterSet',
'PowerPortTemplateFilterSet',
'RackFilterSet',
'RackGroupFilterSet',
'RackReservationFilterSet',
'RackRoleFilterSet',
'RearPortFilterSet',
@ -131,7 +131,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
return queryset.filter(qs_filter)
class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='site__region',
@ -156,18 +156,18 @@ class RackGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
label='Site (slug)',
)
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
queryset=Location.objects.all(),
label='Rack group (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=RackGroup.objects.all(),
queryset=Location.objects.all(),
to_field_name='slug',
label='Rack group (slug)',
)
class Meta:
model = RackGroup
model = Location
fields = ['id', 'name', 'slug', 'description']
@ -206,18 +206,18 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
to_field_name='slug',
label='Site (slug)',
)
group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='group',
location_id = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='location',
lookup_expr='in',
label='Rack group (ID)',
label='Location (ID)',
)
group = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='group',
location = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='location',
lookup_expr='in',
to_field_name='slug',
label='Rack group (slug)',
label='Location (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=RackStatusChoices,
@ -283,18 +283,18 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel
to_field_name='slug',
label='Site (slug)',
)
group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='rack__group',
location_id = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='rack__location',
lookup_expr='in',
label='Rack group (ID)',
label='Location (ID)',
)
group = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='rack__group',
location = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='rack__location',
lookup_expr='in',
to_field_name='slug',
label='Rack group (slug)',
label='Location (slug)',
)
user_id = django_filters.ModelMultipleChoiceFilter(
queryset=User.objects.all(),
@ -575,11 +575,11 @@ class DeviceFilterSet(
to_field_name='slug',
label='Site name (slug)',
)
rack_group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='rack__group',
location_id = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='rack__location',
lookup_expr='in',
label='Rack group (ID)',
label='Location (ID)',
)
rack_id = django_filters.ModelMultipleChoiceFilter(
field_name='rack',
@ -1236,9 +1236,9 @@ class PowerPanelFilterSet(BaseFilterSet):
to_field_name='slug',
label='Site name (slug)',
)
rack_group_id = TreeNodeMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
field_name='rack_group',
location_id = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='location',
lookup_expr='in',
label='Rack group (ID)',
)

View File

@ -35,7 +35,7 @@ from .models import (
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
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(
queryset=Region.objects.all(),
required=False,
@ -376,7 +376,7 @@ class RackGroupForm(BootstrapMixin, CustomFieldModelForm):
}
)
parent = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -385,20 +385,20 @@ class RackGroupForm(BootstrapMixin, CustomFieldModelForm):
slug = SlugField()
class Meta:
model = RackGroup
model = Location
fields = (
'region', 'site', 'parent', 'name', 'slug', 'description',
)
class RackGroupCSVForm(CustomFieldModelCSVForm):
class LocationCSVForm(CustomFieldModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
help_text='Assigned site'
)
parent = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
queryset=Location.objects.all(),
required=False,
to_field_name='name',
help_text='Parent rack group',
@ -408,11 +408,11 @@ class RackGroupCSVForm(CustomFieldModelCSVForm):
)
class Meta:
model = RackGroup
fields = RackGroup.csv_headers
model = Location
fields = Location.csv_headers
class RackGroupFilterForm(BootstrapMixin, forms.Form):
class LocationFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
@ -427,7 +427,7 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form):
label=_('Site')
)
parent = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
queryset=Location.objects.all(),
required=False,
query_params={
'region_id': '$region_id',
@ -480,8 +480,8 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
'region_id': '$region'
}
)
group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -500,7 +500,7 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class Meta:
model = Rack
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',
'comments', 'tags',
]
@ -523,8 +523,8 @@ class RackCSVForm(CustomFieldModelCSVForm):
queryset=Site.objects.all(),
to_field_name='name'
)
group = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
location = CSVModelChoiceField(
queryset=Location.objects.all(),
required=False,
to_field_name='name'
)
@ -569,9 +569,9 @@ class RackCSVForm(CustomFieldModelCSVForm):
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')}
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):
@ -593,8 +593,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
'region_id': '$region'
}
)
group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -662,13 +662,13 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
class Meta:
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):
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(
required=False,
label=_('Search')
@ -686,14 +686,14 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
},
label=_('Site')
)
group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site_id'
},
label=_('Rack group')
label=_('Location')
)
status = forms.MultipleChoiceField(
choices=RackStatusChoices,
@ -724,7 +724,9 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
#
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(
queryset=Rack.objects.all(),
label=_('Rack'),
@ -732,7 +734,7 @@ class RackElevationFilterForm(RackFilterForm):
display_field='display_name',
query_params={
'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'
}
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -768,7 +770,7 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
display_field='display_name',
query_params={
'site_id': '$site',
'group_id': '$rack_group',
'location_id': 'location',
}
)
units = NumericArrayField(
@ -789,10 +791,10 @@ class RackReservationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
class Meta:
model = RackReservation
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 = (
('Reservation', ('region', 'site', 'rack_group', 'rack', 'units', 'user', 'description', 'tags')),
('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
('Tenancy', ('tenant_group', 'tenant')),
)
@ -803,11 +805,11 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
to_field_name='name',
help_text='Parent site'
)
rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
location = CSVModelChoiceField(
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text="Rack's group (if any)"
help_text="Rack's location (if any)"
)
rack = CSVModelChoiceField(
queryset=Rack.objects.all(),
@ -828,21 +830,21 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
class Meta:
model = RackReservation
fields = ('site', 'rack_group', 'rack', 'units', 'tenant', 'description')
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description')
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
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')}
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
params = {
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)
@ -874,7 +876,7 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField
class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
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(
required=False,
label=_('Search')
@ -892,10 +894,10 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm):
},
label=_('Region')
)
group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.prefetch_related('site'),
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.prefetch_related('site'),
required=False,
label='Rack group',
label='Location',
null_option='None'
)
user_id = DynamicModelMultipleChoiceField(
@ -1782,8 +1784,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
'region_id': '$region'
}
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
display_field='display_name',
query_params={
@ -1799,7 +1801,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
display_field='display_name',
query_params={
'site_id': '$site',
'group_id': '$rack_group',
'location_id': 'location',
}
)
position = forms.IntegerField(
@ -2003,11 +2005,11 @@ class DeviceCSVForm(BaseDeviceCSVForm):
to_field_name='name',
help_text='Assigned site'
)
rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
location = CSVModelChoiceField(
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text="Rack's group (if any)"
help_text="Rack's location (if any)"
)
rack = CSVModelChoiceField(
queryset=Rack.objects.all(),
@ -2024,7 +2026,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
class Meta(BaseDeviceCSVForm.Meta):
fields = [
'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):
@ -2032,14 +2034,14 @@ class DeviceCSVForm(BaseDeviceCSVForm):
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')}
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
params = {
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)
@ -2135,7 +2137,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldFilterForm):
model = Device
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',
]
q = forms.CharField(
@ -2153,10 +2155,10 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
'region_id': '$region_id'
}
)
rack_group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
label=_('Rack group'),
label=_('Location'),
query_params={
'site_id': '$site_id'
}
@ -2167,7 +2169,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
null_option='None',
query_params={
'site_id': '$site_id',
'group_id': '$rack_group_id',
'location_id': '$location_id',
},
label=_('Rack')
)
@ -3834,9 +3836,9 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
'region_id': '$termination_b_region'
}
)
termination_b_rackgroup = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
label='Rack Group',
termination_b_location = DynamicModelChoiceField(
queryset=Location.objects.all(),
label='Location',
required=False,
display_field='cid',
query_params={
@ -3849,7 +3851,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
required=False,
query_params={
'site_id': '$termination_b_site',
'rack_group_id': '$termination_b_rackgroup',
'location_id': '$termination_b_location',
}
)
termination_b_id = DynamicModelChoiceField(
@ -3868,7 +3870,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
class Meta:
model = Cable
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',
]
@ -4450,8 +4452,8 @@ class PowerPanelForm(BootstrapMixin, CustomFieldModelForm):
'region_id': '$region'
}
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -4465,10 +4467,10 @@ class PowerPanelForm(BootstrapMixin, CustomFieldModelForm):
class Meta:
model = PowerPanel
fields = [
'region', 'site', 'rack_group', 'name', 'tags',
'region', 'site', 'location', 'name', 'tags',
]
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',
help_text='Name of parent site'
)
rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
location = CSVModelChoiceField(
queryset=Location.objects.all(),
required=False,
to_field_name='name'
)
@ -4495,7 +4497,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
# Limit group queryset by assigned 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):
@ -4517,8 +4519,8 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
'region_id': '$region'
}
)
rack_group = DynamicModelChoiceField(
queryset=RackGroup.objects.all(),
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'site_id': '$site'
@ -4526,7 +4528,7 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkE
)
class Meta:
nullable_fields = ['rack_group']
nullable_fields = ['location']
class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
@ -4548,14 +4550,14 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
},
label=_('Site')
)
rack_group_id = DynamicModelMultipleChoiceField(
queryset=RackGroup.objects.all(),
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
null_option='None',
query_params={
'site_id': '$site_id'
},
label=_('Rack group')
label=_('Location')
)
tag = TagFilterField(model)
@ -4632,11 +4634,11 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
to_field_name='name',
help_text='Upstream power panel'
)
rack_group = CSVModelChoiceField(
queryset=RackGroup.objects.all(),
location = CSVModelChoiceField(
queryset=Location.objects.all(),
to_field_name='name',
required=False,
help_text="Rack's group (if any)"
help_text="Rack's location (if any)"
)
rack = CSVModelChoiceField(
queryset=Rack.objects.all(),
@ -4678,14 +4680,14 @@ class PowerFeedCSVForm(CustomFieldModelCSVForm):
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
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')}
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
params = {
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)
@ -4748,7 +4750,7 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
class Meta:
nullable_fields = [
'rackgroup', 'comments',
'location', 'comments',
]

View 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')},
),
]

View File

@ -34,7 +34,7 @@ __all__ = (
'PowerPort',
'PowerPortTemplate',
'Rack',
'RackGroup',
'Location',
'RackReservation',
'RackRole',
'RearPort',

View File

@ -600,7 +600,7 @@ class Device(PrimaryModel, ConfigContextModel):
csv_headers = [
'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 = [
'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster',
@ -799,7 +799,7 @@ class Device(PrimaryModel, ConfigContextModel):
self.asset_tag,
self.get_status_display(),
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.position,
self.get_face_display(),

View File

@ -32,8 +32,8 @@ class PowerPanel(PrimaryModel):
to='Site',
on_delete=models.PROTECT
)
rack_group = models.ForeignKey(
to='RackGroup',
location = models.ForeignKey(
to='dcim.Location',
on_delete=models.PROTECT,
blank=True,
null=True
@ -45,7 +45,7 @@ class PowerPanel(PrimaryModel):
objects = RestrictedQuerySet.as_manager()
csv_headers = ['site', 'rack_group', 'name']
csv_headers = ['site', 'location', 'name']
class Meta:
ordering = ['site', 'name']
@ -60,17 +60,17 @@ class PowerPanel(PrimaryModel):
def to_csv(self):
return (
self.site.name,
self.rack_group.name if self.rack_group else None,
self.location.name if self.location else None,
self.name,
)
def clean(self):
super().clean()
# RackGroup must belong to assigned Site
if self.rack_group and self.rack_group.site != self.site:
# Location must belong to assigned Site
if self.location and self.location.site != self.site:
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()
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',
]
clone_fields = [
@ -160,7 +160,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
return (
self.power_panel.site.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.name,
self.get_status_display(),

View File

@ -16,21 +16,20 @@ from taggit.managers import TaggableManager
from dcim.choices import *
from dcim.constants import *
from dcim.elevations import RackElevationSVG
from extras.models import ObjectChange, TaggedItem
from extras.models import TaggedItem
from extras.utils import extras_features
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField
from utilities.querysets import RestrictedQuerySet
from utilities.mptt import TreeManager
from utilities.utils import array_to_string, serialize_object
from utilities.utils import array_to_string
from .device_components import PowerOutlet, PowerPort
from .devices import Device
from .power import PowerFeed
__all__ = (
'Rack',
'RackGroup',
'Location',
'RackReservation',
'RackRole',
)
@ -41,11 +40,10 @@ __all__ = (
#
@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
example, if a Site spans a corporate campus, a RackGroup might be defined to represent each building within that
campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor.
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
site, or a room within a building, for example.
"""
name = models.CharField(
max_length=100
@ -56,7 +54,7 @@ class RackGroup(NestedGroupModel):
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.CASCADE,
related_name='rack_groups'
related_name='locations'
)
parent = TreeForeignKey(
to='self',
@ -81,7 +79,7 @@ class RackGroup(NestedGroupModel):
]
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):
return (
@ -95,9 +93,9 @@ class RackGroup(NestedGroupModel):
def clean(self):
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:
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')
@ -147,7 +145,7 @@ class RackRole(OrganizationalModel):
class Rack(PrimaryModel):
"""
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(
max_length=100
@ -169,13 +167,12 @@ class Rack(PrimaryModel):
on_delete=models.PROTECT,
related_name='racks'
)
group = models.ForeignKey(
to='dcim.RackGroup',
location = models.ForeignKey(
to='dcim.Location',
on_delete=models.SET_NULL,
related_name='racks',
blank=True,
null=True,
help_text='Assigned group'
null=True
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
@ -259,20 +256,20 @@ class Rack(PrimaryModel):
objects = RestrictedQuerySet.as_manager()
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',
]
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',
]
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 = (
# Name and facility_id must be unique *only* within a RackGroup
('group', 'name'),
('group', 'facility_id'),
# Name and facility_id must be unique *only* within a Location
('location', 'name'),
('location', 'facility_id'),
)
def __str__(self):
@ -284,9 +281,9 @@ class Rack(PrimaryModel):
def clean(self):
super().clean()
# Validate group/site assignment
if self.site and self.group and self.group.site != self.site:
raise ValidationError(f"Assigned rack group must belong to parent site ({self.site}).")
# Validate location/site assignment
if self.site and self.location and self.location.site != self.site:
raise ValidationError(f"Assigned location must belong to parent site ({self.site}).")
# Validate outer dimensions and 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
)
})
# Validate that Rack was assigned a group of its same site, if applicable
if self.group:
if self.group.site != self.site:
# Validate that Rack was assigned a Location of its same site, if applicable
if self.location:
if self.location.site != self.site:
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):
return (
self.site.name,
self.group.name if self.group else None,
self.location.name if self.location else None,
self.name,
self.facility_id,
self.tenant.name if self.tenant else None,
@ -565,7 +562,7 @@ class RackReservation(PrimaryModel):
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:
ordering = ['created', 'pk']
@ -606,7 +603,7 @@ class RackReservation(PrimaryModel):
def to_csv(self):
return (
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,
','.join([str(u) for u in self.units]),
self.tenant.name if self.tenant else None,

View File

@ -7,7 +7,7 @@ from django.db import transaction
from django.dispatch import receiver
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):
@ -40,20 +40,20 @@ def rebuild_paths(obj):
# Site/rack/device assignment
#
@receiver(post_save, sender=RackGroup)
def handle_rackgroup_site_change(instance, created, **kwargs):
@receiver(post_save, sender=Location)
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.
"""
if not created:
for rackgroup in instance.get_children():
rackgroup.site = instance.site
rackgroup.save()
for rack in Rack.objects.filter(group=instance).exclude(site=instance.site):
for location in instance.get_children():
location.site = instance.site
location.save()
for rack in Rack.objects.filter(location=instance).exclude(site=instance.site):
rack.site = instance.site
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.save()

View File

@ -33,8 +33,8 @@ class PowerPanelTable(BaseTable):
class Meta(BaseTable.Meta):
model = PowerPanel
fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count', 'tags')
default_columns = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
fields = ('pk', 'name', 'site', 'location', 'powerfeed_count', 'tags')
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
#

View File

@ -1,18 +1,18 @@
import django_tables2 as tables
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 utilities.tables import (
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, TagColumn,
ToggleColumn,
)
from .template_code import MPTT_LINK, RACKGROUP_ELEVATIONS, UTILIZATION_GRAPH
from .template_code import MPTT_LINK, LOCATION_ELEVATIONS, UTILIZATION_GRAPH
__all__ = (
'RackTable',
'RackDetailTable',
'RackGroupTable',
'LocationTable',
'RackReservationTable',
'RackRoleTable',
)
@ -22,7 +22,7 @@ __all__ = (
# Rack groups
#
class RackGroupTable(BaseTable):
class LocationTable(BaseTable):
pk = ToggleColumn()
name = tables.TemplateColumn(
template_code=MPTT_LINK,
@ -36,12 +36,12 @@ class RackGroupTable(BaseTable):
verbose_name='Racks'
)
actions = ButtonsColumn(
model=RackGroup,
prepend_template=RACKGROUP_ELEVATIONS
model=Location,
prepend_template=LOCATION_ELEVATIONS
)
class Meta(BaseTable.Meta):
model = RackGroup
model = Location
fields = ('pk', 'name', 'site', 'rack_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'site', 'rack_count', 'description', 'actions')

View File

@ -76,8 +76,8 @@ POWERFEED_CABLETERMINATION = """
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
"""
RACKGROUP_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">
LOCATION_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>
</a>
"""

View File

@ -8,7 +8,7 @@ from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
from ipam.models import VLAN
from utilities.testing import APITestCase, APIViewTestCases
@ -135,8 +135,8 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
]
class RackGroupTest(APIViewTestCases.APIViewTestCase):
model = RackGroup
class LocationTest(APIViewTestCases.APIViewTestCase):
model = Location
brief_fields = ['_depth', 'id', 'name', 'rack_count', 'slug', 'url']
bulk_update_data = {
'description': 'New description',
@ -151,33 +151,33 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase):
)
Site.objects.bulk_create(sites)
parent_rack_groups = (
RackGroup.objects.create(site=sites[0], name='Parent Rack Group 1', slug='parent-rack-group-1'),
RackGroup.objects.create(site=sites[1], name='Parent Rack Group 2', slug='parent-rack-group-2'),
parent_locations = (
Location.objects.create(site=sites[0], name='Parent Location 1', slug='parent-location-1'),
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])
RackGroup.objects.create(site=sites[0], name='Rack Group 2', slug='rack-group-2', parent=parent_rack_groups[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 1', slug='location-1', parent=parent_locations[0])
Location.objects.create(site=sites[0], name='Location 2', slug='location-2', parent=parent_locations[0])
Location.objects.create(site=sites[0], name='Location 3', slug='location-3', parent=parent_locations[0])
cls.create_data = [
{
'name': 'Test Rack Group 4',
'slug': 'test-rack-group-4',
'name': 'Test Location 4',
'slug': 'test-location-4',
'site': sites[1].pk,
'parent': parent_rack_groups[1].pk,
'parent': parent_locations[1].pk,
},
{
'name': 'Test Rack Group 5',
'slug': 'test-rack-group-5',
'name': 'Test Location 5',
'slug': 'test-location-5',
'site': sites[1].pk,
'parent': parent_rack_groups[1].pk,
'parent': parent_locations[1].pk,
},
{
'name': 'Test Rack Group 6',
'slug': 'test-rack-group-6',
'name': 'Test Location 6',
'slug': 'test-location-6',
'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)
rack_groups = (
RackGroup.objects.create(site=sites[0], name='Rack Group 1', slug='rack-group-1'),
RackGroup.objects.create(site=sites[1], name='Rack Group 2', slug='rack-group-2'),
locations = (
Location.objects.create(site=sites[0], name='Location 1', slug='location-1'),
Location.objects.create(site=sites[1], name='Location 2', slug='location-2'),
)
rack_roles = (
@ -245,9 +245,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
RackRole.objects.bulk_create(rack_roles)
racks = (
Rack(site=sites[0], group=rack_groups[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], group=rack_groups[0], role=rack_roles[0], name='Rack 3'),
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 1'),
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 2'),
Rack(site=sites[0], location=locations[0], role=rack_roles[0], name='Rack 3'),
)
Rack.objects.bulk_create(racks)
@ -255,19 +255,19 @@ class RackTest(APIViewTestCases.APIViewTestCase):
{
'name': 'Test Rack 4',
'site': sites[1].pk,
'group': rack_groups[1].pk,
'location': locations[1].pk,
'role': rack_roles[1].pk,
},
{
'name': 'Test Rack 5',
'site': sites[1].pk,
'group': rack_groups[1].pk,
'location': locations[1].pk,
'role': rack_roles[1].pk,
},
{
'name': 'Test Rack 6',
'site': sites[1].pk,
'group': rack_groups[1].pk,
'location': locations[1].pk,
'role': rack_roles[1].pk,
},
]
@ -1588,17 +1588,17 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
Site.objects.create(name='Site 2', slug='site-2'),
)
rack_groups = (
RackGroup.objects.create(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup.objects.create(name='Rack Group 2', slug='rack-group-2', site=sites[0]),
RackGroup.objects.create(name='Rack Group 3', slug='rack-group-3', site=sites[0]),
RackGroup.objects.create(name='Rack Group 4', slug='rack-group-3', site=sites[1]),
locations = (
Location.objects.create(name='Location 1', slug='location-1', site=sites[0]),
Location.objects.create(name='Location 2', slug='location-2', site=sites[0]),
Location.objects.create(name='Location 3', slug='location-3', site=sites[0]),
Location.objects.create(name='Location 4', slug='location-3', site=sites[1]),
)
power_panels = (
PowerPanel(site=sites[0], rack_group=rack_groups[0], name='Power Panel 1'),
PowerPanel(site=sites[0], rack_group=rack_groups[1], name='Power Panel 2'),
PowerPanel(site=sites[0], rack_group=rack_groups[2], name='Power Panel 3'),
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 1'),
PowerPanel(site=sites[0], location=locations[1], name='Power Panel 2'),
PowerPanel(site=sites[0], location=locations[2], name='Power Panel 3'),
)
PowerPanel.objects.bulk_create(power_panels)
@ -1606,23 +1606,23 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
{
'name': 'Power Panel 4',
'site': sites[0].pk,
'rack_group': rack_groups[0].pk,
'location': locations[0].pk,
},
{
'name': 'Power Panel 5',
'site': sites[0].pk,
'rack_group': rack_groups[1].pk,
'location': locations[1].pk,
},
{
'name': 'Power Panel 6',
'site': sites[0].pk,
'rack_group': rack_groups[2].pk,
'location': locations[2].pk,
},
]
cls.bulk_update_data = {
'site': sites[1].pk,
'rack_group': rack_groups[3].pk
'location': locations[3].pk
}
@ -1636,20 +1636,20 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
@classmethod
def setUpTestData(cls):
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')
racks = (
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 1'),
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 2'),
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 3'),
Rack(site=site, group=rackgroup, role=rackrole, name='Rack 4'),
Rack(site=site, location=location, role=rackrole, name='Rack 1'),
Rack(site=site, location=location, role=rackrole, name='Rack 2'),
Rack(site=site, location=location, role=rackrole, name='Rack 3'),
Rack(site=site, location=location, role=rackrole, name='Rack 4'),
)
Rack.objects.bulk_create(racks)
power_panels = (
PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 1'),
PowerPanel(site=site, rack_group=rackgroup, name='Power Panel 2'),
PowerPanel(site=site, location=location, name='Power Panel 1'),
PowerPanel(site=site, location=location, name='Power Panel 2'),
)
PowerPanel.objects.bulk_create(power_panels)

View File

@ -7,7 +7,7 @@ from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
PowerOutletTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
from ipam.models import IPAddress
@ -168,9 +168,9 @@ class SiteTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RackGroupTestCase(TestCase):
queryset = RackGroup.objects.all()
filterset = RackGroupFilterSet
class LocationTestCase(TestCase):
queryset = Location.objects.all()
filterset = LocationFilterSet
@classmethod
def setUpTestData(cls):
@ -190,32 +190,32 @@ class RackGroupTestCase(TestCase):
)
Site.objects.bulk_create(sites)
parent_rack_groups = (
RackGroup(name='Parent Rack Group 1', slug='parent-rack-group-1', site=sites[0]),
RackGroup(name='Parent Rack Group 2', slug='parent-rack-group-2', site=sites[1]),
RackGroup(name='Parent Rack Group 3', slug='parent-rack-group-3', site=sites[2]),
parent_locations = (
Location(name='Parent Location 1', slug='parent-location-1', site=sites[0]),
Location(name='Parent Location 2', slug='parent-location-2', site=sites[1]),
Location(name='Parent Location 3', slug='parent-location-3', site=sites[2]),
)
for rackgroup in parent_rack_groups:
rackgroup.save()
for location in parent_locations:
location.save()
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0], parent=parent_rack_groups[0], description='A'),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1], parent=parent_rack_groups[1], description='B'),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2], parent=parent_rack_groups[2], description='C'),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0], parent=parent_locations[0], description='A'),
Location(name='Location 2', slug='location-2', site=sites[1], parent=parent_locations[1], description='B'),
Location(name='Location 3', slug='location-3', site=sites[2], parent=parent_locations[2], description='C'),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
def test_id(self):
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
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)
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)
def test_description(self):
@ -237,7 +237,7 @@ class RackGroupTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
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]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
@ -297,13 +297,13 @@ class RackTestCase(TestCase):
)
Site.objects.bulk_create(sites)
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1]),
Location(name='Location 3', slug='location-3', site=sites[2]),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
rack_roles = (
RackRole(name='Rack Role 1', slug='rack-role-1'),
@ -328,9 +328,9 @@ class RackTestCase(TestCase):
Tenant.objects.bulk_create(tenants)
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 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 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 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], 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], 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)
@ -395,11 +395,11 @@ class RackTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_group(self):
groups = RackGroup.objects.all()[:2]
params = {'group_id': [groups[0].pk, groups[1].pk]}
def test_location(self):
locations = Location.objects.all()[:2]
params = {'location_id': [locations[0].pk, locations[1].pk]}
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)
def test_status(self):
@ -448,18 +448,18 @@ class RackReservationTestCase(TestCase):
)
Site.objects.bulk_create(sites)
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1]),
Location(name='Location 3', slug='location-3', site=sites[2]),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
racks = (
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
Rack(name='Rack 1', site=sites[0], location=locations[0]),
Rack(name='Rack 2', site=sites[1], location=locations[1]),
Rack(name='Rack 3', site=sites[2], location=locations[2]),
)
Rack.objects.bulk_create(racks)
@ -503,11 +503,11 @@ class RackReservationTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_group(self):
groups = RackGroup.objects.all()[:2]
params = {'group_id': [groups[0].pk, groups[1].pk]}
def test_location(self):
locations = Location.objects.all()[:2]
params = {'location_id': [locations[0].pk, locations[1].pk]}
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)
def test_user(self):
@ -1168,18 +1168,18 @@ class DeviceTestCase(TestCase):
)
Site.objects.bulk_create(sites)
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1]),
Location(name='Location 3', slug='location-3', site=sites[2]),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
racks = (
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
Rack(name='Rack 1', site=sites[0], location=locations[0]),
Rack(name='Rack 2', site=sites[1], location=locations[1]),
Rack(name='Rack 3', site=sites[2], location=locations[2]),
)
Rack.objects.bulk_create(racks)
@ -1331,9 +1331,9 @@ class DeviceTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rackgroup(self):
rack_groups = RackGroup.objects.all()[:2]
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
def test_location(self):
locations = Location.objects.all()[:2]
params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rack(self):
@ -2589,18 +2589,18 @@ class PowerPanelTestCase(TestCase):
)
Site.objects.bulk_create(sites)
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1]),
Location(name='Location 3', slug='location-3', site=sites[2]),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
power_panels = (
PowerPanel(name='Power Panel 1', site=sites[0], rack_group=rack_groups[0]),
PowerPanel(name='Power Panel 2', site=sites[1], rack_group=rack_groups[1]),
PowerPanel(name='Power Panel 3', site=sites[2], rack_group=rack_groups[2]),
PowerPanel(name='Power Panel 1', site=sites[0], location=locations[0]),
PowerPanel(name='Power Panel 2', site=sites[1], location=locations[1]),
PowerPanel(name='Power Panel 3', site=sites[2], location=locations[2]),
)
PowerPanel.objects.bulk_create(power_panels)
@ -2626,9 +2626,9 @@ class PowerPanelTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rack_group(self):
rack_groups = RackGroup.objects.all()[:2]
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
def test_location(self):
locations = Location.objects.all()[:2]
params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -7,37 +7,37 @@ from dcim.models import *
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
- RackGroup A1
- RackGroup A2
- Location A1
- Location A2
- Rack 2
- Rack 1
"""
site_a = Site.objects.create(name='Site A', slug='site-a')
site_b = Site.objects.create(name='Site B', slug='site-b')
rackgroup_a1 = RackGroup(site=site_a, name='RackGroup A1', slug='rackgroup-a1')
rackgroup_a1.save()
rackgroup_a2 = RackGroup(site=site_a, parent=rackgroup_a1, name='RackGroup A2', slug='rackgroup-a2')
rackgroup_a2.save()
location_a1 = Location(site=site_a, name='Location A1', slug='location-a1')
location_a1.save()
location_a2 = Location(site=site_a, parent=location_a1, name='Location A2', slug='location-a2')
location_a2.save()
rack1 = Rack.objects.create(site=site_a, group=rackgroup_a1, name='Rack 1')
rack2 = Rack.objects.create(site=site_a, group=rackgroup_a2, name='Rack 2')
rack1 = Rack.objects.create(site=site_a, location=location_a1, name='Rack 1')
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
rackgroup_a1.site = site_b
rackgroup_a1.save()
# Move Location A1 to Site B
location_a1.site = site_b
location_a1.save()
# Check that all objects within RackGroup A1 now belong to Site B
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a1.pk).site, site_b)
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a2.pk).site, site_b)
# Check that all objects within Location A1 now belong to Site B
self.assertEqual(Location.objects.get(pk=location_a1.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=rack2.pk).site, site_b)
self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b)
@ -55,12 +55,12 @@ class RackTestCase(TestCase):
name='TestSite2',
slug='test-site-2'
)
self.group1 = RackGroup.objects.create(
self.location1 = Location.objects.create(
name='TestGroup1',
slug='test-group-1',
site=self.site1
)
self.group2 = RackGroup.objects.create(
self.location2 = Location.objects.create(
name='TestGroup2',
slug='test-group-2',
site=self.site2
@ -69,7 +69,7 @@ class RackTestCase(TestCase):
name='TestRack1',
facility_id='A101',
site=self.site1,
group=self.group1,
location=self.location1,
u_height=42
)
self.manufacturer = Manufacturer.objects.create(
@ -134,19 +134,19 @@ class RackTestCase(TestCase):
with self.assertRaises(ValidationError):
rack1.clean()
def test_rack_group_site(self):
def test_location_site(self):
rack_invalid_group = Rack(
rack_invalid_location = Rack(
name='TestRack2',
facility_id='A102',
site=self.site1,
u_height=42,
group=self.group2
location=self.location2
)
rack_invalid_group.save()
rack_invalid_location.save()
with self.assertRaises(ValidationError):
rack_invalid_group.clean()
rack_invalid_location.clean()
def test_mount_single_device(self):

View File

@ -117,8 +117,8 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
model = RackGroup
class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
model = Location
@classmethod
def setUpTestData(cls):
@ -126,26 +126,26 @@ class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
site = Site(name='Site 1', slug='site-1')
site.save()
rack_groups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=site),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=site),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=site),
locations = (
Location(name='Location 1', slug='location-1', site=site),
Location(name='Location 2', slug='location-2', site=site),
Location(name='Location 3', slug='location-3', site=site),
)
for rackgroup in rack_groups:
rackgroup.save()
for location in locations:
location.save()
cls.form_data = {
'name': 'Rack Group X',
'slug': 'rack-group-x',
'name': 'Location X',
'slug': 'location-x',
'site': site.pk,
'description': 'A new rack group',
'description': 'A new location',
}
cls.csv_data = (
"site,name,slug,description",
"Site 1,Rack Group 4,rack-group-4,Fourth rack group",
"Site 1,Rack Group 5,rack-group-5,Fifth rack group",
"Site 1,Rack Group 6,rack-group-6,Sixth rack group",
"Site 1,Location 4,location-4,Fourth location",
"Site 1,Location 5,location-5,Fifth location",
"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')
rack_group = RackGroup(name='Rack Group 1', slug='rack-group-1', site=site)
rack_group.save()
location = Location(name='Location 1', slug='location-1', site=site)
location.save()
rack = Rack(name='Rack 1', site=site, group=rack_group)
rack = Rack(name='Rack 1', site=site, location=location)
rack.save()
RackReservation.objects.bulk_create([
@ -211,10 +211,10 @@ class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
cls.csv_data = (
'site,rack_group,rack,units,description',
'Site 1,Rack Group 1,Rack 1,"10,11,12",Reservation 1',
'Site 1,Rack Group 1,Rack 1,"13,14,15",Reservation 2',
'Site 1,Rack Group 1,Rack 1,"16,17,18",Reservation 3',
'site,location,rack,units,description',
'Site 1,Location 1,Rack 1,"10,11,12",Reservation 1',
'Site 1,Location 1,Rack 1,"13,14,15",Reservation 2',
'Site 1,Location 1,Rack 1,"16,17,18",Reservation 3',
)
cls.bulk_edit_data = {
@ -236,12 +236,12 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
Site.objects.bulk_create(sites)
rackgroups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1])
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1])
)
for rackgroup in rackgroups:
rackgroup.save()
for location in locations:
location.save()
rackroles = (
RackRole(name='Rack Role 1', slug='rack-role-1'),
@ -261,7 +261,7 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'name': 'Rack X',
'facility_id': 'Facility X',
'site': sites[1].pk,
'group': rackgroups[1].pk,
'location': locations[1].pk,
'tenant': None,
'status': RackStatusChoices.STATUS_PLANNED,
'role': rackroles[1].pk,
@ -279,15 +279,15 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
cls.csv_data = (
"site,group,name,width,u_height",
"site,location,name,width,u_height",
"Site 1,,Rack 4,19,42",
"Site 1,Rack Group 1,Rack 5,19,42",
"Site 2,Rack Group 2,Rack 6,19,42",
"Site 1,Location 1,Rack 5,19,42",
"Site 2,Location 2,Rack 6,19,42",
)
cls.bulk_edit_data = {
'site': sites[1].pk,
'group': rackgroups[1].pk,
'location': locations[1].pk,
'tenant': None,
'status': RackStatusChoices.STATUS_DEPRECATED,
'role': rackroles[1].pk,
@ -929,11 +929,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
Site.objects.bulk_create(sites)
rack_group = RackGroup(site=sites[0], name='Rack Group 1', slug='rack-group-1')
rack_group.save()
location = Location(site=sites[0], name='Location 1', slug='location-1')
location.save()
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.objects.bulk_create(racks)
@ -991,10 +991,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
cls.csv_data = (
"device_role,manufacturer,device_type,status,name,site,rack_group,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 5,Site 1,Rack Group 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,manufacturer,device_type,status,name,site,location,rack,position,face",
"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,Location 1,Rack 1,20,front",
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 6,Site 1,Location 1,Rack 1,30,front",
)
cls.bulk_edit_data = {
@ -1771,38 +1771,38 @@ class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
Site.objects.bulk_create(sites)
rackgroups = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
locations = (
Location(name='Location 1', slug='location-1', site=sites[0]),
Location(name='Location 2', slug='location-2', site=sites[1]),
)
for rackgroup in rackgroups:
rackgroup.save()
for location in locations:
location.save()
PowerPanel.objects.bulk_create((
PowerPanel(site=sites[0], rack_group=rackgroups[0], name='Power Panel 1'),
PowerPanel(site=sites[0], rack_group=rackgroups[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 1'),
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 2'),
PowerPanel(site=sites[0], location=locations[0], name='Power Panel 3'),
))
tags = cls.create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'site': sites[1].pk,
'rack_group': rackgroups[1].pk,
'location': locations[1].pk,
'name': 'Power Panel X',
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"site,rack_group,name",
"Site 1,Rack Group 1,Power Panel 4",
"Site 1,Rack Group 1,Power Panel 5",
"Site 1,Rack Group 1,Power Panel 6",
"site,location,name",
"Site 1,Location 1,Power Panel 4",
"Site 1,Location 1,Power Panel 5",
"Site 1,Location 1,Power Panel 6",
)
cls.bulk_edit_data = {
'site': sites[1].pk,
'rack_group': rackgroups[1].pk,
'location': locations[1].pk,
}

View File

@ -5,7 +5,7 @@ from ipam.views import ServiceEditView
from . import views
from .models import (
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,
)
@ -33,14 +33,14 @@ urlpatterns = [
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}),
# Rack groups
path('rack-groups/', views.RackGroupListView.as_view(), name='rackgroup_list'),
path('rack-groups/add/', views.RackGroupEditView.as_view(), name='rackgroup_add'),
path('rack-groups/import/', views.RackGroupBulkImportView.as_view(), name='rackgroup_import'),
path('rack-groups/delete/', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
path('rack-groups/<int:pk>/edit/', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
path('rack-groups/<int:pk>/delete/', views.RackGroupDeleteView.as_view(), name='rackgroup_delete'),
path('rack-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rackgroup_changelog', kwargs={'model': RackGroup}),
# Locations
path('locations/', views.LocationListView.as_view(), name='location_list'),
path('locations/add/', views.LocationEditView.as_view(), name='location_add'),
path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
# Rack roles
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),

View File

@ -30,7 +30,7 @@ from .models import (
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
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,
)
@ -161,17 +161,17 @@ class SiteView(generic.ObjectView):
'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(),
}
rack_groups = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
locations = Location.objects.add_related_count(
Location.objects.all(),
Rack,
'group',
'location',
'rack_count',
cumulative=True
).restrict(request.user, 'view').filter(site=instance)
return {
'stats': stats,
'rack_groups': rack_groups,
'locations': locations,
}
@ -207,44 +207,44 @@ class SiteBulkDeleteView(generic.BulkDeleteView):
# Rack groups
#
class RackGroupListView(generic.ObjectListView):
queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
class LocationListView(generic.ObjectListView):
queryset = Location.objects.add_related_count(
Location.objects.all(),
Rack,
'group',
'location',
'rack_count',
cumulative=True
)
filterset = filters.RackGroupFilterSet
filterset_form = forms.RackGroupFilterForm
table = tables.RackGroupTable
filterset = filters.LocationFilterSet
filterset_form = forms.LocationFilterForm
table = tables.LocationTable
class RackGroupEditView(generic.ObjectEditView):
queryset = RackGroup.objects.all()
model_form = forms.RackGroupForm
class LocationEditView(generic.ObjectEditView):
queryset = Location.objects.all()
model_form = forms.LocationForm
class RackGroupDeleteView(generic.ObjectDeleteView):
queryset = RackGroup.objects.all()
class LocationDeleteView(generic.ObjectDeleteView):
queryset = Location.objects.all()
class RackGroupBulkImportView(generic.BulkImportView):
queryset = RackGroup.objects.all()
model_form = forms.RackGroupCSVForm
table = tables.RackGroupTable
class LocationBulkImportView(generic.BulkImportView):
queryset = Location.objects.all()
model_form = forms.LocationCSVForm
table = tables.LocationTable
class RackGroupBulkDeleteView(generic.BulkDeleteView):
queryset = RackGroup.objects.add_related_count(
RackGroup.objects.all(),
class LocationBulkDeleteView(generic.BulkDeleteView):
queryset = Location.objects.add_related_count(
Location.objects.all(),
Rack,
'group',
'location',
'rack_count',
cumulative=True
).prefetch_related('site')
filterset = filters.RackGroupFilterSet
table = tables.RackGroupTable
filterset = filters.LocationFilterSet
table = tables.LocationTable
#
@ -286,7 +286,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
class RackListView(generic.ObjectListView):
queryset = Rack.objects.prefetch_related(
'site', 'group', 'tenant', 'role', 'devices__device_type'
'site', 'location', 'tenant', 'role', 'devices__device_type'
).annotate(
device_count=count_related(Device, 'rack')
)
@ -338,7 +338,7 @@ class RackElevationListView(generic.ObjectListView):
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):
# 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)
if instance.group:
peer_racks = peer_racks.filter(group=instance.group)
if instance.location:
peer_racks = peer_racks.filter(location=instance.location)
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()
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):
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
filterset = filters.RackFilterSet
table = tables.RackTable
form = forms.RackBulkEditForm
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
table = tables.RackTable
@ -982,7 +982,7 @@ class DeviceListView(generic.ObjectListView):
class DeviceView(generic.ObjectView):
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):
@ -2560,7 +2560,7 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
class PowerPanelListView(generic.ObjectListView):
queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group'
'site', 'location'
).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel')
)
@ -2570,7 +2570,7 @@ class PowerPanelListView(generic.ObjectListView):
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):
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):
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
queryset = PowerPanel.objects.prefetch_related('site', 'location')
filterset = filters.PowerPanelFilterSet
table = tables.PowerPanelTable
form = forms.PowerPanelBulkEditForm
@ -2609,7 +2609,7 @@ class PowerPanelBulkEditView(generic.BulkEditView):
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group'
'site', 'location'
).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel')
)

View File

@ -9,7 +9,7 @@ from django_rq.queues import get_connection
from rest_framework import status
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.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag
from extras.reports import Report
@ -382,13 +382,13 @@ class CreatedUpdatedFilterTest(APITestCase):
super().setUp()
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.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(
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

View File

@ -6,12 +6,12 @@ from circuits.filters import CircuitFilterSet, ProviderFilterSet
from circuits.models import Circuit, Provider
from circuits.tables import CircuitTable, ProviderTable
from dcim.filters import (
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, RackGroupFilterSet,
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
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 (
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable,
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
VirtualChassisTable,
)
from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
@ -55,22 +55,22 @@ SEARCH_TYPES = OrderedDict((
'url': 'dcim:site_list',
}),
('rack', {
'queryset': Rack.objects.prefetch_related('site', 'group', 'tenant', 'role'),
'queryset': Rack.objects.prefetch_related('site', 'location', 'tenant', 'role'),
'filterset': RackFilterSet,
'table': RackTable,
'url': 'dcim:rack_list',
}),
('rackgroup', {
'queryset': RackGroup.objects.add_related_count(
RackGroup.objects.all(),
('location', {
'queryset': Location.objects.add_related_count(
Location.objects.all(),
Rack,
'group',
'location',
'rack_count',
cumulative=True
).prefetch_related('site'),
'filterset': RackGroupFilterSet,
'table': RackGroupTable,
'url': 'dcim:rackgroup_list',
'filterset': LocationFilterSet,
'table': LocationTable,
'url': 'dcim:location_list',
}),
('devicetype', {
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(

View File

@ -11,7 +11,7 @@ OBJ_TYPE_CHOICES = (
('DCIM', (
('site', 'Sites'),
('rack', 'Racks'),
('rackgroup', 'Rack Groups'),
('location', 'Locations'),
('devicetype', 'Device types'),
('device', 'Devices'),
('virtualchassis', 'Virtual Chassis'),

View File

@ -428,7 +428,7 @@ CACHEOPS = {
'circuits.*': {'ops': 'all'},
'dcim.inventoryitem': 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'},
'ipam.*': {'ops': 'all'},
'extras.*': {'ops': 'all'},

View File

@ -123,8 +123,8 @@
{% if 'termination_b_site' in form.fields %}
{% render_field form.termination_b_site %}
{% endif %}
{% if 'termination_b_rackgroup' in form.fields %}
{% render_field form.termination_b_rackgroup %}
{% if 'termination_b_location' in form.fields %}
{% render_field form.termination_b_location %}
{% endif %}
{% if 'termination_b_rack' in form.fields %}
{% render_field form.termination_b_rack %}

View File

@ -24,7 +24,7 @@
<div class="panel-body">
{% render_field form.region %}
{% render_field form.site %}
{% render_field form.rack_group %}
{% render_field form.location %}
{% render_field form.rack %}
{% if obj.device_type.is_child_device and obj.parent_bay %}
<div class="form-group">

View File

@ -5,8 +5,8 @@
{% block breadcrumbs %}
<li><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></li>
<li><a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a></li>
{% if object.rack_group %}
<li><a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a></li>
{% if object.location %}
<li><a href="{{ object.location.get_absolute_url }}">{{ object.location }}</a></li>
{% endif %}
<li>{{ object }}</li>
{% endblock %}
@ -26,10 +26,10 @@
</td>
</tr>
<tr>
<td>Rack Group</td>
<td>Location</td>
<td>
{% if object.rack_group %}
<a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a>
{% if object.location %}
<a href="{{ object.location.get_absolute_url }}">{{ object.location }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}

View File

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

View File

@ -7,7 +7,7 @@
<div class="panel-body">
{% render_field form.region %}
{% render_field form.site %}
{% render_field form.group %}
{% render_field form.location %}
{% render_field form.name %}
{% render_field form.status %}
{% render_field form.role %}

View File

@ -194,15 +194,17 @@
</div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Rack Groups</strong>
<strong>Locations</strong>
</div>
<table class="table table-hover panel-body">
{% for rg in rack_groups %}
{% for location in locations %}
<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>{{ rg.rack_count }}</td>
<td style="padding-left: {{ location.level }}8px">
<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">
<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>
</a>
</td>

View File

@ -49,14 +49,14 @@
{% endif %}
<a href="{% url 'dcim:rack_list' %}">Racks</a>
</li>
<li{% if not perms.dcim.view_rackgroup %} class="disabled"{% endif %}>
{% if perms.dcim.add_rackgroup %}
<li{% if not perms.dcim.view_location %} class="disabled"{% endif %}>
{% if perms.dcim.add_location %}
<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:rackgroup_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></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:location_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:rackgroup_list' %}">Rack Groups</a>
<a href="{% url 'dcim:location_list' %}">Locations</a>
</li>
<li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}>
{% if perms.dcim.add_rackrole %}