1
0
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:
Jeremy Stretch
2018-11-01 16:03:42 -04:00
parent 91ce6c2420
commit fa6c4db13b
10 changed files with 124 additions and 37 deletions

View File

@ -117,6 +117,7 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
site = NestedSiteSerializer()
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
role = NestedRackRoleSerializer(required=False, allow_null=True)
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
@ -125,8 +126,8 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
class Meta:
model = Rack
fields = [
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial', 'type',
'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
# prevents facility_id from being interpreted as a required field.

View File

@ -29,6 +29,20 @@ RACK_FACE_CHOICES = [
[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
SUBDEVICE_ROLE_PARENT = True
SUBDEVICE_ROLE_CHILD = False
@ -265,7 +279,7 @@ SITE_STATUS_CHOICES = [
[SITE_STATUS_RETIRED, 'Retired'],
]
# Bootstrap CSS classes for device statuses
# Bootstrap CSS classes for device/rack statuses
STATUS_CLASSES = {
0: 'warning',
1: 'success',

View File

@ -8,10 +8,7 @@ from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableCharFieldFilter, NumericInFilter
from virtualization.models import Cluster
from .constants import (
DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
WIRELESS_IFACE_TYPES,
)
from .constants import *
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
@ -183,6 +180,10 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Tenant (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=RACK_STATUS_CHOICES,
null_value=None
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(),
label='Role (ID)',

View File

@ -306,8 +306,8 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class Meta:
model = Rack
fields = [
'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'role', 'serial', 'type', 'width',
'u_height', 'desc_units', 'comments', 'tags',
'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', 'type',
'width', 'u_height', 'desc_units', 'comments', 'tags',
]
help_texts = {
'site': "The site at which the rack exists",
@ -342,6 +342,11 @@ class RackCSVForm(forms.ModelForm):
'invalid_choice': 'Tenant not found.',
}
)
status = CSVChoiceField(
choices=RACK_STATUS_CHOICES,
required=False,
help_text='Operational status'
)
role = forms.ModelChoiceField(
queryset=RackRole.objects.all(),
required=False,
@ -402,17 +407,56 @@ class RackCSVForm(forms.ModelForm):
class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False, label='Group')
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
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, label='Type')
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
u_height = forms.IntegerField(required=False, label='Height (U)')
desc_units = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Descending units')
comments = CommentField(widget=SmallTextarea)
pk = forms.ModelMultipleChoiceField(
queryset=Rack.objects.all(),
widget=forms.MultipleHiddenInput
)
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False
)
group = forms.ModelChoiceField(
queryset=RackGroup.objects.all(),
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:
nullable_fields = ['group', 'tenant', 'role', 'serial', 'comments']
@ -435,6 +479,12 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
to_field_name='slug',
null_label='-- None --'
)
status = AnnotatedMultipleChoiceField(
choices=RACK_STATUS_CHOICES,
annotate=Rack.objects.all(),
annotate_field='status',
required=False
)
role = FilterChoiceField(
queryset=RackRole.objects.annotate(filter_count=Count('racks')),
to_field_name='slug',

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

View File

@ -464,6 +464,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
blank=True,
null=True
)
status = models.PositiveSmallIntegerField(
choices=RACK_STATUS_CHOICES,
default=RACK_STATUS_ACTIVE
)
role = models.ForeignKey(
to='dcim.RackRole',
on_delete=models.PROTECT,
@ -514,7 +518,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
tags = TaggableManager()
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',
]
@ -571,6 +575,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
self.name,
self.facility_id,
self.tenant.name if self.tenant else None,
self.get_status_display(),
self.role.name if self.role else None,
self.get_type_display() if self.type else None,
self.serial,
@ -595,6 +600,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return self.name
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):
"""
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}

View File

@ -274,12 +274,13 @@ class RackTable(BaseTable):
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
status = tables.TemplateColumn(STATUS_LABEL)
role = tables.TemplateColumn(RACK_ROLE)
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
class Meta(BaseTable.Meta):
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):
@ -291,24 +292,11 @@ class RackDetailTable(RackTable):
class Meta(RackTable.Meta):
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',
)
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
#

View File

@ -401,7 +401,7 @@ class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'dcim.add_rack'
model_form = forms.RackCSVForm
table = tables.RackImportTable
table = tables.RackTable
default_return_url = 'dcim:rack_list'

View File

@ -105,6 +105,12 @@
{% endif %}
</td>
</tr>
<tr>
<td>Status</td>
<td>
{{ rack.get_status_display }}
</td>
</tr>
<tr>
<td>Role</td>
<td>

View File

@ -9,6 +9,7 @@
{% render_field form.name %}
{% render_field form.facility_id %}
{% render_field form.group %}
{% render_field form.status %}
{% render_field form.role %}
{% render_field form.serial %}
</div>