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

Closes #450: Add 'outer_width' and 'outer_depth' fields to Rack

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

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@ v2.5.0 (FUTURE)
 
 
 ## Enhancements
 ## Enhancements
 
 
+* [#450](https://github.com/digitalocean/netbox/issues/450) - Added `outer_width` and `outer_depth` fields to rack model
 * [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added an `asset_tag` field for racks
 * [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added an `asset_tag` field for racks
 * [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2
 * [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2
 * [#2104](https://github.com/digitalocean/netbox/issues/2104) - Added a `status` field for racks
 * [#2104](https://github.com/digitalocean/netbox/issues/2104) - Added a `status` field for racks

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

@@ -121,14 +121,15 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
     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)
+    outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False)
     tags = TagListSerializerField(required=False)
     tags = TagListSerializerField(required=False)
 
 
     class Meta:
     class Meta:
         model = Rack
         model = Rack
         fields = [
         fields = [
             'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
             'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
-            'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created',
-            'last_updated',
+            'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
+            '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.
@@ -504,7 +505,7 @@ class CableSerializer(ValidatedModelSerializer):
     termination_a = serializers.SerializerMethodField(read_only=True)
     termination_a = serializers.SerializerMethodField(read_only=True)
     termination_b = serializers.SerializerMethodField(read_only=True)
     termination_b = serializers.SerializerMethodField(read_only=True)
     status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
     status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
-    length_unit = ChoiceField(choices=LENGTH_UNIT_CHOICES, required=False)
+    length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False)
 
 
     class Meta:
     class Meta:
         model = Cable
         model = Cable

+ 6 - 1
netbox/dcim/constants.py

@@ -362,11 +362,16 @@ COMPATIBLE_TERMINATION_TYPES = {
 
 
 LENGTH_UNIT_METER = 'm'
 LENGTH_UNIT_METER = 'm'
 LENGTH_UNIT_CENTIMETER = 'cm'
 LENGTH_UNIT_CENTIMETER = 'cm'
+LENGTH_UNIT_MILLIMETER = 'mm'
 LENGTH_UNIT_FOOT = 'ft'
 LENGTH_UNIT_FOOT = 'ft'
 LENGTH_UNIT_INCH = 'in'
 LENGTH_UNIT_INCH = 'in'
-LENGTH_UNIT_CHOICES = (
+CABLE_LENGTH_UNIT_CHOICES = (
     (LENGTH_UNIT_METER, 'Meters'),
     (LENGTH_UNIT_METER, 'Meters'),
     (LENGTH_UNIT_CENTIMETER, 'Centimeters'),
     (LENGTH_UNIT_CENTIMETER, 'Centimeters'),
     (LENGTH_UNIT_FOOT, 'Feet'),
     (LENGTH_UNIT_FOOT, 'Feet'),
     (LENGTH_UNIT_INCH, 'Inches'),
     (LENGTH_UNIT_INCH, 'Inches'),
 )
 )
+RACK_DIMENSION_UNIT_CHOICES = (
+    (LENGTH_UNIT_MILLIMETER, 'Millimeters'),
+    (LENGTH_UNIT_INCH, 'Inches'),
+)

+ 4 - 1
netbox/dcim/filters.py

@@ -201,7 +201,10 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
 
 
     class Meta:
     class Meta:
         model = Rack
         model = Rack
-        fields = ['name', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units']
+        fields = [
+            'name', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
+            'outer_unit',
+        ]
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
         if not value.strip():
         if not value.strip():

+ 23 - 4
netbox/dcim/forms.py

@@ -307,7 +307,7 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         model = Rack
         model = Rack
         fields = [
         fields = [
             'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', 'asset_tag',
             'site', 'group', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', 'asset_tag',
-            'type', 'width', 'u_height', 'desc_units', 'comments', 'tags',
+            'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'tags',
         ]
         ]
         help_texts = {
         help_texts = {
             'site': "The site at which the rack exists",
             'site': "The site at which the rack exists",
@@ -368,6 +368,11 @@ class RackCSVForm(forms.ModelForm):
         ),
         ),
         help_text='Rail-to-rail width (in inches)'
         help_text='Rail-to-rail width (in inches)'
     )
     )
+    outer_unit = CSVChoiceField(
+        choices=RACK_DIMENSION_UNIT_CHOICES,
+        required=False,
+        help_text='Unit for outer dimensions'
+    )
 
 
     class Meta:
     class Meta:
         model = Rack
         model = Rack
@@ -458,12 +463,26 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
         widget=BulkEditNullBooleanSelect,
         widget=BulkEditNullBooleanSelect,
         label='Descending units'
         label='Descending units'
     )
     )
+    outer_width = forms.IntegerField(
+        required=False,
+        min_value=1
+    )
+    outer_depth = forms.IntegerField(
+        required=False,
+        min_value=1
+    )
+    outer_unit = forms.ChoiceField(
+        choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES),
+        required=False
+    )
     comments = CommentField(
     comments = CommentField(
         widget=SmallTextarea
         widget=SmallTextarea
     )
     )
 
 
     class Meta:
     class Meta:
-        nullable_fields = ['group', 'tenant', 'role', 'serial', 'asset_tag', 'comments']
+        nullable_fields = [
+            'group', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
+        ]
 
 
 
 
 class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
 class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
@@ -1864,7 +1883,7 @@ class CableCSVForm(forms.ModelForm):
         help_text='Cable type'
         help_text='Cable type'
     )
     )
     length_unit = CSVChoiceField(
     length_unit = CSVChoiceField(
-        choices=LENGTH_UNIT_CHOICES,
+        choices=CABLE_LENGTH_UNIT_CHOICES,
         required=False,
         required=False,
         help_text='Length unit'
         help_text='Length unit'
     )
     )
@@ -1962,7 +1981,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
         required=False
         required=False
     )
     )
     length_unit = forms.ChoiceField(
     length_unit = forms.ChoiceField(
-        choices=add_blank_choice(LENGTH_UNIT_CHOICES),
+        choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES),
         required=False,
         required=False,
         initial=''
         initial=''
     )
     )

