mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #2104: Add 'status' field to Rack
This commit is contained in:
@ -117,6 +117,7 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
|
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
|
||||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||||
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
||||||
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
||||||
@ -125,8 +126,8 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
|
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial', 'type',
|
||||||
'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'width', 'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
||||||
# prevents facility_id from being interpreted as a required field.
|
# prevents facility_id from being interpreted as a required field.
|
||||||
|
@ -29,6 +29,20 @@ RACK_FACE_CHOICES = [
|
|||||||
[RACK_FACE_REAR, 'Rear'],
|
[RACK_FACE_REAR, 'Rear'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Rack statuses
|
||||||
|
RACK_STATUS_RESERVED = 0
|
||||||
|
RACK_STATUS_AVAILABLE = 1
|
||||||
|
RACK_STATUS_PLANNED = 2
|
||||||
|
RACK_STATUS_ACTIVE = 3
|
||||||
|
RACK_STATUS_DEPRECATED = 4
|
||||||
|
RACK_STATUS_CHOICES = [
|
||||||
|
[RACK_STATUS_ACTIVE, 'Active'],
|
||||||
|
[RACK_STATUS_PLANNED, 'Planned'],
|
||||||
|
[RACK_STATUS_RESERVED, 'Reserved'],
|
||||||
|
[RACK_STATUS_AVAILABLE, 'Available'],
|
||||||
|
[RACK_STATUS_DEPRECATED, 'Deprecated'],
|
||||||
|
]
|
||||||
|
|
||||||
# Parent/child device roles
|
# Parent/child device roles
|
||||||
SUBDEVICE_ROLE_PARENT = True
|
SUBDEVICE_ROLE_PARENT = True
|
||||||
SUBDEVICE_ROLE_CHILD = False
|
SUBDEVICE_ROLE_CHILD = False
|
||||||
@ -265,7 +279,7 @@ SITE_STATUS_CHOICES = [
|
|||||||
[SITE_STATUS_RETIRED, 'Retired'],
|
[SITE_STATUS_RETIRED, 'Retired'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Bootstrap CSS classes for device statuses
|
# Bootstrap CSS classes for device/rack statuses
|
||||||
STATUS_CLASSES = {
|
STATUS_CLASSES = {
|
||||||
0: 'warning',
|
0: 'warning',
|
||||||
1: 'success',
|
1: 'success',
|
||||||
|
@ -8,10 +8,7 @@ from extras.filters import CustomFieldFilterSet
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.filters import NullableCharFieldFilter, NumericInFilter
|
from utilities.filters import NullableCharFieldFilter, NumericInFilter
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .constants import (
|
from .constants import *
|
||||||
DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
|
|
||||||
WIRELESS_IFACE_TYPES,
|
|
||||||
)
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
@ -183,6 +180,10 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=RACK_STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
label='Role (ID)',
|
label='Role (ID)',
|
||||||
|
@ -306,8 +306,8 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'role', 'serial', 'type', 'width',
|
'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', 'type',
|
||||||
'u_height', 'desc_units', 'comments', 'tags',
|
'width', 'u_height', 'desc_units', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'site': "The site at which the rack exists",
|
'site': "The site at which the rack exists",
|
||||||
@ -342,6 +342,11 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Tenant not found.',
|
'invalid_choice': 'Tenant not found.',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
status = CSVChoiceField(
|
||||||
|
choices=RACK_STATUS_CHOICES,
|
||||||
|
required=False,
|
||||||
|
help_text='Operational status'
|
||||||
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -402,17 +407,56 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
|
queryset=Rack.objects.all(),
|
||||||
group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False, label='Group')
|
widget=forms.MultipleHiddenInput
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
)
|
||||||
role = forms.ModelChoiceField(queryset=RackRole.objects.all(), required=False)
|
site = forms.ModelChoiceField(
|
||||||
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
queryset=Site.objects.all(),
|
||||||
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
required=False
|
||||||
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
)
|
||||||
u_height = forms.IntegerField(required=False, label='Height (U)')
|
group = forms.ModelChoiceField(
|
||||||
desc_units = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Descending units')
|
queryset=RackGroup.objects.all(),
|
||||||
comments = CommentField(widget=SmallTextarea)
|
required=False
|
||||||
|
)
|
||||||
|
tenant = forms.ModelChoiceField(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
choices=add_blank_choice(RACK_STATUS_CHOICES),
|
||||||
|
required=False,
|
||||||
|
initial=''
|
||||||
|
)
|
||||||
|
role = forms.ModelChoiceField(
|
||||||
|
queryset=RackRole.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
serial = forms.CharField(
|
||||||
|
max_length=50,
|
||||||
|
required=False,
|
||||||
|
label='Serial Number'
|
||||||
|
)
|
||||||
|
type = forms.ChoiceField(
|
||||||
|
choices=add_blank_choice(RACK_TYPE_CHOICES),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
width = forms.ChoiceField(
|
||||||
|
choices=add_blank_choice(RACK_WIDTH_CHOICES),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
u_height = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
label='Height (U)'
|
||||||
|
)
|
||||||
|
desc_units = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=BulkEditNullBooleanSelect,
|
||||||
|
label='Descending units'
|
||||||
|
)
|
||||||
|
comments = CommentField(
|
||||||
|
widget=SmallTextarea
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['group', 'tenant', 'role', 'serial', 'comments']
|
nullable_fields = ['group', 'tenant', 'role', 'serial', 'comments']
|
||||||
@ -435,6 +479,12 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
|
status = AnnotatedMultipleChoiceField(
|
||||||
|
choices=RACK_STATUS_CHOICES,
|
||||||
|
annotate=Rack.objects.all(),
|
||||||
|
annotate_field='status',
|
||||||
|
required=False
|
||||||
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
|
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
18
netbox/dcim/migrations/0068_rack_status.py
Normal file
18
netbox/dcim/migrations/0068_rack_status.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.0.9 on 2018-11-01 19:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0067_device_type_remove_qualifiers'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rack',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveSmallIntegerField(default=3),
|
||||||
|
),
|
||||||
|
]
|
@ -464,6 +464,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
status = models.PositiveSmallIntegerField(
|
||||||
|
choices=RACK_STATUS_CHOICES,
|
||||||
|
default=RACK_STATUS_ACTIVE
|
||||||
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
to='dcim.RackRole',
|
to='dcim.RackRole',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -514,7 +518,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
tags = TaggableManager()
|
tags = TaggableManager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'type', 'serial', 'width', 'u_height',
|
'site', 'group_name', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'width', 'u_height',
|
||||||
'desc_units', 'comments',
|
'desc_units', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -571,6 +575,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
self.name,
|
self.name,
|
||||||
self.facility_id,
|
self.facility_id,
|
||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
|
self.get_status_display(),
|
||||||
self.role.name if self.role else None,
|
self.role.name if self.role else None,
|
||||||
self.get_type_display() if self.type else None,
|
self.get_type_display() if self.type else None,
|
||||||
self.serial,
|
self.serial,
|
||||||
@ -595,6 +600,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return self.name
|
return self.name
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_status_class(self):
|
||||||
|
return STATUS_CLASSES[self.status]
|
||||||
|
|
||||||
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False):
|
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False):
|
||||||
"""
|
"""
|
||||||
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
||||||
|
@ -274,12 +274,13 @@ class RackTable(BaseTable):
|
|||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
||||||
|
status = tables.TemplateColumn(STATUS_LABEL)
|
||||||
role = tables.TemplateColumn(RACK_ROLE)
|
role = tables.TemplateColumn(RACK_ROLE)
|
||||||
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height')
|
fields = ('pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height')
|
||||||
|
|
||||||
|
|
||||||
class RackDetailTable(RackTable):
|
class RackDetailTable(RackTable):
|
||||||
@ -291,24 +292,11 @@ class RackDetailTable(RackTable):
|
|||||||
|
|
||||||
class Meta(RackTable.Meta):
|
class Meta(RackTable.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
|
'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
|
||||||
'get_utilization',
|
'get_utilization',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackImportTable(BaseTable):
|
|
||||||
name = tables.LinkColumn('dcim:rack', args=[Accessor('pk')], verbose_name='Name')
|
|
||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
|
||||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
|
||||||
facility_id = tables.Column(verbose_name='Facility ID')
|
|
||||||
tenant = tables.TemplateColumn(template_code=COL_TENANT)
|
|
||||||
u_height = tables.Column(verbose_name='Height (U)')
|
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
|
||||||
model = Rack
|
|
||||||
fields = ('name', 'site', 'group', 'facility_id', 'tenant', 'u_height')
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rack reservations
|
# Rack reservations
|
||||||
#
|
#
|
||||||
|
@ -401,7 +401,7 @@ class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||||
permission_required = 'dcim.add_rack'
|
permission_required = 'dcim.add_rack'
|
||||||
model_form = forms.RackCSVForm
|
model_form = forms.RackCSVForm
|
||||||
table = tables.RackImportTable
|
table = tables.RackTable
|
||||||
default_return_url = 'dcim:rack_list'
|
default_return_url = 'dcim:rack_list'
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,6 +105,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>
|
||||||
|
{{ rack.get_status_display }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Role</td>
|
<td>Role</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.facility_id %}
|
{% render_field form.facility_id %}
|
||||||
{% render_field form.group %}
|
{% render_field form.group %}
|
||||||
|
{% render_field form.status %}
|
||||||
{% render_field form.role %}
|
{% render_field form.role %}
|
||||||
{% render_field form.serial %}
|
{% render_field form.serial %}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user