Browse Source

#180: Added type and width fields to Rack model

Jeremy Stretch 8 years ago
parent
commit
e7116b81a4

+ 1 - 1
netbox/dcim/admin.py

@@ -26,7 +26,7 @@ class RackGroupAdmin(admin.ModelAdmin):
 
 @admin.register(Rack)
 class RackAdmin(admin.ModelAdmin):
-    list_display = ['name', 'facility_id', 'site', 'u_height']
+    list_display = ['name', 'facility_id', 'site', 'type', 'width', 'u_height']
 
 
 #

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

@@ -58,7 +58,8 @@ class RackSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Rack
-        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments']
+        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'type', 'width', 'u_height',
+                  'comments']
 
 
 class RackNestedSerializer(RackSerializer):
@@ -72,8 +73,8 @@ class RackDetailSerializer(RackSerializer):
     rear_units = serializers.SerializerMethodField()
 
     class Meta(RackSerializer.Meta):
-        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments',
-                  'front_units', 'rear_units']
+        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'type', 'width', 'u_height',
+                  'comments', 'front_units', 'rear_units']
 
     def get_front_units(self, obj):
         units = obj.get_rack_units(face=RACK_FACE_FRONT)

+ 21 - 4
netbox/dcim/forms.py

@@ -7,7 +7,7 @@ from ipam.models import IPAddress
 from tenancy.forms import bulkedit_tenant_choices
 from tenancy.models import Tenant
 from utilities.forms import (
-    APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
+    APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
     FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
 )
 
@@ -15,7 +15,8 @@ from .models import (
     DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
     ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
     Interface, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
-    PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
+    PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, Site,
+    STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
 )
 
 
@@ -135,7 +136,7 @@ class RackForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = Rack
-        fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'u_height', 'comments']
+        fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height', 'comments']
         help_texts = {
             'site': "The site at which the rack exists",
             'name': "Organizational rack name",
@@ -165,10 +166,11 @@ class RackFromCSVForm(forms.ModelForm):
     group_name = forms.CharField(required=False)
     tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
                                     error_messages={'invalid_choice': 'Tenant not found.'})
+    type = forms.CharField(required=False)
 
     class Meta:
         model = Rack
-        fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'u_height']
+        fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height']
 
     def clean(self):
 
@@ -182,6 +184,19 @@ class RackFromCSVForm(forms.ModelForm):
             except RackGroup.DoesNotExist:
                 self.add_error('group_name', "Invalid rack group ({})".format(group))
 
+    def clean_type(self):
+        rack_type = self.cleaned_data['type']
+        if not rack_type:
+            return None
+        try:
+            choices = {v.lower(): k for k, v in RACK_TYPE_CHOICES}
+            return choices[rack_type.lower()]
+        except KeyError:
+            raise forms.ValidationError('Invalid rack type ({}). Valid choices are: {}.'.format(
+                rack_type,
+                ', '.join({v: k for k, v in RACK_TYPE_CHOICES}),
+            ))
+
 
 class RackImportForm(BulkImportForm, BootstrapMixin):
     csv = CSVDataField(csv_form=RackFromCSVForm)
@@ -192,6 +207,8 @@ class RackBulkEditForm(forms.Form, BootstrapMixin):
     site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
     group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False)
     tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
+    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)')
     comments = CommentField()
 

+ 25 - 0
netbox/dcim/migrations/0014_rack_add_type_width.py

@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.8 on 2016-08-08 21:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0013_add_interface_form_factors'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='rack',
+            name='type',
+            field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='width',
+            field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'),
+        ),
+    ]

+ 23 - 0
netbox/dcim/models.py

@@ -16,6 +16,26 @@ from utilities.models import CreatedUpdatedModel
 from .fields import ASNField, MACAddressField
 
 
+RACK_TYPE_2POST = 100
+RACK_TYPE_4POST = 200
+RACK_TYPE_CABINET = 300
+RACK_TYPE_WALLFRAME = 1000
+RACK_TYPE_WALLCABINET = 1100
+RACK_TYPE_CHOICES = (
+    (RACK_TYPE_2POST, '2-post frame'),
+    (RACK_TYPE_4POST, '4-post frame'),
+    (RACK_TYPE_CABINET, '4-post cabinet'),
+    (RACK_TYPE_WALLFRAME, 'Wall-mounted frame'),
+    (RACK_TYPE_WALLCABINET, 'Wall-mounted cabinet'),
+)
+
+RACK_WIDTH_19IN = 19
+RACK_WIDTH_23IN = 23
+RACK_WIDTH_CHOICES = (
+    (RACK_WIDTH_19IN, '19 inches'),
+    (RACK_WIDTH_23IN, '23 inches'),
+)
+
 RACK_FACE_FRONT = 0
 RACK_FACE_REAR = 1
 RACK_FACE_CHOICES = [
@@ -284,6 +304,9 @@ class Rack(CreatedUpdatedModel):
     site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT)
     group = models.ForeignKey('RackGroup', related_name='racks', blank=True, null=True, on_delete=models.SET_NULL)
     tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='racks', on_delete=models.PROTECT)
