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