Просмотр исходного кода

Closes #2104: Add 'status' field to Rack

Jeremy Stretch 7 лет назад
Родитель
Сommit
fa6c4db13b

+ 3 - 2
netbox/dcim/api/serializers.py

@@ -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',
-            '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
         # 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.

+ 15 - 1
netbox/dcim/constants.py

@@ -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',

+ 5 - 4
netbox/dcim/filters.py

@@ -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 (
-    DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
-    WIRELESS_IFACE_TYPES,
-)
+from .constants import *
 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)',

+ 63 - 13
netbox/dcim/forms.py

@@ -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',
-            '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 = {
         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)
-    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:
     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 - 0
netbox/dcim/migrations/0068_rack_status.py

@@ -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),
+        ),
+    ]

+ 9 - 1
netbox/dcim/models.py

@@ -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'}

+ 3 - 15
netbox/dcim/tables.py

@@ -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
 #
 #

+ 1 - 1
netbox/dcim/views.py

@@ -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'
 
 
 
 

+ 6 - 0
netbox/templates/dcim/rack.html

@@ -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>

+ 1 - 0
netbox/templates/dcim/rack_edit.html

@@ -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>