mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
Closes #1758: Added 'status' field to Site model
This commit is contained in:
parent
4df128d34e
commit
ed10a99771
@ -8,7 +8,7 @@ from rest_framework.validators import UniqueTogetherValidator
|
|||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from dcim.constants import (
|
from dcim.constants import (
|
||||||
CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
|
CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
|
||||||
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
||||||
)
|
)
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
@ -56,6 +56,7 @@ class WritableRegionSerializer(ValidatedModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class SiteSerializer(CustomFieldModelSerializer):
|
class SiteSerializer(CustomFieldModelSerializer):
|
||||||
|
status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES)
|
||||||
region = NestedRegionSerializer()
|
region = NestedRegionSerializer()
|
||||||
tenant = NestedTenantSerializer()
|
tenant = NestedTenantSerializer()
|
||||||
time_zone = TimeZoneField(required=False)
|
time_zone = TimeZoneField(required=False)
|
||||||
@ -63,7 +64,7 @@ class SiteSerializer(CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
||||||
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
||||||
'created', 'last_updated', 'count_prefixes', 'count_vlans', 'count_racks', 'count_devices',
|
'created', 'last_updated', 'count_prefixes', 'count_vlans', 'count_racks', 'count_devices',
|
||||||
'count_circuits',
|
'count_circuits',
|
||||||
@ -84,7 +85,7 @@ class WritableSiteSerializer(CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
'id', 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
||||||
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
||||||
'created', 'last_updated',
|
'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
@ -218,8 +218,18 @@ DEVICE_STATUS_CHOICES = [
|
|||||||
[DEVICE_STATUS_INVENTORY, 'Inventory'],
|
[DEVICE_STATUS_INVENTORY, 'Inventory'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Bootstrap CSS classes for device stasuses
|
# Site statuses
|
||||||
DEVICE_STATUS_CLASSES = {
|
SITE_STATUS_ACTIVE = 1
|
||||||
|
SITE_STATUS_PLANNED = 2
|
||||||
|
SITE_STATUS_RETIRED = 4
|
||||||
|
SITE_STATUS_CHOICES = [
|
||||||
|
[SITE_STATUS_ACTIVE, 'Active'],
|
||||||
|
[SITE_STATUS_PLANNED, 'Planned'],
|
||||||
|
[SITE_STATUS_RETIRED, 'Retired'],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Bootstrap CSS classes for device statuses
|
||||||
|
STATUS_CLASSES = {
|
||||||
0: 'warning',
|
0: 'warning',
|
||||||
1: 'success',
|
1: 'success',
|
||||||
2: 'info',
|
2: 'info',
|
||||||
|
@ -67,7 +67,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = ['q', 'name', 'slug', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email']
|
fields = ['q', 'name', 'slug', 'status', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -24,7 +24,7 @@ from virtualization.models import Cluster
|
|||||||
from .constants import (
|
from .constants import (
|
||||||
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_FF_LAG,
|
CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_FF_LAG,
|
||||||
IFACE_MODE_ACCESS, IFACE_MODE_CHOICES, IFACE_MODE_TAGGED_ALL, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES,
|
IFACE_MODE_ACCESS, IFACE_MODE_CHOICES, IFACE_MODE_TAGGED_ALL, IFACE_ORDERING_CHOICES, RACK_FACE_CHOICES,
|
||||||
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, SUBDEVICE_ROLE_CHILD,
|
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, RACK_WIDTH_19IN, RACK_WIDTH_23IN, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHILD,
|
||||||
SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES,
|
SUBDEVICE_ROLE_PARENT, SUBDEVICE_ROLE_CHOICES,
|
||||||
)
|
)
|
||||||
from .formfields import MACAddressFormField
|
from .formfields import MACAddressFormField
|
||||||
@ -104,7 +104,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'physical_address',
|
'name', 'slug', 'status', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'physical_address',
|
||||||
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
@ -121,6 +121,11 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
|
|
||||||
class SiteCSVForm(forms.ModelForm):
|
class SiteCSVForm(forms.ModelForm):
|
||||||
|
status = CSVChoiceField(
|
||||||
|
choices=DEVICE_STATUS_CHOICES,
|
||||||
|
required=False,
|
||||||
|
help_text='Operational status'
|
||||||
|
)
|
||||||
region = forms.ModelChoiceField(
|
region = forms.ModelChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -143,7 +148,7 @@ class SiteCSVForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
||||||
'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
@ -155,6 +160,7 @@ class SiteCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
|
status = forms.ChoiceField(choices=add_blank_choice(SITE_STATUS_CHOICES), required=False, initial='')
|
||||||
region = TreeNodeChoiceField(queryset=Region.objects.all(), required=False)
|
region = TreeNodeChoiceField(queryset=Region.objects.all(), required=False)
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
asn = forms.IntegerField(min_value=1, max_value=4294967295, required=False, label='ASN')
|
asn = forms.IntegerField(min_value=1, max_value=4294967295, required=False, label='ASN')
|
||||||
@ -164,9 +170,17 @@ class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['region', 'tenant', 'asn', 'time_zone']
|
nullable_fields = ['region', 'tenant', 'asn', 'time_zone']
|
||||||
|
|
||||||
|
|
||||||
|
def site_status_choices():
|
||||||
|
status_counts = {}
|
||||||
|
for status in Site.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
||||||
|
status_counts[status['status']] = status['count']
|
||||||
|
return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in SITE_STATUS_CHOICES]
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Site
|
model = Site
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
|
status = forms.MultipleChoiceField(choices=site_status_choices, required=False)
|
||||||
region = FilterTreeNodeMultipleChoiceField(
|
region = FilterTreeNodeMultipleChoiceField(
|
||||||
queryset=Region.objects.annotate(filter_count=Count('sites')),
|
queryset=Region.objects.annotate(filter_count=Count('sites')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -889,7 +903,7 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
choices=DEVICE_STATUS_CHOICES,
|
||||||
help_text='Operational status of device'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.6 on 2017-12-19 21:53
|
# Generated by Django 1.11.6 on 2018-01-25 17:57
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations, models
|
||||||
import timezone_field.fields
|
import timezone_field.fields
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +13,11 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='site',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [2, 'Planned'], [4, 'Retired']], default=1),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='site',
|
model_name='site',
|
||||||
name='time_zone',
|
name='time_zone',
|
@ -83,6 +83,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
"""
|
"""
|
||||||
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)
|
||||||
|
status = models.PositiveSmallIntegerField(choices=SITE_STATUS_CHOICES, default=SITE_STATUS_ACTIVE)
|
||||||
region = models.ForeignKey('Region', related_name='sites', blank=True, null=True, on_delete=models.SET_NULL)
|
region = models.ForeignKey('Region', related_name='sites', blank=True, null=True, on_delete=models.SET_NULL)
|
||||||
tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT)
|
tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT)
|
||||||
facility = models.CharField(max_length=50, blank=True)
|
facility = models.CharField(max_length=50, blank=True)
|
||||||
@ -100,7 +101,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
objects = SiteManager()
|
objects = SiteManager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'contact_name', 'contact_phone',
|
'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'contact_name', 'contact_phone',
|
||||||
'contact_email',
|
'contact_email',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -117,6 +118,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
return csv_format([
|
return csv_format([
|
||||||
self.name,
|
self.name,
|
||||||
self.slug,
|
self.slug,
|
||||||
|
self.get_status_display(),
|
||||||
self.region.name if self.region else None,
|
self.region.name if self.region else None,
|
||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
self.facility,
|
self.facility,
|
||||||
@ -127,6 +129,9 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.contact_email,
|
self.contact_email,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def get_status_class(self):
|
||||||
|
return STATUS_CLASSES[self.status]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def count_prefixes(self):
|
def count_prefixes(self):
|
||||||
return self.prefixes.count()
|
return self.prefixes.count()
|
||||||
@ -1088,7 +1093,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
return Device.objects.filter(parent_bay__device=self.pk)
|
return Device.objects.filter(parent_bay__device=self.pk)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return DEVICE_STATUS_CLASSES[self.status]
|
return STATUS_CLASSES[self.status]
|
||||||
|
|
||||||
def get_rpc_client(self):
|
def get_rpc_client(self):
|
||||||
"""
|
"""
|
||||||
|
@ -92,7 +92,7 @@ DEVICE_ROLE = """
|
|||||||
<label class="label" style="background-color: #{{ record.device_role.color }}">{{ value }}</label>
|
<label class="label" style="background-color: #{{ record.device_role.color }}">{{ value }}</label>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_STATUS = """
|
STATUS_LABEL = """
|
||||||
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
|
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -145,12 +145,13 @@ class RegionTable(BaseTable):
|
|||||||
class SiteTable(BaseTable):
|
class SiteTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||||
region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
|
region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Site
|
model = Site
|
||||||
fields = ('pk', 'name', 'facility', 'region', 'tenant', 'asn')
|
fields = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn')
|
||||||
|
|
||||||
|
|
||||||
class SiteDetailTable(SiteTable):
|
class SiteDetailTable(SiteTable):
|
||||||
@ -163,7 +164,7 @@ class SiteDetailTable(SiteTable):
|
|||||||
|
|
||||||
class Meta(SiteTable.Meta):
|
class Meta(SiteTable.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'facility', 'region', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
'pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
||||||
'vlan_count', 'circuit_count', 'vm_count',
|
'vlan_count', 'circuit_count', 'vm_count',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -409,7 +410,7 @@ class PlatformTable(BaseTable):
|
|||||||
class DeviceTable(BaseTable):
|
class DeviceTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.TemplateColumn(template_code=DEVICE_LINK)
|
name = tables.TemplateColumn(template_code=DEVICE_LINK)
|
||||||
status = tables.TemplateColumn(template_code=DEVICE_STATUS, verbose_name='Status')
|
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
|
||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
|
||||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
|
||||||
@ -436,7 +437,7 @@ class DeviceDetailTable(DeviceTable):
|
|||||||
|
|
||||||
class DeviceImportTable(BaseTable):
|
class DeviceImportTable(BaseTable):
|
||||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||||
status = tables.TemplateColumn(template_code=DEVICE_STATUS, verbose_name='Status')
|
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
|
||||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
|
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
|
||||||
|
@ -58,6 +58,12 @@
|
|||||||
<strong>Site</strong>
|
<strong>Site</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>
|
||||||
|
<span class="label label-{{ site.get_status_class }}">{{ site.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Region</td>
|
<td>Region</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.slug %}
|
{% render_field form.slug %}
|
||||||
|
{% render_field form.status %}
|
||||||
{% render_field form.region %}
|
{% render_field form.region %}
|
||||||
{% render_field form.facility %}
|
{% render_field form.facility %}
|
||||||
{% render_field form.asn %}
|
{% render_field form.asn %}
|
||||||
|
Loading…
Reference in New Issue
Block a user