mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#180: Added type and width fields to Rack model
This commit is contained in:
@ -26,7 +26,7 @@ class RackGroupAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Rack)
|
@admin.register(Rack)
|
||||||
class RackAdmin(admin.ModelAdmin):
|
class RackAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name', 'facility_id', 'site', 'u_height']
|
list_display = ['name', 'facility_id', 'site', 'type', 'width', 'u_height']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -58,7 +58,8 @@ class RackSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
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):
|
class RackNestedSerializer(RackSerializer):
|
||||||
@ -72,8 +73,8 @@ class RackDetailSerializer(RackSerializer):
|
|||||||
rear_units = serializers.SerializerMethodField()
|
rear_units = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta(RackSerializer.Meta):
|
class Meta(RackSerializer.Meta):
|
||||||
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',
|
||||||
'front_units', 'rear_units']
|
'comments', 'front_units', 'rear_units']
|
||||||
|
|
||||||
def get_front_units(self, obj):
|
def get_front_units(self, obj):
|
||||||
units = obj.get_rack_units(face=RACK_FACE_FRONT)
|
units = obj.get_rack_units(face=RACK_FACE_FRONT)
|
||||||
|
@ -7,7 +7,7 @@ from ipam.models import IPAddress
|
|||||||
from tenancy.forms import bulkedit_tenant_choices
|
from tenancy.forms import bulkedit_tenant_choices
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
||||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +15,8 @@ from .models import (
|
|||||||
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||||
Interface, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
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:
|
class Meta:
|
||||||
model = Rack
|
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 = {
|
help_texts = {
|
||||||
'site': "The site at which the rack exists",
|
'site': "The site at which the rack exists",
|
||||||
'name': "Organizational rack name",
|
'name': "Organizational rack name",
|
||||||
@ -165,10 +166,11 @@ class RackFromCSVForm(forms.ModelForm):
|
|||||||
group_name = forms.CharField(required=False)
|
group_name = forms.CharField(required=False)
|
||||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||||
|
type = forms.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
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):
|
def clean(self):
|
||||||
|
|
||||||
@ -182,6 +184,19 @@ class RackFromCSVForm(forms.ModelForm):
|
|||||||
except RackGroup.DoesNotExist:
|
except RackGroup.DoesNotExist:
|
||||||
self.add_error('group_name', "Invalid rack group ({})".format(group))
|
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):
|
class RackImportForm(BulkImportForm, BootstrapMixin):
|
||||||
csv = CSVDataField(csv_form=RackFromCSVForm)
|
csv = CSVDataField(csv_form=RackFromCSVForm)
|
||||||
@ -192,6 +207,8 @@ class RackBulkEditForm(forms.Form, BootstrapMixin):
|
|||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||||
group = forms.ModelChoiceField(queryset=RackGroup.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')
|
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)')
|
u_height = forms.IntegerField(required=False, label='Height (U)')
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
25
netbox/dcim/migrations/0014_rack_add_type_width.py
Normal file
25
netbox/dcim/migrations/0014_rack_add_type_width.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -16,6 +16,26 @@ from utilities.models import CreatedUpdatedModel
|
|||||||
from .fields import ASNField, MACAddressField
|
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_FRONT = 0
|
||||||
RACK_FACE_REAR = 1
|
RACK_FACE_REAR = 1
|
||||||
RACK_FACE_CHOICES = [
|
RACK_FACE_CHOICES = [
|
||||||
@ -284,6 +304,9 @@ class Rack(CreatedUpdatedModel):
|
|||||||
site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT)
|
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)
|
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)
|
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)')
|
u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)')
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(blank=True)
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ class SiteTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'type',
|
||||||
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
'comments'
|
'comments'
|
||||||
]
|
]
|
||||||
@ -118,6 +120,8 @@ class RackTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'type',
|
||||||
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
'comments'
|
'comments'
|
||||||
]
|
]
|
||||||
@ -130,6 +134,8 @@ class RackTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'type',
|
||||||
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
'comments',
|
'comments',
|
||||||
'front_units',
|
'front_units',
|
||||||
|
@ -227,7 +227,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
fields_to_update['tenant'] = None
|
fields_to_update['tenant'] = None
|
||||||
elif form.cleaned_data['tenant']:
|
elif form.cleaned_data['tenant']:
|
||||||
fields_to_update['tenant'] = 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]:
|
if form.cleaned_data[field]:
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
fields_to_update[field] = form.cleaned_data[field]
|
||||||
|
|
||||||
|
@ -96,6 +96,20 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>Height</td>
|
<td>Height</td>
|
||||||
<td>{{ rack.u_height }}U</td>
|
<td>{{ rack.u_height }}U</td>
|
||||||
|
@ -4,13 +4,24 @@
|
|||||||
{% block title %}Rack Bulk Edit{% endblock %}
|
{% block title %}Rack Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% 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 %}
|
{% for rack in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack }}</a></td>
|
<td><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack }}</a></td>
|
||||||
<td>{{ rack.facility_id }}</td>
|
|
||||||
<td>{{ rack.site }}</td>
|
<td>{{ rack.site }}</td>
|
||||||
|
<td>{{ rack.group }}</td>
|
||||||
<td>{{ rack.tenant }}</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.facility_id %}
|
{% render_field form.facility_id %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
|
{% render_field form.type %}
|
||||||
|
{% render_field form.width %}
|
||||||
{% render_field form.u_height %}
|
{% render_field form.u_height %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,6 +53,16 @@
|
|||||||
<td>Name of tenant (optional)</td>
|
<td>Name of tenant (optional)</td>
|
||||||
<td>Pied Piper</td>
|
<td>Pied Piper</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>Height</td>
|
<td>Height</td>
|
||||||
<td>Height in rack units</td>
|
<td>Height in rack units</td>
|
||||||
@ -61,7 +71,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h4>Example</h4>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -27,6 +27,13 @@ def expand_pattern(string):
|
|||||||
yield "{}{}{}".format(lead, i, remnant)
|
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
|
# Widgets
|
||||||
#
|
#
|
||||||
|
Reference in New Issue
Block a user