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 * [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
* [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields * [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields
* [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI * [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI
* [#5895](https://github.com/netbox-community/netbox/issues/5895) - Rename RackGroup to Location
* [#5901](https://github.com/netbox-community/netbox/issues/5901) - Add `created` and `last_updated` fields to device component models * [#5901](https://github.com/netbox-community/netbox/issues/5901) - Add `created` and `last_updated` fields to device component models
### Other Changes ### Other Changes
@ -39,5 +40,11 @@ In addition to the new `mark_connected` boolean field, the REST API representati
* All cable termination models (cabled device components, power feeds, and circuit terminations) * All cable termination models (cabled device components, power feeds, and circuit terminations)
* Added `mark_connected` boolean field to force connection status * Added `mark_connected` boolean field to force connection status
* Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied * Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied
* Renamed RackGroup to Location
* The `/dcim/rack-groups/` endpoint is now `/dcim/locations/`
* dcim.PowerPanel
* Renamed `rack_group` field to `location`
* dcim.Rack
* Renamed `group` field to `location`
* extras.CustomField * extras.CustomField
* Added new custom field type: `multi-select` * Added new custom field type: `multi-select`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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', 'PowerPort',
'PowerPortTemplate', 'PowerPortTemplate',
'Rack', 'Rack',
'RackGroup', 'Location',
'RackReservation', 'RackReservation',
'RackRole', 'RackRole',
'RearPort', 'RearPort',

View File

@ -600,7 +600,7 @@ class Device(PrimaryModel, ConfigContextModel):
csv_headers = [ csv_headers = [
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', 'site', 'location', 'rack_name', 'position', 'face', 'comments',
] ]
clone_fields = [ clone_fields = [
'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster', 'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster',
@ -799,7 +799,7 @@ class Device(PrimaryModel, ConfigContextModel):
self.asset_tag, self.asset_tag,
self.get_status_display(), self.get_status_display(),
self.site.name, self.site.name,
self.rack.group.name if self.rack and self.rack.group else None, self.rack.location.name if self.rack and self.rack.location else None,
self.rack.name if self.rack else None, self.rack.name if self.rack else None,
self.position, self.position,
self.get_face_display(), self.get_face_display(),

View File

@ -32,8 +32,8 @@ class PowerPanel(PrimaryModel):
to='Site', to='Site',
on_delete=models.PROTECT on_delete=models.PROTECT
) )
rack_group = models.ForeignKey( location = models.ForeignKey(
to='RackGroup', to='dcim.Location',
on_delete=models.PROTECT, on_delete=models.PROTECT,
blank=True, blank=True,
null=True null=True
@ -45,7 +45,7 @@ class PowerPanel(PrimaryModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = ['site', 'rack_group', 'name'] csv_headers = ['site', 'location', 'name']
class Meta: class Meta:
ordering = ['site', 'name'] ordering = ['site', 'name']
@ -60,17 +60,17 @@ class PowerPanel(PrimaryModel):
def to_csv(self): def to_csv(self):
return ( return (
self.site.name, self.site.name,
self.rack_group.name if self.rack_group else None, self.location.name if self.location else None,
self.name, self.name,
) )
def clean(self): def clean(self):
super().clean() super().clean()
# RackGroup must belong to assigned Site # Location must belong to assigned Site
if self.rack_group and self.rack_group.site != self.site: if self.location and self.location.site != self.site:
raise ValidationError("Rack group {} ({}) is in a different site than {}".format( raise ValidationError("Rack group {} ({}) is in a different site than {}".format(
self.rack_group, self.rack_group.site, self.site self.location, self.location.site, self.site
)) ))
@ -138,7 +138,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = [ csv_headers = [
'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
'voltage', 'amperage', 'max_utilization', 'comments', 'voltage', 'amperage', 'max_utilization', 'comments',
] ]
clone_fields = [ clone_fields = [
@ -160,7 +160,7 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
return ( return (
self.power_panel.site.name, self.power_panel.site.name,
self.power_panel.name, self.power_panel.name,
self.rack.group.name if self.rack and self.rack.group else None, self.rack.location.name if self.rack and self.rack.location else None,
self.rack.name if self.rack else None, self.rack.name if self.rack else None,
self.name, self.name,
self.get_status_display(), self.get_status_display(),

View File

@ -16,21 +16,20 @@ from taggit.managers import TaggableManager
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.elevations import RackElevationSVG from dcim.elevations import RackElevationSVG
from extras.models import ObjectChange, TaggedItem from extras.models import TaggedItem
from extras.utils import extras_features from extras.utils import extras_features
from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.mptt import TreeManager from utilities.utils import array_to_string
from utilities.utils import array_to_string, serialize_object
from .device_components import PowerOutlet, PowerPort from .device_components import PowerOutlet, PowerPort
from .devices import Device from .devices import Device
from .power import PowerFeed from .power import PowerFeed
__all__ = ( __all__ = (
'Rack', 'Rack',
'RackGroup', 'Location',
'RackReservation', 'RackReservation',
'RackRole', 'RackRole',
) )
@ -41,11 +40,10 @@ __all__ = (
# #
@extras_features('custom_fields', 'export_templates', 'webhooks') @extras_features('custom_fields', 'export_templates', 'webhooks')
class RackGroup(NestedGroupModel): class Location(NestedGroupModel):
""" """
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
example, if a Site spans a corporate campus, a RackGroup might be defined to represent each building within that site, or a room within a building, for example.
campus. If a Site instead represents a single building, a RackGroup might represent a single room or floor.
""" """
name = models.CharField( name = models.CharField(
max_length=100 max_length=100
@ -56,7 +54,7 @@ class RackGroup(NestedGroupModel):
site = models.ForeignKey( site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='rack_groups' related_name='locations'
) )
parent = TreeForeignKey( parent = TreeForeignKey(
to='self', to='self',
@ -81,7 +79,7 @@ class RackGroup(NestedGroupModel):
] ]
def get_absolute_url(self): def get_absolute_url(self):
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk) return "{}?location_id={}".format(reverse('dcim:rack_list'), self.pk)
def to_csv(self): def to_csv(self):
return ( return (
@ -95,9 +93,9 @@ class RackGroup(NestedGroupModel):
def clean(self): def clean(self):
super().clean() super().clean()
# Parent RackGroup (if any) must belong to the same Site # Parent Location (if any) must belong to the same Site
if self.parent and self.parent.site != self.site: if self.parent and self.parent.site != self.site:
raise ValidationError(f"Parent rack group ({self.parent}) must belong to the same site ({self.site})") raise ValidationError(f"Parent location ({self.parent}) must belong to the same site ({self.site})")
@extras_features('custom_fields', 'export_templates', 'webhooks') @extras_features('custom_fields', 'export_templates', 'webhooks')
@ -147,7 +145,7 @@ class RackRole(OrganizationalModel):
class Rack(PrimaryModel): class Rack(PrimaryModel):
""" """
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face. Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
Each Rack is assigned to a Site and (optionally) a RackGroup. Each Rack is assigned to a Site and (optionally) a Location.
""" """
name = models.CharField( name = models.CharField(
max_length=100 max_length=100
@ -169,13 +167,12 @@ class Rack(PrimaryModel):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='racks' related_name='racks'
) )
group = models.ForeignKey( location = models.ForeignKey(
to='dcim.RackGroup', to='dcim.Location',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name='racks', related_name='racks',
blank=True, blank=True,
null=True, null=True
help_text='Assigned group'
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -259,20 +256,20 @@ class Rack(PrimaryModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = [ csv_headers = [
'site', 'group', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width', 'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
] ]
clone_fields = [ clone_fields = [
'site', 'group', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'site', 'location', 'tenant', 'status', 'role', 'type', 'width', 'u_height', 'desc_units', 'outer_width',
'outer_depth', 'outer_unit', 'outer_depth', 'outer_unit',
] ]
class Meta: class Meta:
ordering = ('site', 'group', '_name', 'pk') # (site, group, name) may be non-unique ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
unique_together = ( unique_together = (
# Name and facility_id must be unique *only* within a RackGroup # Name and facility_id must be unique *only* within a Location
('group', 'name'), ('location', 'name'),
('group', 'facility_id'), ('location', 'facility_id'),
) )
def __str__(self): def __str__(self):
@ -284,9 +281,9 @@ class Rack(PrimaryModel):
def clean(self): def clean(self):
super().clean() super().clean()
# Validate group/site assignment # Validate location/site assignment
if self.site and self.group and self.group.site != self.site: if self.site and self.location and self.location.site != self.site:
raise ValidationError(f"Assigned rack group must belong to parent site ({self.site}).") raise ValidationError(f"Assigned location must belong to parent site ({self.site}).")
# Validate outer dimensions and unit # Validate outer dimensions and unit
if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit: if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
@ -309,17 +306,17 @@ class Rack(PrimaryModel):
min_height min_height
) )
}) })
# Validate that Rack was assigned a group of its same site, if applicable # Validate that Rack was assigned a Location of its same site, if applicable
if self.group: if self.location:
if self.group.site != self.site: if self.location.site != self.site:
raise ValidationError({ raise ValidationError({
'group': "Rack group must be from the same site, {}.".format(self.site) 'location': f"Location must be from the same site, {self.site}."
}) })
def to_csv(self): def to_csv(self):
return ( return (
self.site.name, self.site.name,
self.group.name if self.group else None, self.location.name if self.location else None,
self.name, self.name,
self.facility_id, self.facility_id,
self.tenant.name if self.tenant else None, self.tenant.name if self.tenant else None,
@ -565,7 +562,7 @@ class RackReservation(PrimaryModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = ['site', 'rack_group', 'rack', 'units', 'tenant', 'user', 'description'] csv_headers = ['site', 'location', 'rack', 'units', 'tenant', 'user', 'description']
class Meta: class Meta:
ordering = ['created', 'pk'] ordering = ['created', 'pk']
@ -606,7 +603,7 @@ class RackReservation(PrimaryModel):
def to_csv(self): def to_csv(self):
return ( return (
self.rack.site.name, self.rack.site.name,
self.rack.group if self.rack.group else None, self.rack.location if self.rack.location else None,
self.rack.name, self.rack.name,
','.join([str(u) for u in self.units]), ','.join([str(u) for u in self.units]),
self.tenant.name if self.tenant else None, self.tenant.name if self.tenant else None,

View File

@ -7,7 +7,7 @@ from django.db import transaction
from django.dispatch import receiver from django.dispatch import receiver
from .choices import CableStatusChoices from .choices import CableStatusChoices
from .models import Cable, CablePath, Device, PathEndpoint, PowerPanel, Rack, RackGroup, VirtualChassis from .models import Cable, CablePath, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
def create_cablepath(node): def create_cablepath(node):
@ -40,20 +40,20 @@ def rebuild_paths(obj):
# Site/rack/device assignment # Site/rack/device assignment
# #
@receiver(post_save, sender=RackGroup) @receiver(post_save, sender=Location)
def handle_rackgroup_site_change(instance, created, **kwargs): def handle_location_site_change(instance, created, **kwargs):
""" """
Update child RackGroups and Racks if Site assignment has changed. We intentionally recurse through each child Update child Locations and Racks if Site assignment has changed. We intentionally recurse through each child
object instead of calling update() on the QuerySet to ensure the proper change records get created for each. object instead of calling update() on the QuerySet to ensure the proper change records get created for each.
""" """
if not created: if not created:
for rackgroup in instance.get_children(): for location in instance.get_children():
rackgroup.site = instance.site location.site = instance.site
rackgroup.save() location.save()
for rack in Rack.objects.filter(group=instance).exclude(site=instance.site): for rack in Rack.objects.filter(location=instance).exclude(site=instance.site):
rack.site = instance.site rack.site = instance.site
rack.save() rack.save()
for powerpanel in PowerPanel.objects.filter(rack_group=instance).exclude(site=instance.site): for powerpanel in PowerPanel.objects.filter(location=instance).exclude(site=instance.site):
powerpanel.site = instance.site powerpanel.site = instance.site
powerpanel.save() powerpanel.save()

View File

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

View File

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

View File

@ -76,8 +76,8 @@ POWERFEED_CABLETERMINATION = """
<a href="{{ value.get_absolute_url }}">{{ value }}</a> <a href="{{ value.get_absolute_url }}">{{ value }}</a>
""" """
RACKGROUP_ELEVATIONS = """ LOCATION_ELEVATIONS = """
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&group_id={{ record.pk }}" class="btn btn-xs btn-primary" title="View elevations"> <a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&location_id={{ record.pk }}" class="btn btn-xs btn-primary" title="View elevations">
<i class="mdi mdi-server"></i> <i class="mdi mdi-server"></i>
</a> </a>
""" """

View File

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

View File

@ -7,7 +7,7 @@ from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet, InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, PowerOutletTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis, VirtualChassis,
) )
from ipam.models import IPAddress from ipam.models import IPAddress
@ -168,9 +168,9 @@ class SiteTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RackGroupTestCase(TestCase): class LocationTestCase(TestCase):
queryset = RackGroup.objects.all() queryset = Location.objects.all()
filterset = RackGroupFilterSet filterset = LocationFilterSet
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -190,32 +190,32 @@ class RackGroupTestCase(TestCase):
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
parent_rack_groups = ( parent_locations = (
RackGroup(name='Parent Rack Group 1', slug='parent-rack-group-1', site=sites[0]), Location(name='Parent Location 1', slug='parent-location-1', site=sites[0]),
RackGroup(name='Parent Rack Group 2', slug='parent-rack-group-2', site=sites[1]), Location(name='Parent Location 2', slug='parent-location-2', site=sites[1]),
RackGroup(name='Parent Rack Group 3', slug='parent-rack-group-3', site=sites[2]), Location(name='Parent Location 3', slug='parent-location-3', site=sites[2]),
) )
for rackgroup in parent_rack_groups: for location in parent_locations:
rackgroup.save() location.save()
rack_groups = ( locations = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0], parent=parent_rack_groups[0], description='A'), Location(name='Location 1', slug='location-1', site=sites[0], parent=parent_locations[0], description='A'),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1], parent=parent_rack_groups[1], description='B'), Location(name='Location 2', slug='location-2', site=sites[1], parent=parent_locations[1], description='B'),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2], parent=parent_rack_groups[2], description='C'), Location(name='Location 3', slug='location-3', site=sites[2], parent=parent_locations[2], description='C'),
) )
for rackgroup in rack_groups: for location in locations:
rackgroup.save() location.save()
def test_id(self): def test_id(self):
params = {'id': self.queryset.values_list('pk', flat=True)[:2]} params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_name(self): def test_name(self):
params = {'name': ['Rack Group 1', 'Rack Group 2']} params = {'name': ['Location 1', 'Location 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_slug(self): def test_slug(self):
params = {'slug': ['rack-group-1', 'rack-group-2']} params = {'slug': ['location-1', 'location-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self): def test_description(self):
@ -237,7 +237,7 @@ class RackGroupTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_parent(self): def test_parent(self):
parent_groups = RackGroup.objects.filter(name__startswith='Parent')[:2] parent_groups = Location.objects.filter(name__startswith='Parent')[:2]
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]} params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]} params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
@ -297,13 +297,13 @@ class RackTestCase(TestCase):
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
rack_groups = ( locations = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), Location(name='Location 2', slug='location-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), Location(name='Location 3', slug='location-3', site=sites[2]),
) )
for rackgroup in rack_groups: for location in locations:
rackgroup.save() location.save()
rack_roles = ( rack_roles = (
RackRole(name='Rack Role 1', slug='rack-role-1'), RackRole(name='Rack Role 1', slug='rack-role-1'),
@ -328,9 +328,9 @@ class RackTestCase(TestCase):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
racks = ( racks = (
Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER), Rack(name='Rack 1', facility_id='rack-1', site=sites[0], location=locations[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER), Rack(name='Rack 2', facility_id='rack-2', site=sites[1], location=locations[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_21IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH), Rack(name='Rack 3', facility_id='rack-3', site=sites[2], location=locations[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH),
) )
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
@ -395,11 +395,11 @@ class RackTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]} params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_group(self): def test_location(self):
groups = RackGroup.objects.all()[:2] locations = Location.objects.all()[:2]
params = {'group_id': [groups[0].pk, groups[1].pk]} params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'group': [groups[0].slug, groups[1].slug]} params = {'location': [locations[0].slug, locations[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_status(self): def test_status(self):
@ -448,18 +448,18 @@ class RackReservationTestCase(TestCase):
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
rack_groups = ( locations = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), Location(name='Location 2', slug='location-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), Location(name='Location 3', slug='location-3', site=sites[2]),
) )
for rackgroup in rack_groups: for location in locations:
rackgroup.save() location.save()
racks = ( racks = (
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]), Rack(name='Rack 1', site=sites[0], location=locations[0]),
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]), Rack(name='Rack 2', site=sites[1], location=locations[1]),
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]), Rack(name='Rack 3', site=sites[2], location=locations[2]),
) )
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
@ -503,11 +503,11 @@ class RackReservationTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]} params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_group(self): def test_location(self):
groups = RackGroup.objects.all()[:2] locations = Location.objects.all()[:2]
params = {'group_id': [groups[0].pk, groups[1].pk]} params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'group': [groups[0].slug, groups[1].slug]} params = {'location': [locations[0].slug, locations[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_user(self): def test_user(self):
@ -1168,18 +1168,18 @@ class DeviceTestCase(TestCase):
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
rack_groups = ( locations = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), Location(name='Location 2', slug='location-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), Location(name='Location 3', slug='location-3', site=sites[2]),
) )
for rackgroup in rack_groups: for location in locations:
rackgroup.save() location.save()
racks = ( racks = (
Rack(name='Rack 1', site=sites[0], group=rack_groups[0]), Rack(name='Rack 1', site=sites[0], location=locations[0]),
Rack(name='Rack 2', site=sites[1], group=rack_groups[1]), Rack(name='Rack 2', site=sites[1], location=locations[1]),
Rack(name='Rack 3', site=sites[2], group=rack_groups[2]), Rack(name='Rack 3', site=sites[2], location=locations[2]),
) )
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
@ -1331,9 +1331,9 @@ class DeviceTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]} params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rackgroup(self): def test_location(self):
rack_groups = RackGroup.objects.all()[:2] locations = Location.objects.all()[:2]
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rack(self): def test_rack(self):
@ -2589,18 +2589,18 @@ class PowerPanelTestCase(TestCase):
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
rack_groups = ( locations = (
RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]), Location(name='Location 2', slug='location-2', site=sites[1]),
RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]), Location(name='Location 3', slug='location-3', site=sites[2]),
) )
for rackgroup in rack_groups: for location in locations:
rackgroup.save() location.save()
power_panels = ( power_panels = (
PowerPanel(name='Power Panel 1', site=sites[0], rack_group=rack_groups[0]), PowerPanel(name='Power Panel 1', site=sites[0], location=locations[0]),
PowerPanel(name='Power Panel 2', site=sites[1], rack_group=rack_groups[1]), PowerPanel(name='Power Panel 2', site=sites[1], location=locations[1]),
PowerPanel(name='Power Panel 3', site=sites[2], rack_group=rack_groups[2]), PowerPanel(name='Power Panel 3', site=sites[2], location=locations[2]),
) )
PowerPanel.objects.bulk_create(power_panels) PowerPanel.objects.bulk_create(power_panels)
@ -2626,9 +2626,9 @@ class PowerPanelTestCase(TestCase):
params = {'site': [sites[0].slug, sites[1].slug]} params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_rack_group(self): def test_location(self):
rack_groups = RackGroup.objects.all()[:2] locations = Location.objects.all()[:2]
params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]} params = {'location_id': [locations[0].pk, locations[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -7,37 +7,37 @@ from dcim.models import *
from tenancy.models import Tenant from tenancy.models import Tenant
class RackGroupTestCase(TestCase): class LocationTestCase(TestCase):
def test_change_rackgroup_site(self): def test_change_location_site(self):
""" """
Check that all child RackGroups and Racks get updated when a RackGroup is moved to a new Site. Topology: Check that all child Locations and Racks get updated when a Location is moved to a new Site. Topology:
Site A Site A
- RackGroup A1 - Location A1
- RackGroup A2 - Location A2
- Rack 2 - Rack 2
- Rack 1 - Rack 1
""" """
site_a = Site.objects.create(name='Site A', slug='site-a') site_a = Site.objects.create(name='Site A', slug='site-a')
site_b = Site.objects.create(name='Site B', slug='site-b') site_b = Site.objects.create(name='Site B', slug='site-b')
rackgroup_a1 = RackGroup(site=site_a, name='RackGroup A1', slug='rackgroup-a1') location_a1 = Location(site=site_a, name='Location A1', slug='location-a1')
rackgroup_a1.save() location_a1.save()
rackgroup_a2 = RackGroup(site=site_a, parent=rackgroup_a1, name='RackGroup A2', slug='rackgroup-a2') location_a2 = Location(site=site_a, parent=location_a1, name='Location A2', slug='location-a2')
rackgroup_a2.save() location_a2.save()
rack1 = Rack.objects.create(site=site_a, group=rackgroup_a1, name='Rack 1') rack1 = Rack.objects.create(site=site_a, location=location_a1, name='Rack 1')
rack2 = Rack.objects.create(site=site_a, group=rackgroup_a2, name='Rack 2') rack2 = Rack.objects.create(site=site_a, location=location_a2, name='Rack 2')
powerpanel1 = PowerPanel.objects.create(site=site_a, rack_group=rackgroup_a1, name='Power Panel 1') powerpanel1 = PowerPanel.objects.create(site=site_a, location=location_a1, name='Power Panel 1')
# Move RackGroup A1 to Site B # Move Location A1 to Site B
rackgroup_a1.site = site_b location_a1.site = site_b
rackgroup_a1.save() location_a1.save()
# Check that all objects within RackGroup A1 now belong to Site B # Check that all objects within Location A1 now belong to Site B
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a1.pk).site, site_b) self.assertEqual(Location.objects.get(pk=location_a1.pk).site, site_b)
self.assertEqual(RackGroup.objects.get(pk=rackgroup_a2.pk).site, site_b) self.assertEqual(Location.objects.get(pk=location_a2.pk).site, site_b)
self.assertEqual(Rack.objects.get(pk=rack1.pk).site, site_b) self.assertEqual(Rack.objects.get(pk=rack1.pk).site, site_b)
self.assertEqual(Rack.objects.get(pk=rack2.pk).site, site_b) self.assertEqual(Rack.objects.get(pk=rack2.pk).site, site_b)
self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b) self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b)
@ -55,12 +55,12 @@ class RackTestCase(TestCase):
name='TestSite2', name='TestSite2',
slug='test-site-2' slug='test-site-2'
) )
self.group1 = RackGroup.objects.create( self.location1 = Location.objects.create(
name='TestGroup1', name='TestGroup1',
slug='test-group-1', slug='test-group-1',
site=self.site1 site=self.site1
) )
self.group2 = RackGroup.objects.create( self.location2 = Location.objects.create(
name='TestGroup2', name='TestGroup2',
slug='test-group-2', slug='test-group-2',
site=self.site2 site=self.site2
@ -69,7 +69,7 @@ class RackTestCase(TestCase):
name='TestRack1', name='TestRack1',
facility_id='A101', facility_id='A101',
site=self.site1, site=self.site1,
group=self.group1, location=self.location1,
u_height=42 u_height=42
) )
self.manufacturer = Manufacturer.objects.create( self.manufacturer = Manufacturer.objects.create(
@ -134,19 +134,19 @@ class RackTestCase(TestCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
rack1.clean() rack1.clean()
def test_rack_group_site(self): def test_location_site(self):
rack_invalid_group = Rack( rack_invalid_location = Rack(
name='TestRack2', name='TestRack2',
facility_id='A102', facility_id='A102',
site=self.site1, site=self.site1,
u_height=42, u_height=42,
group=self.group2 location=self.location2
) )
rack_invalid_group.save() rack_invalid_location.save()
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
rack_invalid_group.clean() rack_invalid_location.clean()
def test_mount_single_device(self): def test_mount_single_device(self):

View File

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

View File

@ -5,7 +5,7 @@ from ipam.views import ServiceEditView
from . import views from . import views
from .models import ( from .models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, FrontPort, Interface, Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, FrontPort, Interface,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup, InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, Location,
RackReservation, RackRole, RearPort, Region, Site, VirtualChassis, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
) )
@ -33,14 +33,14 @@ urlpatterns = [
path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}), path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
path('sites/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}), path('sites/<int:object_id>/images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}),
# Rack groups # Locations
path('rack-groups/', views.RackGroupListView.as_view(), name='rackgroup_list'), path('locations/', views.LocationListView.as_view(), name='location_list'),
path('rack-groups/add/', views.RackGroupEditView.as_view(), name='rackgroup_add'), path('locations/add/', views.LocationEditView.as_view(), name='location_add'),
path('rack-groups/import/', views.RackGroupBulkImportView.as_view(), name='rackgroup_import'), path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
path('rack-groups/delete/', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'), path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
path('rack-groups/<int:pk>/edit/', views.RackGroupEditView.as_view(), name='rackgroup_edit'), path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
path('rack-groups/<int:pk>/delete/', views.RackGroupDeleteView.as_view(), name='rackgroup_delete'), path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
path('rack-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rackgroup_changelog', kwargs={'model': RackGroup}), path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
# Rack roles # Rack roles
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'), path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),

View File

@ -30,7 +30,7 @@ from .models import (
Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, PathEndpoint, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, InventoryItem, Manufacturer, PathEndpoint, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel,
PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, PowerPort, PowerPortTemplate, Rack, Location, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis, VirtualChassis,
) )
@ -161,17 +161,17 @@ class SiteView(generic.ObjectView):
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(), 'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(),
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(), 'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
} }
rack_groups = RackGroup.objects.add_related_count( locations = Location.objects.add_related_count(
RackGroup.objects.all(), Location.objects.all(),
Rack, Rack,
'group', 'location',
'rack_count', 'rack_count',
cumulative=True cumulative=True
).restrict(request.user, 'view').filter(site=instance) ).restrict(request.user, 'view').filter(site=instance)
return { return {
'stats': stats, 'stats': stats,
'rack_groups': rack_groups, 'locations': locations,
} }
@ -207,44 +207,44 @@ class SiteBulkDeleteView(generic.BulkDeleteView):
# Rack groups # Rack groups
# #
class RackGroupListView(generic.ObjectListView): class LocationListView(generic.ObjectListView):
queryset = RackGroup.objects.add_related_count( queryset = Location.objects.add_related_count(
RackGroup.objects.all(), Location.objects.all(),
Rack, Rack,
'group', 'location',
'rack_count', 'rack_count',
cumulative=True cumulative=True
) )
filterset = filters.RackGroupFilterSet filterset = filters.LocationFilterSet
filterset_form = forms.RackGroupFilterForm filterset_form = forms.LocationFilterForm
table = tables.RackGroupTable table = tables.LocationTable
class RackGroupEditView(generic.ObjectEditView): class LocationEditView(generic.ObjectEditView):
queryset = RackGroup.objects.all() queryset = Location.objects.all()
model_form = forms.RackGroupForm model_form = forms.LocationForm
class RackGroupDeleteView(generic.ObjectDeleteView): class LocationDeleteView(generic.ObjectDeleteView):
queryset = RackGroup.objects.all() queryset = Location.objects.all()
class RackGroupBulkImportView(generic.BulkImportView): class LocationBulkImportView(generic.BulkImportView):
queryset = RackGroup.objects.all() queryset = Location.objects.all()
model_form = forms.RackGroupCSVForm model_form = forms.LocationCSVForm
table = tables.RackGroupTable table = tables.LocationTable
class RackGroupBulkDeleteView(generic.BulkDeleteView): class LocationBulkDeleteView(generic.BulkDeleteView):
queryset = RackGroup.objects.add_related_count( queryset = Location.objects.add_related_count(
RackGroup.objects.all(), Location.objects.all(),
Rack, Rack,
'group', 'location',
'rack_count', 'rack_count',
cumulative=True cumulative=True
).prefetch_related('site') ).prefetch_related('site')
filterset = filters.RackGroupFilterSet filterset = filters.LocationFilterSet
table = tables.RackGroupTable table = tables.LocationTable
# #
@ -286,7 +286,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
class RackListView(generic.ObjectListView): class RackListView(generic.ObjectListView):
queryset = Rack.objects.prefetch_related( queryset = Rack.objects.prefetch_related(
'site', 'group', 'tenant', 'role', 'devices__device_type' 'site', 'location', 'tenant', 'role', 'devices__device_type'
).annotate( ).annotate(
device_count=count_related(Device, 'rack') device_count=count_related(Device, 'rack')
) )
@ -338,7 +338,7 @@ class RackElevationListView(generic.ObjectListView):
class RackView(generic.ObjectView): class RackView(generic.ObjectView):
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role') queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
# Get 0U and child devices located within the rack # Get 0U and child devices located within the rack
@ -349,10 +349,10 @@ class RackView(generic.ObjectView):
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site) peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
if instance.group: if instance.location:
peer_racks = peer_racks.filter(group=instance.group) peer_racks = peer_racks.filter(location=instance.location)
else: else:
peer_racks = peer_racks.filter(group__isnull=True) peer_racks = peer_racks.filter(location__isnull=True)
next_rack = peer_racks.filter(name__gt=instance.name).order_by('name').first() next_rack = peer_racks.filter(name__gt=instance.name).order_by('name').first()
prev_rack = peer_racks.filter(name__lt=instance.name).order_by('-name').first() prev_rack = peer_racks.filter(name__lt=instance.name).order_by('-name').first()
@ -390,14 +390,14 @@ class RackBulkImportView(generic.BulkImportView):
class RackBulkEditView(generic.BulkEditView): class RackBulkEditView(generic.BulkEditView):
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role') queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
filterset = filters.RackFilterSet filterset = filters.RackFilterSet
table = tables.RackTable table = tables.RackTable
form = forms.RackBulkEditForm form = forms.RackBulkEditForm
class RackBulkDeleteView(generic.BulkDeleteView): class RackBulkDeleteView(generic.BulkDeleteView):
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role') queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
filterset = filters.RackFilterSet filterset = filters.RackFilterSet
table = tables.RackTable table = tables.RackTable
@ -982,7 +982,7 @@ class DeviceListView(generic.ObjectListView):
class DeviceView(generic.ObjectView): class DeviceView(generic.ObjectView):
queryset = Device.objects.prefetch_related( queryset = Device.objects.prefetch_related(
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6' 'site__region', 'rack__location', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -2560,7 +2560,7 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
class PowerPanelListView(generic.ObjectListView): class PowerPanelListView(generic.ObjectListView):
queryset = PowerPanel.objects.prefetch_related( queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group' 'site', 'location'
).annotate( ).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel') powerfeed_count=count_related(PowerFeed, 'power_panel')
) )
@ -2570,7 +2570,7 @@ class PowerPanelListView(generic.ObjectListView):
class PowerPanelView(generic.ObjectView): class PowerPanelView(generic.ObjectView):
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group') queryset = PowerPanel.objects.prefetch_related('site', 'location')
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=instance).prefetch_related('rack') power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=instance).prefetch_related('rack')
@ -2601,7 +2601,7 @@ class PowerPanelBulkImportView(generic.BulkImportView):
class PowerPanelBulkEditView(generic.BulkEditView): class PowerPanelBulkEditView(generic.BulkEditView):
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group') queryset = PowerPanel.objects.prefetch_related('site', 'location')
filterset = filters.PowerPanelFilterSet filterset = filters.PowerPanelFilterSet
table = tables.PowerPanelTable table = tables.PowerPanelTable
form = forms.PowerPanelBulkEditForm form = forms.PowerPanelBulkEditForm
@ -2609,7 +2609,7 @@ class PowerPanelBulkEditView(generic.BulkEditView):
class PowerPanelBulkDeleteView(generic.BulkDeleteView): class PowerPanelBulkDeleteView(generic.BulkDeleteView):
queryset = PowerPanel.objects.prefetch_related( queryset = PowerPanel.objects.prefetch_related(
'site', 'rack_group' 'site', 'location'
).annotate( ).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel') powerfeed_count=count_related(PowerFeed, 'power_panel')
) )

View File

@ -9,7 +9,7 @@ from django_rq.queues import get_connection
from rest_framework import status from rest_framework import status
from rq import Worker from rq import Worker
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
from extras.api.views import ReportViewSet, ScriptViewSet from extras.api.views import ReportViewSet, ScriptViewSet
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag
from extras.reports import Report from extras.reports import Report
@ -382,13 +382,13 @@ class CreatedUpdatedFilterTest(APITestCase):
super().setUp() super().setUp()
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1') self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1') self.location1 = Location.objects.create(site=self.site1, name='Test Location 1', slug='test-location-1')
self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000') self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
self.rack1 = Rack.objects.create( self.rack1 = Rack.objects.create(
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42, site=self.site1, location=self.location1, role=self.rackrole1, name='Test Rack 1', u_height=42,
) )
self.rack2 = Rack.objects.create( self.rack2 = Rack.objects.create(
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42, site=self.site1, location=self.location1, role=self.rackrole1, name='Test Rack 2', u_height=42,
) )
# change the created and last_updated of one # change the created and last_updated of one

View File

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

View File

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

View File

@ -428,7 +428,7 @@ CACHEOPS = {
'circuits.*': {'ops': 'all'}, 'circuits.*': {'ops': 'all'},
'dcim.inventoryitem': None, # MPTT models are exempt due to raw SQL 'dcim.inventoryitem': None, # MPTT models are exempt due to raw SQL
'dcim.region': None, # MPTT models are exempt due to raw SQL 'dcim.region': None, # MPTT models are exempt due to raw SQL
'dcim.rackgroup': None, # MPTT models are exempt due to raw SQL 'dcim.location': None, # MPTT models are exempt due to raw SQL
'dcim.*': {'ops': 'all'}, 'dcim.*': {'ops': 'all'},
'ipam.*': {'ops': 'all'}, 'ipam.*': {'ops': 'all'},
'extras.*': {'ops': 'all'}, 'extras.*': {'ops': 'all'},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -194,15 +194,17 @@
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Rack Groups</strong> <strong>Locations</strong>
</div> </div>
<table class="table table-hover panel-body"> <table class="table table-hover panel-body">
{% for rg in rack_groups %} {% for location in locations %}
<tr> <tr>
<td style="padding-left: {{ rg.level }}8px"><i class="mdi mdi-folder-open"></i> <a href="{{ rg.get_absolute_url }}">{{ rg }}</a></td> <td style="padding-left: {{ location.level }}8px">
<td>{{ rg.rack_count }}</td> <i class="mdi mdi-folder-open"></i> <a href="{{ location.get_absolute_url }}">{{ location }}</a>
</td>
<td>{{ location.rack_count }}</td>
<td class="text-right noprint"> <td class="text-right noprint">
<a href="{% url 'dcim:rack_elevation_list' %}?group_id={{ rg.pk }}" class="btn btn-xs btn-primary" title="View elevations"> <a href="{% url 'dcim:rack_elevation_list' %}?location_id={{ location.pk }}" class="btn btn-xs btn-primary" title="View elevations">
<i class="mdi mdi-server"></i> <i class="mdi mdi-server"></i>
</a> </a>
</td> </td>

View File

@ -49,14 +49,14 @@
{% endif %} {% endif %}
<a href="{% url 'dcim:rack_list' %}">Racks</a> <a href="{% url 'dcim:rack_list' %}">Racks</a>
</li> </li>
<li{% if not perms.dcim.view_rackgroup %} class="disabled"{% endif %}> <li{% if not perms.dcim.view_location %} class="disabled"{% endif %}>
{% if perms.dcim.add_rackgroup %} {% if perms.dcim.add_location %}
<div class="buttons pull-right"> <div class="buttons pull-right">
<a href="{% url 'dcim:rackgroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a> <a href="{% url 'dcim:location_add' %}" class="btn btn-xs btn-success" title="Add"><i class="mdi mdi-plus-thick"></i></a>
<a href="{% url 'dcim:rackgroup_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a> <a href="{% url 'dcim:location_import' %}" class="btn btn-xs btn-info" title="Import"><i class="mdi mdi-database-import-outline"></i></a>
</div> </div>
{% endif %} {% endif %}
<a href="{% url 'dcim:rackgroup_list' %}">Rack Groups</a> <a href="{% url 'dcim:location_list' %}">Locations</a>
</li> </li>
<li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}> <li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}>
{% if perms.dcim.add_rackrole %} {% if perms.dcim.add_rackrole %}