diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 39a0b6b26..fb63654b1 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField -from circuits.constants import CIRCUIT_STATUS_CHOICES +from circuits.choices import CircuitStatusChoices from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer from dcim.api.serializers import ConnectedEndpointSerializer @@ -41,7 +41,7 @@ class CircuitTypeSerializer(ValidatedModelSerializer): class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer): provider = NestedProviderSerializer() - status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False) + status = ChoiceField(choices=CircuitStatusChoices, required=False) type = NestedCircuitTypeSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True) tags = TagListSerializerField(required=False) diff --git a/netbox/circuits/choices.py b/netbox/circuits/choices.py new file mode 100644 index 000000000..f562e9837 --- /dev/null +++ b/netbox/circuits/choices.py @@ -0,0 +1,29 @@ +from utilities.choices import ChoiceSet + + +class CircuitStatusChoices(ChoiceSet): + + STATUS_DEPROVISIONING = 'deprovisioning' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_PROVISIONING = 'provisioning' + STATUS_OFFLINE = 'offline' + STATUS_DECOMMISSIONED = 'decommissioned' + + CHOICES = ( + (STATUS_PLANNED, 'Planned'), + (STATUS_PROVISIONING, 'Provisioning'), + (STATUS_ACTIVE, 'Active'), + (STATUS_OFFLINE, 'Offline'), + (STATUS_DEPROVISIONING, 'Deprovisioning'), + (STATUS_DECOMMISSIONED, 'Decommissioned'), + ) + + LEGACY_MAP = { + STATUS_DEPROVISIONING: 0, + STATUS_ACTIVE: 1, + STATUS_PLANNED: 2, + STATUS_PROVISIONING: 3, + STATUS_OFFLINE: 4, + STATUS_DECOMMISSIONED: 5, + } diff --git a/netbox/circuits/constants.py b/netbox/circuits/constants.py index 9e180e655..7c26de754 100644 --- a/netbox/circuits/constants.py +++ b/netbox/circuits/constants.py @@ -1,19 +1,3 @@ -# 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 088ec144a..a70a3aae2 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -5,6 +5,7 @@ from dcim.models import Region, Site from extras.filters import CustomFieldFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter +from .choices import * from .constants import * from .models import Circuit, CircuitTermination, CircuitType, Provider @@ -84,7 +85,7 @@ class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet): label='Circuit type (slug)', ) status = django_filters.MultipleChoiceFilter( - choices=CIRCUIT_STATUS_CHOICES, + choices=CircuitStatusChoices, null_value=None ) site_id = django_filters.ModelMultipleChoiceFilter( diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index dfe4f46e4..e73504e20 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -9,6 +9,7 @@ from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple ) +from .choices import CircuitStatusChoices from .constants import * from .models import Circuit, CircuitTermination, CircuitType, Provider @@ -194,7 +195,7 @@ class CircuitCSVForm(forms.ModelForm): } ) status = CSVChoiceField( - choices=CIRCUIT_STATUS_CHOICES, + choices=CircuitStatusChoices, required=False, help_text='Operational status' ) @@ -235,7 +236,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ) ) status = forms.ChoiceField( - choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), + choices=add_blank_choice(CircuitStatusChoices), required=False, initial='', widget=StaticSelect2() @@ -292,7 +293,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm ) ) status = forms.MultipleChoiceField( - choices=CIRCUIT_STATUS_CHOICES, + choices=CircuitStatusChoices, required=False, widget=StaticSelect2Multiple() ) diff --git a/netbox/circuits/migrations/0016_circuit_status_slug.py b/netbox/circuits/migrations/0016_circuit_status_slug.py new file mode 100644 index 000000000..f4e4dbb3c --- /dev/null +++ b/netbox/circuits/migrations/0016_circuit_status_slug.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.6 on 2019-11-07 03:36 + +from django.db import migrations, models + + +CIRCUIT_STATUS_CHOICES = ( + (0, 'deprovisioning'), + (1, 'active'), + (2, 'planned'), + (3, 'provisioning'), + (4, 'offline'), + (5, 'decommissioned') +) + + +def circuit_status_to_slug(apps, schema_editor): + Circuit = apps.get_model('circuits', 'Circuit') + for id, slug in CIRCUIT_STATUS_CHOICES: + Circuit.objects.filter(status=str(id)).update(status=slug) + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0015_custom_tag_models'), + ] + + operations = [ + migrations.AlterField( + model_name='circuit', + name='status', + field=models.CharField(default='active', max_length=50), + ), + migrations.RunPython( + code=circuit_status_to_slug + ) + ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 8cf18617c..abc643db5 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -9,6 +9,7 @@ from dcim.models import CableTermination from extras.models import CustomFieldModel, ObjectChange, TaggedItem from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object +from .choices import * from .constants import * @@ -132,9 +133,10 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): on_delete=models.PROTECT, related_name='circuits' ) - status = models.PositiveSmallIntegerField( - choices=CIRCUIT_STATUS_CHOICES, - default=CIRCUIT_STATUS_ACTIVE + status = models.CharField( + max_length=50, + choices=CircuitStatusChoices, + default=CircuitStatusChoices.STATUS_ACTIVE ) tenant = models.ForeignKey( to='tenancy.Tenant', @@ -171,6 +173,15 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', ] + STATUS_CLASS_MAP = { + CircuitStatusChoices.STATUS_DEPROVISIONING: 'warning', + CircuitStatusChoices.STATUS_ACTIVE: 'success', + CircuitStatusChoices.STATUS_PLANNED: 'info', + CircuitStatusChoices.STATUS_PROVISIONING: 'primary', + CircuitStatusChoices.STATUS_OFFLINE: 'danger', + CircuitStatusChoices.STATUS_DECOMMISSIONED: 'default', + } + class Meta: ordering = ['provider', 'cid'] unique_together = ['provider', 'cid'] @@ -195,7 +206,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel): ) def get_status_class(self): - return STATUS_CLASSES[self.status] + return self.STATUS_CLASS_MAP.get(self.status) def _get_termination(self, side): for ct in self.terminations.all(): diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index e53c2c402..86bf814a8 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -1,9 +1,10 @@ from django.urls import reverse from rest_framework import status -from circuits.constants import CIRCUIT_STATUS_ACTIVE, TERM_SIDE_A, TERM_SIDE_Z +from circuits.choices import CircuitStatusChoices +from circuits.constants import TERM_SIDE_A, TERM_SIDE_Z from circuits.models import Circuit, CircuitTermination, CircuitType, Provider -from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site +from dcim.models import Site from extras.constants import GRAPH_TYPE_PROVIDER from extras.models import Graph from utilities.testing import APITestCase @@ -250,7 +251,7 @@ class CircuitTest(APITestCase): 'cid': 'TEST0004', 'provider': self.provider1.pk, 'type': self.circuittype1.pk, - 'status': CIRCUIT_STATUS_ACTIVE, + 'status': CircuitStatusChoices.STATUS_ACTIVE, } url = reverse('circuits-api:circuit-list') @@ -270,19 +271,19 @@ class CircuitTest(APITestCase): 'cid': 'TEST0004', 'provider': self.provider1.pk, 'type': self.circuittype1.pk, - 'status': CIRCUIT_STATUS_ACTIVE, + 'status': CircuitStatusChoices.STATUS_ACTIVE, }, { 'cid': 'TEST0005', 'provider': self.provider1.pk, 'type': self.circuittype1.pk, - 'status': CIRCUIT_STATUS_ACTIVE, + 'status': CircuitStatusChoices.STATUS_ACTIVE, }, { 'cid': 'TEST0006', 'provider': self.provider1.pk, 'type': self.circuittype1.pk, - 'status': CIRCUIT_STATUS_ACTIVE, + 'status': CircuitStatusChoices.STATUS_ACTIVE, }, ] diff --git a/netbox/utilities/choices.py b/netbox/utilities/choices.py index 367451dd5..7738aa7c0 100644 --- a/netbox/utilities/choices.py +++ b/netbox/utilities/choices.py @@ -3,7 +3,7 @@ class ChoiceSetMeta(type): Metaclass for ChoiceSet """ def __call__(cls, *args, **kwargs): - # Django will check if a choices value is callable, and if so assume that it returns an iterable + # Django will check if a 'choices' value is callable, and if so assume that it returns an iterable return getattr(cls, 'CHOICES', ()) def __iter__(cls):