mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #450: Add 'outer_width' and 'outer_depth' fields to Rack
This commit is contained in:
@ -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
|
||||||
|
@ -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',
|
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||||
'last_updated',
|
'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
|
||||||
|
@ -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'),
|
||||||
|
)
|
||||||
|
@ -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():
|
||||||
|
@ -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=''
|
||||||
)
|
)
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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">—</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">—</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 %}
|
||||||
|
@ -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>
|
||||||
|
Reference in New Issue
Block a user