diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 51a5ec878..db550a63b 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -2,11 +2,12 @@ from __future__ import unicode_literals from rest_framework import serializers +from circuits.constants import CIRCUIT_STATUS_CHOICES from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer from extras.api.customfields import CustomFieldModelSerializer from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ValidatedModelSerializer +from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer # @@ -66,14 +67,15 @@ class NestedCircuitTypeSerializer(serializers.ModelSerializer): class CircuitSerializer(CustomFieldModelSerializer): provider = NestedProviderSerializer() + status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES) type = NestedCircuitTypeSerializer() tenant = NestedTenantSerializer() class Meta: model = Circuit fields = [ - 'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', - 'custom_fields', 'created', 'last_updated', + 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', + 'comments', 'custom_fields', 'created', 'last_updated', ] @@ -90,8 +92,8 @@ class WritableCircuitSerializer(CustomFieldModelSerializer): class Meta: model = Circuit fields = [ - 'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', - 'custom_fields', 'created', 'last_updated', + 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', + 'comments', 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/circuits/constants.py b/netbox/circuits/constants.py index 816e28e4e..c13975b06 100644 --- a/netbox/circuits/constants.py +++ b/netbox/circuits/constants.py @@ -1,6 +1,22 @@ from __future__ import unicode_literals +# Circuit statuses +CIRCUIT_STATUS_DEPROVISIONING = 0 +CIRCUIT_STATUS_ACTIVE = 1 +CIRCUIT_STATUS_PLANNED = 2 +CIRCUIT_STATUS_PROVISIONING = 3 +CIRCUIT_STATUS_OFFLINE = 4 +CIRCUIT_STATUS_DECOMMISSIONED = 5 +CIRCUIT_STATUS_CHOICES = [ + [CIRCUIT_STATUS_PLANNED, 'Planned'], + [CIRCUIT_STATUS_PROVISIONING, 'Provisioning'], + [CIRCUIT_STATUS_ACTIVE, 'Active'], + [CIRCUIT_STATUS_OFFLINE, 'Offline'], + [CIRCUIT_STATUS_DEPROVISIONING, 'Deprovisioning'], + [CIRCUIT_STATUS_DECOMMISSIONED, 'Decommissioned'], +] + # CircuitTermination sides TERM_SIDE_A = 'A' TERM_SIDE_Z = 'Z' diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index d4b84fc60..ca66be406 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -7,6 +7,7 @@ from dcim.models import Site from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.filters import NumericInFilter +from .constants import CIRCUIT_STATUS_CHOICES from .models import Provider, Circuit, CircuitTermination, CircuitType @@ -77,6 +78,10 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet): to_field_name='slug', label='Circuit type (slug)', ) + status = django_filters.MultipleChoiceFilter( + choices=CIRCUIT_STATUS_CHOICES, + null_value=None + ) tenant_id = django_filters.ModelMultipleChoiceFilter( queryset=Tenant.objects.all(), label='Tenant (ID)', diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 8acad4bb9..22bbbe464 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -8,9 +8,10 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField, - SmallTextarea, SlugField, + APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, + CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField, ) +from .constants import CIRCUIT_STATUS_CHOICES from .models import Circuit, CircuitTermination, CircuitType, Provider @@ -105,7 +106,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm): class Meta: model = Circuit fields = [ - 'cid', 'type', 'provider', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant', + 'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant', 'comments', ] help_texts = { @@ -132,6 +133,11 @@ class CircuitCSVForm(forms.ModelForm): 'invalid_choice': 'Invalid circuit type.' } ) + status = CSVChoiceField( + choices=CIRCUIT_STATUS_CHOICES, + required=False, + help_text='Operational status' + ) tenant = forms.ModelChoiceField( queryset=Tenant.objects.all(), required=False, @@ -144,13 +150,16 @@ class CircuitCSVForm(forms.ModelForm): class Meta: model = Circuit - fields = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments'] + fields = [ + 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', + ] class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput) type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False) provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False) + status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='') tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)') description = forms.CharField(max_length=100, required=False) @@ -160,6 +169,13 @@ class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): nullable_fields = ['tenant', 'commit_rate', 'description', 'comments'] +def circuit_status_choices(): + status_counts = {} + for status in Circuit.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 CIRCUIT_STATUS_CHOICES] + + class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Circuit q = forms.CharField(required=False, label='Search') @@ -171,6 +187,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm): queryset=Provider.objects.annotate(filter_count=Count('circuits')), to_field_name='slug' ) + status = forms.MultipleChoiceField(choices=circuit_status_choices, required=False) tenant = FilterChoiceField( queryset=Tenant.objects.annotate(filter_count=Count('circuits')), to_field_name='slug', diff --git a/netbox/circuits/migrations/0010_circuit_status.py b/netbox/circuits/migrations/0010_circuit_status.py new file mode 100644 index 000000000..3abe5d319 --- /dev/null +++ b/netbox/circuits/migrations/0010_circuit_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.9 on 2018-02-06 18:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0009_unicode_literals'), + ] + + operations = [ + migrations.AddField( + model_name='circuit', + name='status', + field=models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1), + ), + ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index fd8a2b2f6..06cd48a25 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -5,12 +5,13 @@ from django.db import models from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from dcim.constants import STATUS_CLASSES from dcim.fields import ASNField from extras.models import CustomFieldModel, CustomFieldValue from tenancy.models import Tenant from utilities.models import CreatedUpdatedModel from utilities.utils import csv_format -from .constants import * +from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES @python_2_unicode_compatible @@ -79,6 +80,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel): cid = models.CharField(max_length=50, verbose_name='Circuit ID') provider = models.ForeignKey('Provider', related_name='circuits', on_delete=models.PROTECT) type = models.ForeignKey('CircuitType', related_name='circuits', on_delete=models.PROTECT) + status = models.PositiveSmallIntegerField(choices=CIRCUIT_STATUS_CHOICES, default=CIRCUIT_STATUS_ACTIVE) tenant = models.ForeignKey(Tenant, related_name='circuits', blank=True, null=True, on_delete=models.PROTECT) install_date = models.DateField(blank=True, null=True, verbose_name='Date installed') commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)') @@ -86,7 +88,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel): comments = models.TextField(blank=True) custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') - csv_headers = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description'] + csv_headers = ['cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description'] class Meta: ordering = ['provider', 'cid'] @@ -103,12 +105,16 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel): self.cid, self.provider.name, self.type.name, + self.get_status_display(), self.tenant.name if self.tenant else None, self.install_date.isoformat() if self.install_date else None, self.commit_rate, self.description, ]) + def get_status_class(self): + return STATUS_CLASSES[self.status] + def _get_termination(self, side): for ct in self.terminations.all(): if ct.term_side == side: diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 9a5225d56..46dac3c31 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -14,6 +14,10 @@ CIRCUITTYPE_ACTIONS = """ {% endif %} """ +STATUS_LABEL = """ +{{ record.get_status_display }} +""" + class CircuitTerminationColumn(tables.Column): @@ -76,10 +80,11 @@ class CircuitTable(BaseTable): pk = ToggleColumn() cid = tables.LinkColumn(verbose_name='ID') provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')]) + status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status') tenant = tables.TemplateColumn(template_code=COL_TENANT) termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side') termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side') class Meta(BaseTable.Meta): model = Circuit - fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'termination_a', 'termination_z', 'description') + fields = ('pk', 'cid', 'status', 'type', 'provider', 'tenant', 'termination_a', 'termination_z', 'description') diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 027efa83c..1133f41f3 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -46,6 +46,12 @@ Circuit
Status | ++ {{ circuit.get_status_display }} + | +
Provider |
diff --git a/netbox/templates/circuits/circuit_edit.html b/netbox/templates/circuits/circuit_edit.html
index 63d38ef52..8503e68f6 100644
--- a/netbox/templates/circuits/circuit_edit.html
+++ b/netbox/templates/circuits/circuit_edit.html
@@ -8,6 +8,7 @@
{% render_field form.provider %}
{% render_field form.cid %}
{% render_field form.type %}
+ {% render_field form.status %}
{% render_field form.install_date %}
|