+ 15 - 0
netbox/dcim/migrations/0068_rack_new_fields.py

@@ -20,4 +20,19 @@ class Migration(migrations.Migration):
             name='asset_tag',
             name='asset_tag',
             field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, unique=True),
             field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, unique=True),
         ),
         ),
+        migrations.AddField(
+            model_name='rack',
+            name='outer_depth',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='outer_unit',
+            field=models.CharField(blank=True, max_length=2),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='outer_width',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
     ]
     ]

+ 26 - 2
netbox/dcim/models.py

@@ -510,6 +510,19 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
         verbose_name='Descending units',
         verbose_name='Descending units',
         help_text='Units are numbered top-to-bottom'
         help_text='Units are numbered top-to-bottom'
     )
     )
+    outer_width = models.PositiveSmallIntegerField(
+        blank=True,
+        null=True
+    )
+    outer_depth = models.PositiveSmallIntegerField(
+        blank=True,
+        null=True
+    )
+    outer_unit = models.CharField(
+        choices=RACK_DIMENSION_UNIT_CHOICES,
+        max_length=2,
+        blank=True
+    )
     comments = models.TextField(
     comments = models.TextField(
         blank=True
         blank=True
     )
     )
@@ -527,7 +540,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
 
 
     csv_headers = [
     csv_headers = [
         'site', 'group_name', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
         'site', 'group_name', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width',
-        'u_height', 'desc_units', 'comments',
+        'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
     ]
     ]
 
 
     class Meta:
     class Meta:
@@ -545,6 +558,14 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
 
 
     def clean(self):
     def clean(self):
 
 
+        # Validate outer dimensions and unit
+        if self.outer_width and not self.outer_unit:
+            raise ValidationError("Must specify a unit when setting an outer width")
+        if self.outer_depth and not self.outer_unit:
+            raise ValidationError("Must specify a unit when setting an outer depth")
+        if self.outer_unit and self.outer_width is None and self.outer_depth is None:
+            self.length_unit = ''
+
         if self.pk:
         if self.pk:
             # Validate that Rack is tall enough to house the installed Devices
             # Validate that Rack is tall enough to house the installed Devices
             top_device = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('-position').first()
             top_device = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('-position').first()
@@ -591,6 +612,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
             self.width,
             self.width,
             self.u_height,
             self.u_height,
             self.desc_units,
             self.desc_units,
+            self.outer_width,
+            self.outer_depth,
+            self.outer_unit,
             self.comments,
             self.comments,
         )
         )
 
 
@@ -2410,7 +2434,7 @@ class Cable(ChangeLoggedModel):
         null=True
         null=True
     )
     )
     length_unit = models.CharField(
     length_unit = models.CharField(
-        choices=LENGTH_UNIT_CHOICES,
+        choices=CABLE_LENGTH_UNIT_CHOICES,
         max_length=2,
         max_length=2,
         blank=True
         blank=True
     )
     )

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

@@ -172,6 +172,26 @@
                     <td>Height</td>
                     <td>Height</td>
                     <td>{{ rack.u_height }}U ({% if rack.desc_units %}descending{% else %}ascending{% endif %})</td>
                     <td>{{ rack.u_height }}U ({% if rack.desc_units %}descending{% else %}ascending{% endif %})</td>
                 </tr>
                 </tr>
+                <tr>
+                    <td>Outer Width</td>
+                    <td>
+                        {% if rack.outer_width %}
+                            <span>{{ rack.outer_width }}{{ rack.outer_unit }}</span>
+                        {% else %}
+                            <span class="text-muted">&mdash;</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Outer Depth</td>
+                    <td>
+                        {% if rack.outer_depth %}
+                            <span>{{ rack.outer_depth }}{{ rack.outer_unit }}</span>
+                        {% else %}
+                            <span class="text-muted">&mdash;</span>
+                        {% endif %}
+                    </td>
+                </tr>
             </table>
             </table>
         </div>
         </div>
         {% include 'inc/custom_fields_panel.html' with obj=rack %}
         {% include 'inc/custom_fields_panel.html' with obj=rack %}

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

@@ -28,6 +28,18 @@
             {% render_field form.type %}
             {% render_field form.type %}
             {% render_field form.width %}
             {% render_field form.width %}
             {% render_field form.u_height %}
             {% render_field form.u_height %}
+            <div class="form-group">
+                <label class="col-md-3 control-label">Outer dimensions</label>
+                <div class="col-md-3">
+                    {{ form.outer_width }}
+                </div>
+                <div class="col-md-3">
+                    {{ form.outer_depth }}
+                </div>
+                <div class="col-md-2">
+                    {{ form.outer_unit }}
+                </div>
+            </div>
             {% render_field form.desc_units %}
             {% render_field form.desc_units %}
         </div>
         </div>
     </div>
     </div>