mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #241: Introduced rack roles
This commit is contained in:
@ -4,7 +4,7 @@ from django.db.models import Count
|
|||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Module, Platform,
|
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceTemplate, Manufacturer, Module, Platform,
|
||||||
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, Site,
|
PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -24,9 +24,17 @@ class RackGroupAdmin(admin.ModelAdmin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(RackRole)
|
||||||
|
class RackRoleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name', 'slug', 'color']
|
||||||
|
prepopulated_fields = {
|
||||||
|
'slug': ['name'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Rack)
|
@admin.register(Rack)
|
||||||
class RackAdmin(admin.ModelAdmin):
|
class RackAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name', 'facility_id', 'site', 'type', 'width', 'u_height']
|
list_display = ['name', 'facility_id', 'site', 'group', 'tenant', 'role', 'type', 'width', 'u_height']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -4,7 +4,7 @@ from ipam.models import IPAddress
|
|||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
|
||||||
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
||||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
||||||
)
|
)
|
||||||
from tenancy.api.serializers import TenantNestedSerializer
|
from tenancy.api.serializers import TenantNestedSerializer
|
||||||
|
|
||||||
@ -46,6 +46,23 @@ class RackGroupNestedSerializer(RackGroupSerializer):
|
|||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rack roles
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackRoleSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RackRole
|
||||||
|
fields = ['id', 'name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
|
class RackRoleNestedSerializer(RackRoleSerializer):
|
||||||
|
|
||||||
|
class Meta(RackRoleSerializer.Meta):
|
||||||
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
@ -55,11 +72,12 @@ class RackSerializer(serializers.ModelSerializer):
|
|||||||
site = SiteNestedSerializer()
|
site = SiteNestedSerializer()
|
||||||
group = RackGroupNestedSerializer()
|
group = RackGroupNestedSerializer()
|
||||||
tenant = TenantNestedSerializer()
|
tenant = TenantNestedSerializer()
|
||||||
|
role = RackRoleNestedSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'type', 'width', 'u_height',
|
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width',
|
||||||
'comments']
|
'u_height', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class RackNestedSerializer(RackSerializer):
|
class RackNestedSerializer(RackSerializer):
|
||||||
@ -73,8 +91,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', 'type', 'width', 'u_height',
|
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'type', 'width',
|
||||||
'comments', 'front_units', 'rear_units']
|
'u_height', '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)
|
||||||
|
@ -18,6 +18,10 @@ urlpatterns = [
|
|||||||
url(r'^rack-groups/$', RackGroupListView.as_view(), name='rackgroup_list'),
|
url(r'^rack-groups/$', RackGroupListView.as_view(), name='rackgroup_list'),
|
||||||
url(r'^rack-groups/(?P<pk>\d+)/$', RackGroupDetailView.as_view(), name='rackgroup_detail'),
|
url(r'^rack-groups/(?P<pk>\d+)/$', RackGroupDetailView.as_view(), name='rackgroup_detail'),
|
||||||
|
|
||||||
|
# Rack roles
|
||||||
|
url(r'^rack-roles/$', RackRoleListView.as_view(), name='rackrole_list'),
|
||||||
|
url(r'^rack-roles/(?P<pk>\d+)/$', RackRoleDetailView.as_view(), name='rackrole_detail'),
|
||||||
|
|
||||||
# Racks
|
# Racks
|
||||||
url(r'^racks/$', RackListView.as_view(), name='rack_list'),
|
url(r'^racks/$', RackListView.as_view(), name='rack_list'),
|
||||||
url(r'^racks/(?P<pk>\d+)/$', RackDetailView.as_view(), name='rack_detail'),
|
url(r'^racks/(?P<pk>\d+)/$', RackDetailView.as_view(), name='rack_detail'),
|
||||||
|
@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, IFACE_FF_VIRTUAL, Interface,
|
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, IFACE_FF_VIRTUAL, Interface,
|
||||||
InterfaceConnection, Manufacturer, Module, Platform, PowerOutlet, PowerPort, Rack, RackGroup, Site,
|
InterfaceConnection, Manufacturer, Module, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
|
||||||
)
|
)
|
||||||
from dcim import filters
|
from dcim import filters
|
||||||
from .exceptions import MissingFilterException
|
from .exceptions import MissingFilterException
|
||||||
@ -60,6 +60,26 @@ class RackGroupDetailView(generics.RetrieveAPIView):
|
|||||||
serializer_class = serializers.RackGroupSerializer
|
serializer_class = serializers.RackGroupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rack roles
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackRoleListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
List all rack roles
|
||||||
|
"""
|
||||||
|
queryset = RackRole.objects.all()
|
||||||
|
serializer_class = serializers.RackRoleSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class RackRoleDetailView(generics.RetrieveAPIView):
|
||||||
|
"""
|
||||||
|
Retrieve a single rack role
|
||||||
|
"""
|
||||||
|
queryset = RackRole.objects.all()
|
||||||
|
serializer_class = serializers.RackRoleSerializer
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
@ -4,7 +4,7 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
|
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
|
||||||
Platform, PowerOutlet, PowerPort, Rack, RackGroup, Site,
|
Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
|
||||||
)
|
)
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
|
|
||||||
@ -96,6 +96,17 @@ class RackFilter(django_filters.FilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
)
|
)
|
||||||
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='role',
|
||||||
|
queryset=RackRole.objects.all(),
|
||||||
|
label='Role (ID)',
|
||||||
|
)
|
||||||
|
role = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='role',
|
||||||
|
queryset=RackRole.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Role (slug)',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
|
@ -15,8 +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_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, Site,
|
PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackRole,
|
||||||
STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +50,30 @@ def bulkedit_platform_choices():
|
|||||||
return choices
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
def bulkedit_rackgroup_choices():
|
||||||
|
"""
|
||||||
|
Include an option to remove the currently assigned group from a rack.
|
||||||
|
"""
|
||||||
|
choices = [
|
||||||
|
(None, '---------'),
|
||||||
|
(0, 'None'),
|
||||||
|
]
|
||||||
|
choices += [(r.pk, r) for r in RackGroup.objects.all()]
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
def bulkedit_rackrole_choices():
|
||||||
|
"""
|
||||||
|
Include an option to remove the currently assigned role from a rack.
|
||||||
|
"""
|
||||||
|
choices = [
|
||||||
|
(None, '---------'),
|
||||||
|
(0, 'None'),
|
||||||
|
]
|
||||||
|
choices += [(r.pk, r.name) for r in RackRole.objects.all()]
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Sites
|
# Sites
|
||||||
#
|
#
|
||||||
@ -124,6 +148,18 @@ class RackGroupFilterForm(forms.Form, BootstrapMixin):
|
|||||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rack roles
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackRoleForm(forms.ModelForm, BootstrapMixin):
|
||||||
|
slug = SlugField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RackRole
|
||||||
|
fields = ['name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
@ -136,7 +172,7 @@ class RackForm(forms.ModelForm, BootstrapMixin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height', 'comments']
|
fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'role', '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",
|
||||||
@ -166,11 +202,13 @@ 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.'})
|
||||||
|
role = forms.ModelChoiceField(RackRole.objects.all(), to_field_name='name', required=False,
|
||||||
|
error_messages={'invalid_choice': 'Role not found.'})
|
||||||
type = forms.CharField(required=False)
|
type = forms.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'type', 'width', 'u_height']
|
fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height']
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
@ -204,9 +242,10 @@ class RackImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class RackBulkEditForm(forms.Form, BootstrapMixin):
|
class RackBulkEditForm(forms.Form, BootstrapMixin):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
|
||||||
group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False)
|
group = forms.TypedChoiceField(choices=bulkedit_rackgroup_choices, coerce=int, required=False, label='Group')
|
||||||
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')
|
||||||
|
role = forms.TypedChoiceField(choices=bulkedit_rackrole_choices, coerce=int, required=False, label='Role')
|
||||||
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
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')
|
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)')
|
||||||
@ -228,6 +267,11 @@ def rack_tenant_choices():
|
|||||||
return [(t.slug, u'{} ({})'.format(t.name, t.rack_count)) for t in tenant_choices]
|
return [(t.slug, u'{} ({})'.format(t.name, t.rack_count)) for t in tenant_choices]
|
||||||
|
|
||||||
|
|
||||||
|
def rack_role_choices():
|
||||||
|
role_choices = RackRole.objects.annotate(rack_count=Count('racks'))
|
||||||
|
return [(r.slug, u'{} ({})'.format(r.name, r.rack_count)) for r in role_choices]
|
||||||
|
|
||||||
|
|
||||||
class RackFilterForm(forms.Form, BootstrapMixin):
|
class RackFilterForm(forms.Form, BootstrapMixin):
|
||||||
site = forms.MultipleChoiceField(required=False, choices=rack_site_choices,
|
site = forms.MultipleChoiceField(required=False, choices=rack_site_choices,
|
||||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
@ -235,6 +279,8 @@ class RackFilterForm(forms.Form, BootstrapMixin):
|
|||||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
tenant = forms.MultipleChoiceField(required=False, choices=rack_tenant_choices,
|
tenant = forms.MultipleChoiceField(required=False, choices=rack_tenant_choices,
|
||||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
|
role = forms.MultipleChoiceField(required=False, choices=rack_role_choices,
|
||||||
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
33
netbox/dcim/migrations/0017_rack_add_role.py
Normal file
33
netbox/dcim/migrations/0017_rack_add_role.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.8 on 2016-08-10 14:58
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0016_module_add_manufacturer'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RackRole',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=50, unique=True)),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('color', models.CharField(choices=[[b'teal', b'Teal'], [b'green', b'Green'], [b'blue', b'Blue'], [b'purple', b'Purple'], [b'yellow', b'Yellow'], [b'orange', b'Orange'], [b'red', b'Red'], [b'light_gray', b'Light Gray'], [b'medium_gray', b'Medium Gray'], [b'dark_gray', b'Dark Gray']], max_length=30)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rack',
|
||||||
|
name='role',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.RackRole'),
|
||||||
|
),
|
||||||
|
]
|
@ -61,7 +61,7 @@ COLOR_RED = 'red'
|
|||||||
COLOR_GRAY1 = 'light_gray'
|
COLOR_GRAY1 = 'light_gray'
|
||||||
COLOR_GRAY2 = 'medium_gray'
|
COLOR_GRAY2 = 'medium_gray'
|
||||||
COLOR_GRAY3 = 'dark_gray'
|
COLOR_GRAY3 = 'dark_gray'
|
||||||
DEVICE_ROLE_COLOR_CHOICES = [
|
ROLE_COLOR_CHOICES = [
|
||||||
[COLOR_TEAL, 'Teal'],
|
[COLOR_TEAL, 'Teal'],
|
||||||
[COLOR_GREEN, 'Green'],
|
[COLOR_GREEN, 'Green'],
|
||||||
[COLOR_BLUE, 'Blue'],
|
[COLOR_BLUE, 'Blue'],
|
||||||
@ -203,6 +203,10 @@ def order_interfaces(queryset, sql_col, primary_ordering=tuple()):
|
|||||||
}).order_by(*ordering)
|
}).order_by(*ordering)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sites
|
||||||
|
#
|
||||||
|
|
||||||
class SiteManager(NaturalOrderByManager):
|
class SiteManager(NaturalOrderByManager):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -264,6 +268,10 @@ class Site(CreatedUpdatedModel):
|
|||||||
return self.circuits.count()
|
return self.circuits.count()
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Racks
|
||||||
|
#
|
||||||
|
|
||||||
class RackGroup(models.Model):
|
class RackGroup(models.Model):
|
||||||
"""
|
"""
|
||||||
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
|
Racks can be grouped as subsets within a Site. The scope of a group will depend on how Sites are defined. For
|
||||||
@ -288,6 +296,24 @@ class RackGroup(models.Model):
|
|||||||
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class RackRole(models.Model):
|
||||||
|
"""
|
||||||
|
Racks can be organized by functional role, similar to Devices.
|
||||||
|
"""
|
||||||
|
name = models.CharField(max_length=50, unique=True)
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return "{}?role={}".format(reverse('dcim:rack_list'), self.slug)
|
||||||
|
|
||||||
|
|
||||||
class RackManager(NaturalOrderByManager):
|
class RackManager(NaturalOrderByManager):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -304,6 +330,7 @@ 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)
|
||||||
|
role = models.ForeignKey('RackRole', related_name='racks', blank=True, null=True, on_delete=models.PROTECT)
|
||||||
type = models.PositiveSmallIntegerField(choices=RACK_TYPE_CHOICES, blank=True, null=True, verbose_name='Type')
|
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',
|
width = models.PositiveSmallIntegerField(choices=RACK_WIDTH_CHOICES, default=RACK_WIDTH_19IN, verbose_name='Width',
|
||||||
help_text='Rail-to-rail width')
|
help_text='Rail-to-rail width')
|
||||||
@ -344,6 +371,9 @@ class Rack(CreatedUpdatedModel):
|
|||||||
self.name,
|
self.name,
|
||||||
self.facility_id or '',
|
self.facility_id or '',
|
||||||
self.tenant.name if self.tenant else '',
|
self.tenant.name if self.tenant else '',
|
||||||
|
self.role.name if self.role else '',
|
||||||
|
self.get_type_display() if self.type else '',
|
||||||
|
self.width,
|
||||||
str(self.u_height),
|
str(self.u_height),
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -651,7 +681,7 @@ class DeviceRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
color = models.CharField(max_length=30, choices=DEVICE_ROLE_COLOR_CHOICES)
|
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -22,6 +22,12 @@ RACKGROUP_ACTIONS = """
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
RACKROLE_ACTIONS = """
|
||||||
|
{% if perms.dcim.change_rackrole %}
|
||||||
|
<a href="{% url 'dcim:rackrole_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
|
||||||
DEVICEROLE_ACTIONS = """
|
DEVICEROLE_ACTIONS = """
|
||||||
{% if perms.dcim.change_devicerole %}
|
{% if perms.dcim.change_devicerole %}
|
||||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||||
@ -94,6 +100,24 @@ class RackGroupTable(BaseTable):
|
|||||||
fields = ('pk', 'name', 'site', 'rack_count', 'slug', 'actions')
|
fields = ('pk', 'name', 'site', 'rack_count', 'slug', 'actions')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rack roles
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackRoleTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
|
rack_count = tables.Column(verbose_name='Racks')
|
||||||
|
color = tables.Column(verbose_name='Color')
|
||||||
|
slug = tables.Column(verbose_name='Slug')
|
||||||
|
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||||
|
verbose_name='')
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = RackGroup
|
||||||
|
fields = ('pk', 'name', 'rack_count', 'color', 'slug', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
@ -105,6 +129,7 @@ class RackTable(BaseTable):
|
|||||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||||
facility_id = tables.Column(verbose_name='Facility ID')
|
facility_id = tables.Column(verbose_name='Facility ID')
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||||
|
role = tables.Column(verbose_name='Role')
|
||||||
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
||||||
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
|
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
|
||||||
u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
|
u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
|
||||||
@ -112,7 +137,7 @@ class RackTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'u_height', 'devices', 'u_consumed',
|
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height', 'devices', 'u_consumed',
|
||||||
'utilization')
|
'utilization')
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ class SiteTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'role',
|
||||||
'type',
|
'type',
|
||||||
'width',
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
@ -120,6 +121,7 @@ class RackTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'role',
|
||||||
'type',
|
'type',
|
||||||
'width',
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
@ -134,6 +136,7 @@ class RackTest(APITestCase):
|
|||||||
'site',
|
'site',
|
||||||
'group',
|
'group',
|
||||||
'tenant',
|
'tenant',
|
||||||
|
'role',
|
||||||
'type',
|
'type',
|
||||||
'width',
|
'width',
|
||||||
'u_height',
|
'u_height',
|
||||||
|
@ -26,6 +26,12 @@ urlpatterns = [
|
|||||||
url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
|
url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
|
||||||
url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
|
url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
|
||||||
|
|
||||||
|
# Rack roles
|
||||||
|
url(r'^rack-roles/$', views.RackRoleListView.as_view(), name='rackrole_list'),
|
||||||
|
url(r'^rack-roles/add/$', views.RackRoleEditView.as_view(), name='rackrole_add'),
|
||||||
|
url(r'^rack-roles/delete/$', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'),
|
||||||
|
url(r'^rack-roles/(?P<pk>\d+)/edit/$', views.RackRoleEditView.as_view(), name='rackrole_edit'),
|
||||||
|
|
||||||
# Racks
|
# Racks
|
||||||
url(r'^racks/$', views.RackListView.as_view(), name='rack_list'),
|
url(r'^racks/$', views.RackListView.as_view(), name='rack_list'),
|
||||||
url(r'^racks/add/$', views.RackEditView.as_view(), name='rack_add'),
|
url(r'^racks/add/$', views.RackEditView.as_view(), name='rack_add'),
|
||||||
|
@ -26,7 +26,7 @@ from .models import (
|
|||||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||||
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||||
Site,
|
RackRole, Site,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -158,6 +158,31 @@ class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
default_redirect_url = 'dcim:rackgroup_list'
|
default_redirect_url = 'dcim:rackgroup_list'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rack roles
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackRoleListView(ObjectListView):
|
||||||
|
queryset = RackRole.objects.annotate(rack_count=Count('racks'))
|
||||||
|
table = tables.RackRoleTable
|
||||||
|
edit_permissions = ['dcim.change_rackrole', 'dcim.delete_rackrole']
|
||||||
|
template_name = 'dcim/rackrole_list.html'
|
||||||
|
|
||||||
|
|
||||||
|
class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
|
permission_required = 'dcim.change_rackrole'
|
||||||
|
model = RackRole
|
||||||
|
form_class = forms.RackRoleForm
|
||||||
|
success_url = 'dcim:rackrole_list'
|
||||||
|
cancel_url = 'dcim:rackrole_list'
|
||||||
|
|
||||||
|
|
||||||
|
class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
|
permission_required = 'dcim.delete_rackrole'
|
||||||
|
cls = RackRole
|
||||||
|
default_redirect_url = 'dcim:rackrole_list'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
@ -223,11 +248,12 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
def update_objects(self, pk_list, form):
|
def update_objects(self, pk_list, form):
|
||||||
|
|
||||||
fields_to_update = {}
|
fields_to_update = {}
|
||||||
if form.cleaned_data['tenant'] == 0:
|
for field in ['group', 'tenant', 'role']:
|
||||||
fields_to_update['tenant'] = None
|
if form.cleaned_data[field] == 0:
|
||||||
elif form.cleaned_data['tenant']:
|
fields_to_update[field] = None
|
||||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
elif form.cleaned_data[field]:
|
||||||
for field in ['site', 'group', 'tenant', 'type', 'width', 'u_height', 'comments']:
|
fields_to_update[field] = form.cleaned_data[field]
|
||||||
|
for field in ['site', '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]
|
||||||
|
|
||||||
|
@ -61,6 +61,11 @@
|
|||||||
{% if perms.dcim.add_rackgroup %}
|
{% if perms.dcim.add_rackgroup %}
|
||||||
<li><a href="{% url 'dcim:rackgroup_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
|
<li><a href="{% url 'dcim:rackgroup_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li><a href="{% url 'dcim:rackrole_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Rack Roles</a></li>
|
||||||
|
{% if perms.dcim.add_rackrole %}
|
||||||
|
<li><a href="{% url 'dcim:rackrole_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack Role</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown{% if request.path|startswith:'/dcim/device' or request.path|startswith:'/dcim/manufacturers/' or request.path|startswith:'/dcim/platforms/' %} active{% endif %}">
|
<li class="dropdown{% if request.path|startswith:'/dcim/device' or request.path|startswith:'/dcim/manufacturers/' or request.path|startswith:'/dcim/platforms/' %} active{% endif %}">
|
||||||
|
21
netbox/templates/dcim/rackrole_list.html
Normal file
21
netbox/templates/dcim/rackrole_list.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block title %}Rack Role{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if perms.dcim.add_rackrole %}
|
||||||
|
<a href="{% url 'dcim:rackrole_add' %}" class="btn btn-primary">
|
||||||
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
|
Add a rack role
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<h1>Rack Roles</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackrole_bulk_delete' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Reference in New Issue
Block a user