+    type = models.PositiveSmallIntegerField(choices=RACK_TYPE_CHOICES, blank=True, null=True, verbose_name='Type')
+    width = models.PositiveSmallIntegerField(choices=RACK_WIDTH_CHOICES, default=RACK_WIDTH_19IN, verbose_name='Width',
+                                             help_text='Rail-to-rail width')
     u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)')
     comments = models.TextField(blank=True)
 

+ 6 - 0
netbox/dcim/tests/test_apis.py

@@ -42,6 +42,8 @@ class SiteTest(APITestCase):
         'site',
         'group',
         'tenant',
+        'type',
+        'width',
         'u_height',
         'comments'
     ]
@@ -118,6 +120,8 @@ class RackTest(APITestCase):
         'site',
         'group',
         'tenant',
+        'type',
+        'width',
         'u_height',
         'comments'
     ]
@@ -130,6 +134,8 @@ class RackTest(APITestCase):
         'site',
         'group',
         'tenant',
+        'type',
+        'width',
         'u_height',
         'comments',
         'front_units',

+ 1 - 1
netbox/dcim/views.py

@@ -227,7 +227,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
             fields_to_update['tenant'] = None
         elif form.cleaned_data['tenant']:
             fields_to_update['tenant'] = form.cleaned_data['tenant']
-        for field in ['site', 'group', 'tenant', 'u_height', 'comments']:
+        for field in ['site', 'group', 'tenant', 'type', 'width', 'u_height', 'comments']:
             if form.cleaned_data[field]:
                 fields_to_update[field] = form.cleaned_data[field]
 

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

@@ -97,6 +97,20 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Type</td>
+                    <td>
+                        {% if rack.type %}
+                            {{ rack.get_type_display }}
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Width</td>
+                    <td>{{ rack.get_width_display }}</td>
+                </tr>
+                <tr>
                     <td>Height</td>
                     <td>{{ rack.u_height }}U</td>
                 </tr>

+ 13 - 2
netbox/templates/dcim/rack_bulk_edit.html

@@ -4,13 +4,24 @@
 {% block title %}Rack Bulk Edit{% endblock %}
 
 {% block select_objects_table %}
+    <tr>
+        <th>Name</th>
+        <th>Site</th>
+        <th>Group</th>
+        <th>Tenant</th>
+        <th>Type</th>
+        <th>Width</th>
+        <th>Height</th>
+    </tr>
     {% for rack in selected_objects %}
         <tr>
             <td><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack }}</a></td>
-            <td>{{ rack.facility_id }}</td>
             <td>{{ rack.site }}</td>
+            <td>{{ rack.group }}</td>
             <td>{{ rack.tenant }}</td>
-            <td>{{ rack.u_height }}</td>
+            <td>{{ rack.get_type_display }}</td>
+            <td>{{ rack.get_width_display }}</td>
+            <td>{{ rack.u_height }}U</td>
         </tr>
     {% endfor %}
 {% endblock %}

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

@@ -10,6 +10,8 @@
             {% render_field form.name %}
             {% render_field form.facility_id %}
             {% render_field form.tenant %}
+            {% render_field form.type %}
+            {% render_field form.width %}
             {% render_field form.u_height %}
         </div>
     </div>

+ 11 - 1
netbox/templates/dcim/rack_import.html

@@ -54,6 +54,16 @@
 					<td>Pied Piper</td>
 				</tr>
 				<tr>
+					<td>Type</td>
+					<td>Rack type (optional)</td>
+					<td>4-post cabinet</td>
+				</tr>
+				<tr>
+					<td>Width</td>
+					<td>Rail-to-rail width (19 or 23 inches)</td>
+					<td>19</td>
+				</tr>
+				<tr>
 					<td>Height</td>
 					<td>Height in rack units</td>
 					<td>42</td>
@@ -61,7 +71,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>DC-4,Cage 1400,R101,J12.100,Pied Piper,42</pre>
+		<pre>DC-4,Cage 1400,R101,J12.100,Pied Piper,4-post cabinet,19,42</pre>
 	</div>
 </div>
 {% endblock %}

+ 7 - 0
netbox/utilities/forms.py

@@ -27,6 +27,13 @@ def expand_pattern(string):
             yield "{}{}{}".format(lead, i, remnant)
 
 
+def add_blank_choice(choices):
+    """
+    Add a blank choice to the beginning of a choices list.
+    """
+    return ((None, '---------'),) + choices
+
+
 #
 # Widgets
 #