Browse Source

Closes #1758: Added 'status' field to Site model

Jeremy Stretch 7 years ago
parent
commit
ed10a99771

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

@@ -8,7 +8,7 @@ from rest_framework.validators import UniqueTogetherValidator
 from circuits.models import Circuit, CircuitTermination
 from dcim.constants import (
     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 (
     ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@@ -56,6 +56,7 @@ class WritableRegionSerializer(ValidatedModelSerializer):
 #
 
 class SiteSerializer(CustomFieldModelSerializer):
+    status = ChoiceFieldSerializer(choices=SITE_STATUS_CHOICES)
     region = NestedRegionSerializer()
     tenant = NestedTenantSerializer()
     time_zone = TimeZoneField(required=False)
@@ -63,7 +64,7 @@ class SiteSerializer(CustomFieldModelSerializer):
     class Meta:
         model = Site
         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',
             'created', 'last_updated', 'count_prefixes', 'count_vlans', 'count_racks', 'count_devices',
             'count_circuits',
@@ -84,7 +85,7 @@ class WritableSiteSerializer(CustomFieldModelSerializer):
     class Meta:
         model = Site
         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',
             'created', 'last_updated',
         ]

+ 12 - 2
netbox/dcim/constants.py

@@ -218,8 +218,18 @@ DEVICE_STATUS_CHOICES = [
     [DEVICE_STATUS_INVENTORY, 'Inventory'],
 ]
 
-# Bootstrap CSS classes for device stasuses
-DEVICE_STATUS_CLASSES = {
+# Site statuses
+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',
     1: 'success',
     2: 'info',

+ 1 - 1
netbox/dcim/filters.py

@@ -67,7 +67,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
 
     class Meta:
         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):
         if not value.strip():

+ 18 - 4
netbox/dcim/forms.py

@@ -24,7 +24,7 @@ from virtualization.models import Cluster
 from .constants import (
     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,
-    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,
 )
 from .formfields import MACAddressFormField
@@ -104,7 +104,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
     class Meta:
         model = Site
         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',
         ]
         widgets = {
@@ -121,6 +121,11 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
 
 
 class SiteCSVForm(forms.ModelForm):
+    status = CSVChoiceField(
+        choices=DEVICE_STATUS_CHOICES,
+        required=False,
+        help_text='Operational status'
+    )
     region = forms.ModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -143,7 +148,7 @@ class SiteCSVForm(forms.ModelForm):
     class Meta:
         model = Site
         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',
         ]
         help_texts = {
@@ -155,6 +160,7 @@ class SiteCSVForm(forms.ModelForm):
 
 class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
     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)
     tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
     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']
 
 
+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):
     model = Site
     q = forms.CharField(required=False, label='Search')
+    status = forms.MultipleChoiceField(choices=site_status_choices, required=False)
     region = FilterTreeNodeMultipleChoiceField(
         queryset=Region.objects.annotate(filter_count=Count('sites')),
         to_field_name='slug',
@@ -889,7 +903,7 @@ class BaseDeviceCSVForm(forms.ModelForm):
     )
     status = CSVChoiceField(
         choices=DEVICE_STATUS_CHOICES,
-        help_text='Operational status of device'
+        help_text='Operational status'
     )
 
     class Meta:

+ 7 - 2
netbox/dcim/migrations/0054_site_time_zone.py

@@ -1,8 +1,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 django.db import migrations
+from django.db import migrations, models
 import timezone_field.fields
 
 
@@ -15,6 +15,11 @@ class Migration(migrations.Migration):
     operations = [
         migrations.AddField(
             model_name='site',
+            name='status',
+            field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [2, 'Planned'], [4, 'Retired']], default=1),
+        ),
+        migrations.AddField(
+            model_name='site',
             name='time_zone',
             field=timezone_field.fields.TimeZoneField(blank=True),
         ),

+ 7 - 2
netbox/dcim/models.py

@@ -83,6 +83,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
     """
     name = models.CharField(max_length=50, 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)
     tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT)
     facility = models.CharField(max_length=50, blank=True)
@@ -100,7 +101,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
     objects = SiteManager()
 
     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',
     ]
 
@@ -117,6 +118,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
         return csv_format([
             self.name,
             self.slug,
+            self.get_status_display(),
             self.region.name if self.region else None,
             self.tenant.name if self.tenant else None,
             self.facility,
@@ -127,6 +129,9 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
             self.contact_email,
         ])
 
+    def get_status_class(self):
+        return STATUS_CLASSES[self.status]
+
     @property
     def count_prefixes(self):
         return self.prefixes.count()
@@ -1088,7 +1093,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
         return Device.objects.filter(parent_bay__device=self.pk)
 
     def get_status_class(self):
-        return DEVICE_STATUS_CLASSES[self.status]
+        return STATUS_CLASSES[self.status]
 
     def get_rpc_client(self):
         """

+ 6 - 5
netbox/dcim/tables.py

@@ -92,7 +92,7 @@ DEVICE_ROLE = """
 <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>
 """
 
@@ -145,12 +145,13 @@ class RegionTable(BaseTable):
 class SiteTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
+    status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
     region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
     tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
 
     class Meta(BaseTable.Meta):
         model = Site
-        fields = ('pk', 'name', 'facility', 'region', 'tenant', 'asn')
+        fields = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn')
 
 
 class SiteDetailTable(SiteTable):
@@ -163,7 +164,7 @@ class SiteDetailTable(SiteTable):
 
     class Meta(SiteTable.Meta):
         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',
         )
 
@@ -409,7 +410,7 @@ class PlatformTable(BaseTable):
 class DeviceTable(BaseTable):
     pk = ToggleColumn()
     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')])
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
@@ -436,7 +437,7 @@ class DeviceDetailTable(DeviceTable):
 
 class DeviceImportTable(BaseTable):
     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')
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')

+ 6 - 0
netbox/templates/dcim/site.html

@@ -59,6 +59,12 @@
             </div>
             <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>
                     <td>Region</td>
                     <td>
                         {% if site.region %}

+ 1 - 0
netbox/templates/dcim/site_edit.html

@@ -7,6 +7,7 @@
         <div class="panel-body">
             {% render_field form.name %}
             {% render_field form.slug %}
+            {% render_field form.status %}
             {% render_field form.region %}
             {% render_field form.facility %}
             {% render_field form.asn %}