Merge pull request #3732 from netbox-community/3569-api-choice-slugs

Replace API integer API choice values with slugs
This commit is contained in:
Jeremy Stretch 2019-12-05 17:53:47 -05:00 committed by GitHub
commit dcb2c4722c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2760 additions and 1401 deletions

View File

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField 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 circuits.models import Provider, Circuit, CircuitTermination, CircuitType
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
from dcim.api.serializers import ConnectedEndpointSerializer from dcim.api.serializers import ConnectedEndpointSerializer
@ -41,7 +41,7 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer): class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
provider = NestedProviderSerializer() provider = NestedProviderSerializer()
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False) status = ChoiceField(choices=CircuitStatusChoices, required=False)
type = NestedCircuitTypeSerializer() type = NestedCircuitTypeSerializer()
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)

View File

@ -0,0 +1,48 @@
from utilities.choices import ChoiceSet
#
# Circuits
#
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,
}
#
# CircuitTerminations
#
class CircuitTerminationSideChoices(ChoiceSet):
SIDE_A = 'A'
SIDE_Z = 'Z'
CHOICES = (
(SIDE_A, 'A'),
(SIDE_Z, 'Z')
)

View File

@ -1,23 +0,0 @@
# 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'
TERM_SIDE_CHOICES = (
(TERM_SIDE_A, 'A'),
(TERM_SIDE_Z, 'Z'),
)

View File

@ -5,7 +5,7 @@ from dcim.models import Region, Site
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import TenancyFilterSet
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
from .constants import * from .choices import *
from .models import Circuit, CircuitTermination, CircuitType, Provider from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -84,7 +84,7 @@ class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilter
label='Circuit type (slug)', label='Circuit type (slug)',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=CIRCUIT_STATUS_CHOICES, choices=CircuitStatusChoices,
null_value=None null_value=None
) )
site_id = django_filters.ModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(

View File

@ -9,7 +9,7 @@ from utilities.forms import (
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
) )
from .constants import * from .choices import CircuitStatusChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -194,7 +194,7 @@ class CircuitCSVForm(forms.ModelForm):
} }
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=CIRCUIT_STATUS_CHOICES, choices=CircuitStatusChoices,
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
@ -235,7 +235,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), choices=add_blank_choice(CircuitStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -292,7 +292,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=CIRCUIT_STATUS_CHOICES, choices=CircuitStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )

View File

@ -0,0 +1,39 @@
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):
atomic = False
dependencies = [
('circuits', '0015_custom_tag_models'),
]
operations = [
# Circuit.status
migrations.AlterField(
model_name='circuit',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=circuit_status_to_slug
),
]

View File

@ -3,13 +3,13 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dcim.constants import CONNECTION_STATUS_CHOICES, STATUS_CLASSES from dcim.constants import CONNECTION_STATUS_CHOICES
from dcim.fields import ASNField from dcim.fields import ASNField
from dcim.models import CableTermination from dcim.models import CableTermination
from extras.models import CustomFieldModel, ObjectChange, TaggedItem from extras.models import CustomFieldModel, ObjectChange, TaggedItem
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object from utilities.utils import serialize_object
from .constants import * from .choices import *
class Provider(ChangeLoggedModel, CustomFieldModel): class Provider(ChangeLoggedModel, CustomFieldModel):
@ -132,9 +132,10 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='circuits' related_name='circuits'
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=CIRCUIT_STATUS_CHOICES, max_length=50,
default=CIRCUIT_STATUS_ACTIVE choices=CircuitStatusChoices,
default=CircuitStatusChoices.STATUS_ACTIVE
) )
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
@ -171,6 +172,15 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', '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: class Meta:
ordering = ['provider', 'cid'] ordering = ['provider', 'cid']
unique_together = ['provider', 'cid'] unique_together = ['provider', 'cid']
@ -195,7 +205,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
) )
def get_status_class(self): def get_status_class(self):
return STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
def _get_termination(self, side): def _get_termination(self, side):
for ct in self.terminations.all(): for ct in self.terminations.all():
@ -220,7 +230,7 @@ class CircuitTermination(CableTermination):
) )
term_side = models.CharField( term_side = models.CharField(
max_length=1, max_length=1,
choices=TERM_SIDE_CHOICES, choices=CircuitTerminationSideChoices,
verbose_name='Termination' verbose_name='Termination'
) )
site = models.ForeignKey( site = models.ForeignKey(

View File

@ -1,9 +1,9 @@
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
from circuits.constants import CIRCUIT_STATUS_ACTIVE, TERM_SIDE_A, TERM_SIDE_Z from circuits.choices import *
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider 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.constants import GRAPH_TYPE_PROVIDER
from extras.models import Graph from extras.models import Graph
from utilities.testing import APITestCase from utilities.testing import APITestCase
@ -250,7 +250,7 @@ class CircuitTest(APITestCase):
'cid': 'TEST0004', 'cid': 'TEST0004',
'provider': self.provider1.pk, 'provider': self.provider1.pk,
'type': self.circuittype1.pk, 'type': self.circuittype1.pk,
'status': CIRCUIT_STATUS_ACTIVE, 'status': CircuitStatusChoices.STATUS_ACTIVE,
} }
url = reverse('circuits-api:circuit-list') url = reverse('circuits-api:circuit-list')
@ -270,19 +270,19 @@ class CircuitTest(APITestCase):
'cid': 'TEST0004', 'cid': 'TEST0004',
'provider': self.provider1.pk, 'provider': self.provider1.pk,
'type': self.circuittype1.pk, 'type': self.circuittype1.pk,
'status': CIRCUIT_STATUS_ACTIVE, 'status': CircuitStatusChoices.STATUS_ACTIVE,
}, },
{ {
'cid': 'TEST0005', 'cid': 'TEST0005',
'provider': self.provider1.pk, 'provider': self.provider1.pk,
'type': self.circuittype1.pk, 'type': self.circuittype1.pk,
'status': CIRCUIT_STATUS_ACTIVE, 'status': CircuitStatusChoices.STATUS_ACTIVE,
}, },
{ {
'cid': 'TEST0006', 'cid': 'TEST0006',
'provider': self.provider1.pk, 'provider': self.provider1.pk,
'type': self.circuittype1.pk, 'type': self.circuittype1.pk,
'status': CIRCUIT_STATUS_ACTIVE, 'status': CircuitStatusChoices.STATUS_ACTIVE,
}, },
] ]
@ -336,16 +336,28 @@ class CircuitTerminationTest(APITestCase):
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype) self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype) self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
self.circuittermination1 = CircuitTermination.objects.create( self.circuittermination1 = CircuitTermination.objects.create(
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000 circuit=self.circuit1,
term_side=CircuitTerminationSideChoices.SIDE_A,
site=self.site1,
port_speed=1000000
) )
self.circuittermination2 = CircuitTermination.objects.create( self.circuittermination2 = CircuitTermination.objects.create(
circuit=self.circuit1, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000 circuit=self.circuit1,
term_side=CircuitTerminationSideChoices.SIDE_Z,
site=self.site2,
port_speed=1000000
) )
self.circuittermination3 = CircuitTermination.objects.create( self.circuittermination3 = CircuitTermination.objects.create(
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000 circuit=self.circuit2,
term_side=CircuitTerminationSideChoices.SIDE_A,
site=self.site1,
port_speed=1000000
) )
self.circuittermination4 = CircuitTermination.objects.create( self.circuittermination4 = CircuitTermination.objects.create(
circuit=self.circuit2, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000 circuit=self.circuit2,
term_side=CircuitTerminationSideChoices.SIDE_Z,
site=self.site2,
port_speed=1000000
) )
def test_get_circuittermination(self): def test_get_circuittermination(self):
@ -366,7 +378,7 @@ class CircuitTerminationTest(APITestCase):
data = { data = {
'circuit': self.circuit3.pk, 'circuit': self.circuit3.pk,
'term_side': TERM_SIDE_A, 'term_side': CircuitTerminationSideChoices.SIDE_A,
'site': self.site1.pk, 'site': self.site1.pk,
'port_speed': 1000000, 'port_speed': 1000000,
} }
@ -385,12 +397,15 @@ class CircuitTerminationTest(APITestCase):
def test_update_circuittermination(self): def test_update_circuittermination(self):
circuittermination5 = CircuitTermination.objects.create( circuittermination5 = CircuitTermination.objects.create(
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000 circuit=self.circuit3,
term_side=CircuitTerminationSideChoices.SIDE_A,
site=self.site1,
port_speed=1000000
) )
data = { data = {
'circuit': self.circuit3.pk, 'circuit': self.circuit3.pk,
'term_side': TERM_SIDE_Z, 'term_side': CircuitTerminationSideChoices.SIDE_Z,
'site': self.site2.pk, 'site': self.site2.pk,
'port_speed': 1000000, 'port_speed': 1000000,
} }

View File

@ -12,7 +12,7 @@ from utilities.views import (
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
) )
from . import filters, forms, tables from . import filters, forms, tables
from .constants import TERM_SIDE_A, TERM_SIDE_Z from .choices import CircuitTerminationSideChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -151,12 +151,12 @@ class CircuitView(PermissionRequiredMixin, View):
termination_a = CircuitTermination.objects.prefetch_related( termination_a = CircuitTermination.objects.prefetch_related(
'site__region', 'connected_endpoint__device' 'site__region', 'connected_endpoint__device'
).filter( ).filter(
circuit=circuit, term_side=TERM_SIDE_A circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
).first() ).first()
termination_z = CircuitTermination.objects.prefetch_related( termination_z = CircuitTermination.objects.prefetch_related(
'site__region', 'connected_endpoint__device' 'site__region', 'connected_endpoint__device'
).filter( ).filter(
circuit=circuit, term_side=TERM_SIDE_Z circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
).first() ).first()
return render(request, 'circuits/circuit.html', { return render(request, 'circuits/circuit.html', {
@ -212,8 +212,12 @@ class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
def circuit_terminations_swap(request, pk): def circuit_terminations_swap(request, pk):
circuit = get_object_or_404(Circuit, pk=pk) circuit = get_object_or_404(Circuit, pk=pk)
termination_a = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_A).first() termination_a = CircuitTermination.objects.filter(
termination_z = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_Z).first() circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
).first()
termination_z = CircuitTermination.objects.filter(
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
).first()
if not termination_a and not termination_z: if not termination_a and not termination_z:
messages.error(request, "No terminations have been defined for circuit {}.".format(circuit)) messages.error(request, "No terminations have been defined for circuit {}.".format(circuit))
return redirect('circuits:circuit', pk=circuit.pk) return redirect('circuits:circuit', pk=circuit.pk)

View File

@ -68,7 +68,7 @@ class RegionSerializer(serializers.ModelSerializer):
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer): class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False) status = ChoiceField(choices=SiteStatusChoices, required=False)
region = NestedRegionSerializer(required=False, allow_null=True) region = NestedRegionSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
time_zone = TimeZoneField(required=False) time_zone = TimeZoneField(required=False)
@ -115,11 +115,11 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
site = NestedSiteSerializer() site = NestedSiteSerializer()
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None) group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False) status = ChoiceField(choices=RackStatusChoices, required=False)
role = NestedRackRoleSerializer(required=False, allow_null=True) role = NestedRackRoleSerializer(required=False, allow_null=True)
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True) type = ChoiceField(choices=RackTypeChoices, required=False, allow_null=True)
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False) width = ChoiceField(choices=RackWidthChoices, required=False)
outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False) outer_unit = ChoiceField(choices=RackDimensionUnitChoices, required=False)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
powerfeed_count = serializers.IntegerField(read_only=True) powerfeed_count = serializers.IntegerField(read_only=True)
@ -187,7 +187,7 @@ class ManufacturerSerializer(ValidatedModelSerializer):
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
manufacturer = NestedManufacturerSerializer() manufacturer = NestedManufacturerSerializer()
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, required=False, allow_null=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
device_count = serializers.IntegerField(read_only=True) device_count = serializers.IntegerField(read_only=True)
@ -202,7 +202,7 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
class ConsolePortTemplateSerializer(ValidatedModelSerializer): class ConsolePortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField( type = ChoiceField(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
required=False required=False
) )
@ -214,7 +214,7 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer): class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField( type = ChoiceField(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
required=False required=False
) )
@ -226,7 +226,7 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
class PowerPortTemplateSerializer(ValidatedModelSerializer): class PowerPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField( type = ChoiceField(
choices=PowerPortTypes.CHOICES, choices=PowerPortTypeChoices,
required=False required=False
) )
@ -238,14 +238,14 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
class PowerOutletTemplateSerializer(ValidatedModelSerializer): class PowerOutletTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField( type = ChoiceField(
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
required=False required=False
) )
power_port = PowerPortTemplateSerializer( power_port = PowerPortTemplateSerializer(
required=False required=False
) )
feed_leg = ChoiceField( feed_leg = ChoiceField(
choices=POWERFEED_LEG_CHOICES, choices=PowerOutletFeedLegChoices,
required=False, required=False,
allow_null=True allow_null=True
) )
@ -257,7 +257,7 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
class InterfaceTemplateSerializer(ValidatedModelSerializer): class InterfaceTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=IFACE_TYPE_CHOICES, required=False) type = ChoiceField(choices=InterfaceTypeChoices, required=False)
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
@ -266,7 +266,7 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
class RearPortTemplateSerializer(ValidatedModelSerializer): class RearPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES) type = ChoiceField(choices=PortTypeChoices)
class Meta: class Meta:
model = RearPortTemplate model = RearPortTemplate
@ -275,7 +275,7 @@ class RearPortTemplateSerializer(ValidatedModelSerializer):
class FrontPortTemplateSerializer(ValidatedModelSerializer): class FrontPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer() device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES) type = ChoiceField(choices=PortTypeChoices)
rear_port = NestedRearPortTemplateSerializer() rear_port = NestedRearPortTemplateSerializer()
class Meta: class Meta:
@ -324,8 +324,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
platform = NestedPlatformSerializer(required=False, allow_null=True) platform = NestedPlatformSerializer(required=False, allow_null=True)
site = NestedSiteSerializer() site = NestedSiteSerializer()
rack = NestedRackSerializer(required=False, allow_null=True) rack = NestedRackSerializer(required=False, allow_null=True)
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True) face = ChoiceField(choices=DeviceFaceChoices, required=False, allow_null=True)
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False) status = ChoiceField(choices=DeviceStatusChoices, required=False)
primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
@ -388,7 +388,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer): class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField( type = ChoiceField(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
required=False required=False
) )
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
@ -405,7 +405,7 @@ class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer)
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer): class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField( type = ChoiceField(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
required=False required=False
) )
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
@ -422,14 +422,14 @@ class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer): class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField( type = ChoiceField(
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
required=False required=False
) )
power_port = NestedPowerPortSerializer( power_port = NestedPowerPortSerializer(
required=False required=False
) )
feed_leg = ChoiceField( feed_leg = ChoiceField(
choices=POWERFEED_LEG_CHOICES, choices=PowerOutletFeedLegChoices,
required=False, required=False,
allow_null=True allow_null=True
) )
@ -451,7 +451,7 @@ class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer): class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField( type = ChoiceField(
choices=PowerPortTypes.CHOICES, choices=PowerPortTypeChoices,
required=False required=False
) )
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
@ -467,9 +467,9 @@ class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer): class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField(choices=IFACE_TYPE_CHOICES, required=False) type = ChoiceField(choices=InterfaceTypeChoices, required=False)
lag = NestedInterfaceSerializer(required=False, allow_null=True) lag = NestedInterfaceSerializer(required=False, allow_null=True)
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_null=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField( tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
@ -511,7 +511,7 @@ class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
class RearPortSerializer(TaggitSerializer, ValidatedModelSerializer): class RearPortSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES) type = ChoiceField(choices=PortTypeChoices)
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
@ -533,7 +533,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
class FrontPortSerializer(TaggitSerializer, ValidatedModelSerializer): class FrontPortSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES) type = ChoiceField(choices=PortTypeChoices)
rear_port = FrontPortRearPortSerializer() rear_port = FrontPortRearPortSerializer()
cable = NestedCableSerializer(read_only=True) cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
@ -586,7 +586,7 @@ class CableSerializer(ValidatedModelSerializer):
termination_a = serializers.SerializerMethodField(read_only=True) termination_a = serializers.SerializerMethodField(read_only=True)
termination_b = serializers.SerializerMethodField(read_only=True) termination_b = serializers.SerializerMethodField(read_only=True)
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False) status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False, allow_null=True) length_unit = ChoiceField(choices=CableLengthUnitChoices, required=False, allow_null=True)
class Meta: class Meta:
model = Cable model = Cable
@ -691,20 +691,20 @@ class PowerFeedSerializer(TaggitSerializer, CustomFieldModelSerializer):
default=None default=None
) )
type = ChoiceField( type = ChoiceField(
choices=POWERFEED_TYPE_CHOICES, choices=PowerFeedTypeChoices,
default=POWERFEED_TYPE_PRIMARY default=PowerFeedTypeChoices.TYPE_PRIMARY
) )
status = ChoiceField( status = ChoiceField(
choices=POWERFEED_STATUS_CHOICES, choices=PowerFeedStatusChoices,
default=POWERFEED_STATUS_ACTIVE default=PowerFeedStatusChoices.STATUS_ACTIVE
) )
supply = ChoiceField( supply = ChoiceField(
choices=POWERFEED_SUPPLY_CHOICES, choices=PowerFeedSupplyChoices,
default=POWERFEED_SUPPLY_AC default=PowerFeedSupplyChoices.SUPPLY_AC
) )
phase = ChoiceField( phase = ChoiceField(
choices=POWERFEED_PHASE_CHOICES, choices=PowerFeedPhaseChoices,
default=POWERFEED_PHASE_SINGLE default=PowerFeedPhaseChoices.PHASE_SINGLE
) )
tags = TagListSerializerField( tags = TagListSerializerField(
required=False required=False

View File

@ -1,14 +1,187 @@
from .constants import * from utilities.choices import ChoiceSet
# #
# Console port type values # Sites
# #
class ConsolePortTypes: class SiteStatusChoices(ChoiceSet):
"""
ConsolePort/ConsoleServerPort.type slugs STATUS_ACTIVE = 'active'
""" STATUS_PLANNED = 'planned'
STATUS_RETIRED = 'retired'
CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_RETIRED, 'Retired'),
)
LEGACY_MAP = {
STATUS_ACTIVE: 1,
STATUS_PLANNED: 2,
STATUS_RETIRED: 4,
}
#
# Racks
#
class RackTypeChoices(ChoiceSet):
TYPE_2POST = '2-post-frame'
TYPE_4POST = '4-post-frame'
TYPE_CABINET = '4-post-cabinet'
TYPE_WALLFRAME = 'wall-frame'
TYPE_WALLCABINET = 'wall-cabinet'
CHOICES = (
(TYPE_2POST, '2-post frame'),
(TYPE_4POST, '4-post frame'),
(TYPE_CABINET, '4-post cabinet'),
(TYPE_WALLFRAME, 'Wall-mounted frame'),
(TYPE_WALLCABINET, 'Wall-mounted cabinet'),
)
LEGACY_MAP = {
TYPE_2POST: 100,
TYPE_4POST: 200,
TYPE_CABINET: 300,
TYPE_WALLFRAME: 1000,
TYPE_WALLCABINET: 1100,
}
class RackWidthChoices(ChoiceSet):
WIDTH_19IN = 19
WIDTH_23IN = 23
CHOICES = (
(WIDTH_19IN, '19 inches'),
(WIDTH_23IN, '23 inches'),
)
class RackStatusChoices(ChoiceSet):
STATUS_RESERVED = 'reserved'
STATUS_AVAILABLE = 'available'
STATUS_PLANNED = 'planned'
STATUS_ACTIVE = 'active'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
(STATUS_RESERVED, 'Reserved'),
(STATUS_AVAILABLE, 'Available'),
(STATUS_PLANNED, 'Planned'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DEPRECATED, 'Deprecated'),
)
LEGACY_MAP = {
STATUS_RESERVED: 0,
STATUS_AVAILABLE: 1,
STATUS_PLANNED: 2,
STATUS_ACTIVE: 3,
STATUS_DEPRECATED: 4,
}
class RackDimensionUnitChoices(ChoiceSet):
UNIT_MILLIMETER = 'mm'
UNIT_INCH = 'in'
CHOICES = (
(UNIT_MILLIMETER, 'Millimeters'),
(UNIT_INCH, 'Inches'),
)
LEGACY_MAP = {
UNIT_MILLIMETER: 1000,
UNIT_INCH: 2000,
}
#
# DeviceTypes
#
class SubdeviceRoleChoices(ChoiceSet):
ROLE_PARENT = 'parent'
ROLE_CHILD = 'child'
CHOICES = (
(ROLE_PARENT, 'Parent'),
(ROLE_CHILD, 'Child'),
)
LEGACY_MAP = {
ROLE_PARENT: True,
ROLE_CHILD: False,
}
#
# Devices
#
class DeviceFaceChoices(ChoiceSet):
FACE_FRONT = 'front'
FACE_REAR = 'rear'
CHOICES = (
(FACE_FRONT, 'Front'),
(FACE_REAR, 'Rear'),
)
LEGACY_MAP = {
FACE_FRONT: 0,
FACE_REAR: 1,
}
class DeviceStatusChoices(ChoiceSet):
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
STATUS_PLANNED = 'planned'
STATUS_STAGED = 'staged'
STATUS_FAILED = 'failed'
STATUS_INVENTORY = 'inventory'
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGED, 'Staged'),
(STATUS_FAILED, 'Failed'),
(STATUS_INVENTORY, 'Inventory'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
)
LEGACY_MAP = {
STATUS_OFFLINE: 0,
STATUS_ACTIVE: 1,
STATUS_PLANNED: 2,
STATUS_STAGED: 3,
STATUS_FAILED: 4,
STATUS_INVENTORY: 5,
STATUS_DECOMMISSIONING: 6,
}
#
# ConsolePorts
#
class ConsolePortTypeChoices(ChoiceSet):
TYPE_DE9 = 'de-9' TYPE_DE9 = 'de-9'
TYPE_DB25 = 'db-25' TYPE_DB25 = 'db-25'
TYPE_RJ45 = 'rj-45' TYPE_RJ45 = 'rj-45'
@ -43,10 +216,11 @@ class ConsolePortTypes:
# #
# Power port types # PowerPorts
# #
class PowerPortTypes: class PowerPortTypeChoices(ChoiceSet):
# TODO: Add more power port types # TODO: Add more power port types
# IEC 60320 # IEC 60320
TYPE_IEC_C6 = 'iec-60320-c6' TYPE_IEC_C6 = 'iec-60320-c6'
@ -130,10 +304,11 @@ class PowerPortTypes:
# #
# Power outlet types # PowerOutlets
# #
class PowerOutletTypes: class PowerOutletTypeChoices(ChoiceSet):
# TODO: Add more power outlet types # TODO: Add more power outlet types
# IEC 60320 # IEC 60320
TYPE_IEC_C5 = 'iec-60320-c5' TYPE_IEC_C5 = 'iec-60320-c5'
@ -216,14 +391,31 @@ class PowerOutletTypes:
) )
class PowerOutletFeedLegChoices(ChoiceSet):
FEED_LEG_A = 'A'
FEED_LEG_B = 'B'
FEED_LEG_C = 'C'
CHOICES = (
(FEED_LEG_A, 'A'),
(FEED_LEG_B, 'B'),
(FEED_LEG_C, 'C'),
)
LEGACY_MAP = {
FEED_LEG_A: 1,
FEED_LEG_B: 2,
FEED_LEG_C: 3,
}
# #
# Interface type values # Interfaces
# #
class InterfaceTypes: class InterfaceTypeChoices(ChoiceSet):
"""
Interface.type slugs
"""
# Virtual # Virtual
TYPE_VIRTUAL = 'virtual' TYPE_VIRTUAL = 'virtual'
TYPE_LAG = 'lag' TYPE_LAG = 'lag'
@ -315,7 +507,7 @@ class InterfaceTypes:
# Other # Other
TYPE_OTHER = 'other' TYPE_OTHER = 'other'
TYPE_CHOICES = ( CHOICES = (
( (
'Virtual interfaces', 'Virtual interfaces',
( (
@ -444,93 +636,105 @@ class InterfaceTypes:
), ),
) )
@classmethod LEGACY_MAP = {
def slug_to_integer(cls, slug): TYPE_VIRTUAL: 0,
""" TYPE_LAG: 200,
Provide backward-compatible mapping of the type slug to integer. TYPE_100ME_FIXED: 800,
""" TYPE_1GE_FIXED: 1000,
return { TYPE_1GE_GBIC: 1050,
# Slug: integer TYPE_1GE_SFP: 1100,
cls.TYPE_VIRTUAL: IFACE_TYPE_VIRTUAL, TYPE_2GE_FIXED: 1120,
cls.TYPE_LAG: IFACE_TYPE_LAG, TYPE_5GE_FIXED: 1130,
cls.TYPE_100ME_FIXED: IFACE_TYPE_100ME_FIXED, TYPE_10GE_FIXED: 1150,
cls.TYPE_1GE_FIXED: IFACE_TYPE_1GE_FIXED, TYPE_10GE_CX4: 1170,
cls.TYPE_1GE_GBIC: IFACE_TYPE_1GE_GBIC, TYPE_10GE_SFP_PLUS: 1200,
cls.TYPE_1GE_SFP: IFACE_TYPE_1GE_SFP, TYPE_10GE_XFP: 1300,
cls.TYPE_2GE_FIXED: IFACE_TYPE_2GE_FIXED, TYPE_10GE_XENPAK: 1310,
cls.TYPE_5GE_FIXED: IFACE_TYPE_5GE_FIXED, TYPE_10GE_X2: 1320,
cls.TYPE_10GE_FIXED: IFACE_TYPE_10GE_FIXED, TYPE_25GE_SFP28: 1350,
cls.TYPE_10GE_CX4: IFACE_TYPE_10GE_CX4, TYPE_40GE_QSFP_PLUS: 1400,
cls.TYPE_10GE_SFP_PLUS: IFACE_TYPE_10GE_SFP_PLUS, TYPE_50GE_QSFP28: 1420,
cls.TYPE_10GE_XFP: IFACE_TYPE_10GE_XFP, TYPE_100GE_CFP: 1500,
cls.TYPE_10GE_XENPAK: IFACE_TYPE_10GE_XENPAK, TYPE_100GE_CFP2: 1510,
cls.TYPE_10GE_X2: IFACE_TYPE_10GE_X2, TYPE_100GE_CFP4: 1520,
cls.TYPE_25GE_SFP28: IFACE_TYPE_25GE_SFP28, TYPE_100GE_CPAK: 1550,
cls.TYPE_40GE_QSFP_PLUS: IFACE_TYPE_40GE_QSFP_PLUS, TYPE_100GE_QSFP28: 1600,
cls.TYPE_50GE_QSFP28: IFACE_TYPE_50GE_QSFP28, TYPE_200GE_CFP2: 1650,
cls.TYPE_100GE_CFP: IFACE_TYPE_100GE_CFP, TYPE_200GE_QSFP56: 1700,
cls.TYPE_100GE_CFP2: IFACE_TYPE_100GE_CFP2, TYPE_400GE_QSFP_DD: 1750,
cls.TYPE_100GE_CFP4: IFACE_TYPE_100GE_CFP4, TYPE_400GE_OSFP: 1800,
cls.TYPE_100GE_CPAK: IFACE_TYPE_100GE_CPAK, TYPE_80211A: 2600,
cls.TYPE_100GE_QSFP28: IFACE_TYPE_100GE_QSFP28, TYPE_80211G: 2610,
cls.TYPE_200GE_CFP2: IFACE_TYPE_200GE_CFP2, TYPE_80211N: 2620,
cls.TYPE_200GE_QSFP56: IFACE_TYPE_200GE_QSFP56, TYPE_80211AC: 2630,
cls.TYPE_400GE_QSFP_DD: IFACE_TYPE_400GE_QSFP_DD, TYPE_80211AD: 2640,
cls.TYPE_80211A: IFACE_TYPE_80211A, TYPE_GSM: 2810,
cls.TYPE_80211G: IFACE_TYPE_80211G, TYPE_CDMA: 2820,
cls.TYPE_80211N: IFACE_TYPE_80211N, TYPE_LTE: 2830,
cls.TYPE_80211AC: IFACE_TYPE_80211AC, TYPE_SONET_OC3: 6100,
cls.TYPE_80211AD: IFACE_TYPE_80211AD, TYPE_SONET_OC12: 6200,
cls.TYPE_GSM: IFACE_TYPE_GSM, TYPE_SONET_OC48: 6300,
cls.TYPE_CDMA: IFACE_TYPE_CDMA, TYPE_SONET_OC192: 6400,
cls.TYPE_LTE: IFACE_TYPE_LTE, TYPE_SONET_OC768: 6500,
cls.TYPE_SONET_OC3: IFACE_TYPE_SONET_OC3, TYPE_SONET_OC1920: 6600,
cls.TYPE_SONET_OC12: IFACE_TYPE_SONET_OC12, TYPE_SONET_OC3840: 6700,
cls.TYPE_SONET_OC48: IFACE_TYPE_SONET_OC48, TYPE_1GFC_SFP: 3010,
cls.TYPE_SONET_OC192: IFACE_TYPE_SONET_OC192, TYPE_2GFC_SFP: 3020,
cls.TYPE_SONET_OC768: IFACE_TYPE_SONET_OC768, TYPE_4GFC_SFP: 3040,
cls.TYPE_SONET_OC1920: IFACE_TYPE_SONET_OC1920, TYPE_8GFC_SFP_PLUS: 3080,
cls.TYPE_SONET_OC3840: IFACE_TYPE_SONET_OC3840, TYPE_16GFC_SFP_PLUS: 3160,
cls.TYPE_1GFC_SFP: IFACE_TYPE_1GFC_SFP, TYPE_32GFC_SFP28: 3320,
cls.TYPE_2GFC_SFP: IFACE_TYPE_2GFC_SFP, TYPE_128GFC_QSFP28: 3400,
cls.TYPE_4GFC_SFP: IFACE_TYPE_4GFC_SFP, TYPE_INFINIBAND_SDR: 7010,
cls.TYPE_8GFC_SFP_PLUS: IFACE_TYPE_8GFC_SFP_PLUS, TYPE_INFINIBAND_DDR: 7020,
cls.TYPE_16GFC_SFP_PLUS: IFACE_TYPE_16GFC_SFP_PLUS, TYPE_INFINIBAND_QDR: 7030,
cls.TYPE_32GFC_SFP28: IFACE_TYPE_32GFC_SFP28, TYPE_INFINIBAND_FDR10: 7040,
cls.TYPE_128GFC_QSFP28: IFACE_TYPE_128GFC_QSFP28, TYPE_INFINIBAND_FDR: 7050,
cls.TYPE_INFINIBAND_SDR: IFACE_TYPE_INFINIBAND_SDR, TYPE_INFINIBAND_EDR: 7060,
cls.TYPE_INFINIBAND_DDR: IFACE_TYPE_INFINIBAND_DDR, TYPE_INFINIBAND_HDR: 7070,
cls.TYPE_INFINIBAND_QDR: IFACE_TYPE_INFINIBAND_QDR, TYPE_INFINIBAND_NDR: 7080,
cls.TYPE_INFINIBAND_FDR10: IFACE_TYPE_INFINIBAND_FDR10, TYPE_INFINIBAND_XDR: 7090,
cls.TYPE_INFINIBAND_FDR: IFACE_TYPE_INFINIBAND_FDR, TYPE_T1: 4000,
cls.TYPE_INFINIBAND_EDR: IFACE_TYPE_INFINIBAND_EDR, TYPE_E1: 4010,
cls.TYPE_INFINIBAND_HDR: IFACE_TYPE_INFINIBAND_HDR, TYPE_T3: 4040,
cls.TYPE_INFINIBAND_NDR: IFACE_TYPE_INFINIBAND_NDR, TYPE_E3: 4050,
cls.TYPE_INFINIBAND_XDR: IFACE_TYPE_INFINIBAND_XDR, TYPE_STACKWISE: 5000,
cls.TYPE_T1: IFACE_TYPE_T1, TYPE_STACKWISE_PLUS: 5050,
cls.TYPE_E1: IFACE_TYPE_E1, TYPE_FLEXSTACK: 5100,
cls.TYPE_T3: IFACE_TYPE_T3, TYPE_FLEXSTACK_PLUS: 5150,
cls.TYPE_E3: IFACE_TYPE_E3, TYPE_JUNIPER_VCP: 5200,
cls.TYPE_STACKWISE: IFACE_TYPE_STACKWISE, TYPE_SUMMITSTACK: 5300,
cls.TYPE_STACKWISE_PLUS: IFACE_TYPE_STACKWISE_PLUS, TYPE_SUMMITSTACK128: 5310,
cls.TYPE_FLEXSTACK: IFACE_TYPE_FLEXSTACK, TYPE_SUMMITSTACK256: 5320,
cls.TYPE_FLEXSTACK_PLUS: IFACE_TYPE_FLEXSTACK_PLUS, TYPE_SUMMITSTACK512: 5330,
cls.TYPE_JUNIPER_VCP: IFACE_TYPE_JUNIPER_VCP, }
cls.TYPE_SUMMITSTACK: IFACE_TYPE_SUMMITSTACK,
cls.TYPE_SUMMITSTACK128: IFACE_TYPE_SUMMITSTACK128,
cls.TYPE_SUMMITSTACK256: IFACE_TYPE_SUMMITSTACK256, class InterfaceModeChoices(ChoiceSet):
cls.TYPE_SUMMITSTACK512: IFACE_TYPE_SUMMITSTACK512,
}.get(slug) MODE_ACCESS = 'access'
MODE_TAGGED = 'tagged'
MODE_TAGGED_ALL = 'tagged-all'
CHOICES = (
(MODE_ACCESS, 'Access'),
(MODE_TAGGED, 'Tagged'),
(MODE_TAGGED_ALL, 'Tagged (All)'),
)
LEGACY_MAP = {
MODE_ACCESS: 100,
MODE_TAGGED: 200,
MODE_TAGGED_ALL: 300,
}
# #
# Port type values # FrontPorts/RearPorts
# #
class PortTypes: class PortTypeChoices(ChoiceSet):
"""
FrontPort/RearPort.type slugs
"""
TYPE_8P8C = '8p8c' TYPE_8P8C = '8p8c'
TYPE_110_PUNCH = '110-punch' TYPE_110_PUNCH = '110-punch'
TYPE_BNC = 'bnc' TYPE_BNC = 'bnc'
@ -545,7 +749,7 @@ class PortTypes:
TYPE_LSH = 'lsh' TYPE_LSH = 'lsh'
TYPE_LSH_APC = 'lsh-apc' TYPE_LSH_APC = 'lsh-apc'
TYPE_CHOICES = ( CHOICES = (
( (
'Copper', 'Copper',
( (
@ -571,24 +775,193 @@ class PortTypes:
) )
) )
@classmethod LEGACY_MAP = {
def slug_to_integer(cls, slug): TYPE_8P8C: 1000,
""" TYPE_110_PUNCH: 1100,
Provide backward-compatible mapping of the type slug to integer. TYPE_BNC: 1200,
""" TYPE_ST: 2000,
return { TYPE_SC: 2100,
# Slug: integer TYPE_SC_APC: 2110,
cls.TYPE_8P8C: PORT_TYPE_8P8C, TYPE_FC: 2200,
cls.TYPE_110_PUNCH: PORT_TYPE_8P8C, TYPE_LC: 2300,
cls.TYPE_BNC: PORT_TYPE_BNC, TYPE_LC_APC: 2310,
cls.TYPE_ST: PORT_TYPE_ST, TYPE_MTRJ: 2400,
cls.TYPE_SC: PORT_TYPE_SC, TYPE_MPO: 2500,
cls.TYPE_SC_APC: PORT_TYPE_SC_APC, TYPE_LSH: 2600,
cls.TYPE_FC: PORT_TYPE_FC, TYPE_LSH_APC: 2610,
cls.TYPE_LC: PORT_TYPE_LC, }
cls.TYPE_LC_APC: PORT_TYPE_LC_APC,
cls.TYPE_MTRJ: PORT_TYPE_MTRJ,
cls.TYPE_MPO: PORT_TYPE_MPO, #
cls.TYPE_LSH: PORT_TYPE_LSH, # Cables
cls.TYPE_LSH_APC: PORT_TYPE_LSH_APC, #
}.get(slug)
class CableTypeChoices(ChoiceSet):
TYPE_CAT3 = 'cat3'
TYPE_CAT5 = 'cat5'
TYPE_CAT5E = 'cat5e'
TYPE_CAT6 = 'cat6'
TYPE_CAT6A = 'cat6a'
TYPE_CAT7 = 'cat7'
TYPE_DAC_ACTIVE = 'dac-active'
TYPE_DAC_PASSIVE = 'dac-passive'
TYPE_COAXIAL = 'coaxial'
TYPE_MMF = 'mmf'
TYPE_MMF_OM1 = 'mmf-om1'
TYPE_MMF_OM2 = 'mmf-om2'
TYPE_MMF_OM3 = 'mmf-om3'
TYPE_MMF_OM4 = 'mmf-om4'
TYPE_SMF = 'smf'
TYPE_SMF_OS1 = 'smf-os1'
TYPE_SMF_OS2 = 'smf-os2'
TYPE_AOC = 'aoc'
TYPE_POWER = 'power'
CHOICES = (
(
'Copper', (
(TYPE_CAT3, 'CAT3'),
(TYPE_CAT5, 'CAT5'),
(TYPE_CAT5E, 'CAT5e'),
(TYPE_CAT6, 'CAT6'),
(TYPE_CAT6A, 'CAT6a'),
(TYPE_CAT7, 'CAT7'),
(TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
(TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
(TYPE_COAXIAL, 'Coaxial'),
),
),
(
'Fiber', (
(TYPE_MMF, 'Multimode Fiber'),
(TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
(TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
(TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
(TYPE_SMF, 'Singlemode Fiber'),
(TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
(TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
(TYPE_AOC, 'Active Optical Cabling (AOC)'),
),
),
(TYPE_POWER, 'Power'),
)
LEGACY_MAP = {
TYPE_CAT3: 1300,
TYPE_CAT5: 1500,
TYPE_CAT5E: 1510,
TYPE_CAT6: 1600,
TYPE_CAT6A: 1610,
TYPE_CAT7: 1700,
TYPE_DAC_ACTIVE: 1800,
TYPE_DAC_PASSIVE: 1810,
TYPE_COAXIAL: 1900,
TYPE_MMF: 3000,
TYPE_MMF_OM1: 3010,
TYPE_MMF_OM2: 3020,
TYPE_MMF_OM3: 3030,
TYPE_MMF_OM4: 3040,
TYPE_SMF: 3500,
TYPE_SMF_OS1: 3510,
TYPE_SMF_OS2: 3520,
TYPE_AOC: 3800,
TYPE_POWER: 5000,
}
class CableLengthUnitChoices(ChoiceSet):
UNIT_METER = 'm'
UNIT_CENTIMETER = 'cm'
UNIT_FOOT = 'ft'
UNIT_INCH = 'in'
CHOICES = (
(UNIT_METER, 'Meters'),
(UNIT_CENTIMETER, 'Centimeters'),
(UNIT_FOOT, 'Feet'),
(UNIT_INCH, 'Inches'),
)
LEGACY_MAP = {
UNIT_METER: 1200,
UNIT_CENTIMETER: 1100,
UNIT_FOOT: 2100,
UNIT_INCH: 2000,
}
#
# PowerFeeds
#
class PowerFeedStatusChoices(ChoiceSet):
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
STATUS_PLANNED = 'planned'
STATUS_FAILED = 'failed'
CHOICES = (
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_FAILED, 'Failed'),
)
LEGACY_MAP = {
STATUS_OFFLINE: 0,
STATUS_ACTIVE: 1,
STATUS_PLANNED: 2,
STATUS_FAILED: 4,
}
class PowerFeedTypeChoices(ChoiceSet):
TYPE_PRIMARY = 'primary'
TYPE_REDUNDANT = 'redundant'
CHOICES = (
(TYPE_PRIMARY, 'Primary'),
(TYPE_REDUNDANT, 'Redundant'),
)
LEGACY_MAP = {
TYPE_PRIMARY: 1,
TYPE_REDUNDANT: 2,
}
class PowerFeedSupplyChoices(ChoiceSet):
SUPPLY_AC = 'ac'
SUPPLY_DC = 'dc'
CHOICES = (
(SUPPLY_AC, 'AC'),
(SUPPLY_DC, 'DC'),
)
LEGACY_MAP = {
SUPPLY_AC: 1,
SUPPLY_DC: 2,
}
class PowerFeedPhaseChoices(ChoiceSet):
PHASE_SINGLE = 'single-phase'
PHASE_3PHASE = 'three-phase'
CHOICES = (
(PHASE_SINGLE, 'Single phase'),
(PHASE_3PHASE, 'Three-phase'),
)
LEGACY_MAP = {
PHASE_SINGLE: 1,
PHASE_3PHASE: 3,
}

View File

@ -1,381 +1,25 @@
# Rack types from .choices import InterfaceTypeChoices
RACK_TYPE_2POST = 100
RACK_TYPE_4POST = 200
RACK_TYPE_CABINET = 300
RACK_TYPE_WALLFRAME = 1000
RACK_TYPE_WALLCABINET = 1100
RACK_TYPE_CHOICES = (
(RACK_TYPE_2POST, '2-post frame'),
(RACK_TYPE_4POST, '4-post frame'),
(RACK_TYPE_CABINET, '4-post cabinet'),
(RACK_TYPE_WALLFRAME, 'Wall-mounted frame'),
(RACK_TYPE_WALLCABINET, 'Wall-mounted cabinet'),
)
# Rack widths
RACK_WIDTH_19IN = 19
RACK_WIDTH_23IN = 23
RACK_WIDTH_CHOICES = (
(RACK_WIDTH_19IN, '19 inches'),
(RACK_WIDTH_23IN, '23 inches'),
)
# Rack faces
RACK_FACE_FRONT = 0
RACK_FACE_REAR = 1
RACK_FACE_CHOICES = [
[RACK_FACE_FRONT, 'Front'],
[RACK_FACE_REAR, 'Rear'],
]
# Rack statuses
RACK_STATUS_RESERVED = 0
RACK_STATUS_AVAILABLE = 1
RACK_STATUS_PLANNED = 2
RACK_STATUS_ACTIVE = 3
RACK_STATUS_DEPRECATED = 4
RACK_STATUS_CHOICES = [
[RACK_STATUS_ACTIVE, 'Active'],
[RACK_STATUS_PLANNED, 'Planned'],
[RACK_STATUS_RESERVED, 'Reserved'],
[RACK_STATUS_AVAILABLE, 'Available'],
[RACK_STATUS_DEPRECATED, 'Deprecated'],
]
# Device rack position
DEVICE_POSITION_CHOICES = [
# Rack.u_height is limited to 100
(i, 'Unit {}'.format(i)) for i in range(1, 101)
]
# Parent/child device roles
SUBDEVICE_ROLE_PARENT = True
SUBDEVICE_ROLE_CHILD = False
SUBDEVICE_ROLE_CHOICES = (
(None, 'None'),
(SUBDEVICE_ROLE_PARENT, 'Parent'),
(SUBDEVICE_ROLE_CHILD, 'Child'),
)
# #
# Numeric interface types # Interface type groups
# #
# Virtual
IFACE_TYPE_VIRTUAL = 0
IFACE_TYPE_LAG = 200
# Ethernet
IFACE_TYPE_100ME_FIXED = 800
IFACE_TYPE_1GE_FIXED = 1000
IFACE_TYPE_1GE_GBIC = 1050
IFACE_TYPE_1GE_SFP = 1100
IFACE_TYPE_2GE_FIXED = 1120
IFACE_TYPE_5GE_FIXED = 1130
IFACE_TYPE_10GE_FIXED = 1150
IFACE_TYPE_10GE_CX4 = 1170
IFACE_TYPE_10GE_SFP_PLUS = 1200
IFACE_TYPE_10GE_XFP = 1300
IFACE_TYPE_10GE_XENPAK = 1310
IFACE_TYPE_10GE_X2 = 1320
IFACE_TYPE_25GE_SFP28 = 1350
IFACE_TYPE_40GE_QSFP_PLUS = 1400
IFACE_TYPE_50GE_QSFP28 = 1420
IFACE_TYPE_100GE_CFP = 1500
IFACE_TYPE_100GE_CFP2 = 1510
IFACE_TYPE_100GE_CFP4 = 1520
IFACE_TYPE_100GE_CPAK = 1550
IFACE_TYPE_100GE_QSFP28 = 1600
IFACE_TYPE_200GE_CFP2 = 1650
IFACE_TYPE_200GE_QSFP56 = 1700
IFACE_TYPE_400GE_QSFP_DD = 1750
IFACE_TYPE_400GE_OSFP = 1800
# Wireless
IFACE_TYPE_80211A = 2600
IFACE_TYPE_80211G = 2610
IFACE_TYPE_80211N = 2620
IFACE_TYPE_80211AC = 2630
IFACE_TYPE_80211AD = 2640
# Cellular
IFACE_TYPE_GSM = 2810
IFACE_TYPE_CDMA = 2820
IFACE_TYPE_LTE = 2830
# SONET
IFACE_TYPE_SONET_OC3 = 6100
IFACE_TYPE_SONET_OC12 = 6200
IFACE_TYPE_SONET_OC48 = 6300
IFACE_TYPE_SONET_OC192 = 6400
IFACE_TYPE_SONET_OC768 = 6500
IFACE_TYPE_SONET_OC1920 = 6600
IFACE_TYPE_SONET_OC3840 = 6700
# Fibrechannel
IFACE_TYPE_1GFC_SFP = 3010
IFACE_TYPE_2GFC_SFP = 3020
IFACE_TYPE_4GFC_SFP = 3040
IFACE_TYPE_8GFC_SFP_PLUS = 3080
IFACE_TYPE_16GFC_SFP_PLUS = 3160
IFACE_TYPE_32GFC_SFP28 = 3320
IFACE_TYPE_128GFC_QSFP28 = 3400
# InfiniBand
IFACE_TYPE_INFINIBAND_SDR = 7010
IFACE_TYPE_INFINIBAND_DDR = 7020
IFACE_TYPE_INFINIBAND_QDR = 7030
IFACE_TYPE_INFINIBAND_FDR10 = 7040
IFACE_TYPE_INFINIBAND_FDR = 7050
IFACE_TYPE_INFINIBAND_EDR = 7060
IFACE_TYPE_INFINIBAND_HDR = 7070
IFACE_TYPE_INFINIBAND_NDR = 7080
IFACE_TYPE_INFINIBAND_XDR = 7090
# Serial
IFACE_TYPE_T1 = 4000
IFACE_TYPE_E1 = 4010
IFACE_TYPE_T3 = 4040
IFACE_TYPE_E3 = 4050
# Stacking
IFACE_TYPE_STACKWISE = 5000
IFACE_TYPE_STACKWISE_PLUS = 5050
IFACE_TYPE_FLEXSTACK = 5100
IFACE_TYPE_FLEXSTACK_PLUS = 5150
IFACE_TYPE_JUNIPER_VCP = 5200
IFACE_TYPE_SUMMITSTACK = 5300
IFACE_TYPE_SUMMITSTACK128 = 5310
IFACE_TYPE_SUMMITSTACK256 = 5320
IFACE_TYPE_SUMMITSTACK512 = 5330
# Other
IFACE_TYPE_OTHER = 32767
IFACE_TYPE_CHOICES = [
[
'Virtual interfaces',
[
[IFACE_TYPE_VIRTUAL, 'Virtual'],
[IFACE_TYPE_LAG, 'Link Aggregation Group (LAG)'],
],
],
[
'Ethernet (fixed)',
[
[IFACE_TYPE_100ME_FIXED, '100BASE-TX (10/100ME)'],
[IFACE_TYPE_1GE_FIXED, '1000BASE-T (1GE)'],
[IFACE_TYPE_2GE_FIXED, '2.5GBASE-T (2.5GE)'],
[IFACE_TYPE_5GE_FIXED, '5GBASE-T (5GE)'],
[IFACE_TYPE_10GE_FIXED, '10GBASE-T (10GE)'],
[IFACE_TYPE_10GE_CX4, '10GBASE-CX4 (10GE)'],
]
],
[
'Ethernet (modular)',
[
[IFACE_TYPE_1GE_GBIC, 'GBIC (1GE)'],
[IFACE_TYPE_1GE_SFP, 'SFP (1GE)'],
[IFACE_TYPE_10GE_SFP_PLUS, 'SFP+ (10GE)'],
[IFACE_TYPE_10GE_XFP, 'XFP (10GE)'],
[IFACE_TYPE_10GE_XENPAK, 'XENPAK (10GE)'],
[IFACE_TYPE_10GE_X2, 'X2 (10GE)'],
[IFACE_TYPE_25GE_SFP28, 'SFP28 (25GE)'],
[IFACE_TYPE_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
[IFACE_TYPE_50GE_QSFP28, 'QSFP28 (50GE)'],
[IFACE_TYPE_100GE_CFP, 'CFP (100GE)'],
[IFACE_TYPE_100GE_CFP2, 'CFP2 (100GE)'],
[IFACE_TYPE_200GE_CFP2, 'CFP2 (200GE)'],
[IFACE_TYPE_100GE_CFP4, 'CFP4 (100GE)'],
[IFACE_TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'],
[IFACE_TYPE_100GE_QSFP28, 'QSFP28 (100GE)'],
[IFACE_TYPE_200GE_QSFP56, 'QSFP56 (200GE)'],
[IFACE_TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'],
[IFACE_TYPE_400GE_OSFP, 'OSFP (400GE)'],
]
],
[
'Wireless',
[
[IFACE_TYPE_80211A, 'IEEE 802.11a'],
[IFACE_TYPE_80211G, 'IEEE 802.11b/g'],
[IFACE_TYPE_80211N, 'IEEE 802.11n'],
[IFACE_TYPE_80211AC, 'IEEE 802.11ac'],
[IFACE_TYPE_80211AD, 'IEEE 802.11ad'],
]
],
[
'Cellular',
[
[IFACE_TYPE_GSM, 'GSM'],
[IFACE_TYPE_CDMA, 'CDMA'],
[IFACE_TYPE_LTE, 'LTE'],
]
],
[
'SONET',
[
[IFACE_TYPE_SONET_OC3, 'OC-3/STM-1'],
[IFACE_TYPE_SONET_OC12, 'OC-12/STM-4'],
[IFACE_TYPE_SONET_OC48, 'OC-48/STM-16'],
[IFACE_TYPE_SONET_OC192, 'OC-192/STM-64'],
[IFACE_TYPE_SONET_OC768, 'OC-768/STM-256'],
[IFACE_TYPE_SONET_OC1920, 'OC-1920/STM-640'],
[IFACE_TYPE_SONET_OC3840, 'OC-3840/STM-1234'],
]
],
[
'FibreChannel',
[
[IFACE_TYPE_1GFC_SFP, 'SFP (1GFC)'],
[IFACE_TYPE_2GFC_SFP, 'SFP (2GFC)'],
[IFACE_TYPE_4GFC_SFP, 'SFP (4GFC)'],
[IFACE_TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'],
[IFACE_TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'],
[IFACE_TYPE_32GFC_SFP28, 'SFP28 (32GFC)'],
[IFACE_TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'],
]
],
[
'InfiniBand',
[
[IFACE_TYPE_INFINIBAND_SDR, 'SDR (2 Gbps)'],
[IFACE_TYPE_INFINIBAND_DDR, 'DDR (4 Gbps)'],
[IFACE_TYPE_INFINIBAND_QDR, 'QDR (8 Gbps)'],
[IFACE_TYPE_INFINIBAND_FDR10, 'FDR10 (10 Gbps)'],
[IFACE_TYPE_INFINIBAND_FDR, 'FDR (13.5 Gbps)'],
[IFACE_TYPE_INFINIBAND_EDR, 'EDR (25 Gbps)'],
[IFACE_TYPE_INFINIBAND_HDR, 'HDR (50 Gbps)'],
[IFACE_TYPE_INFINIBAND_NDR, 'NDR (100 Gbps)'],
[IFACE_TYPE_INFINIBAND_XDR, 'XDR (250 Gbps)'],
]
],
[
'Serial',
[
[IFACE_TYPE_T1, 'T1 (1.544 Mbps)'],
[IFACE_TYPE_E1, 'E1 (2.048 Mbps)'],
[IFACE_TYPE_T3, 'T3 (45 Mbps)'],
[IFACE_TYPE_E3, 'E3 (34 Mbps)'],
]
],
[
'Stacking',
[
[IFACE_TYPE_STACKWISE, 'Cisco StackWise'],
[IFACE_TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'],
[IFACE_TYPE_FLEXSTACK, 'Cisco FlexStack'],
[IFACE_TYPE_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
[IFACE_TYPE_JUNIPER_VCP, 'Juniper VCP'],
[IFACE_TYPE_SUMMITSTACK, 'Extreme SummitStack'],
[IFACE_TYPE_SUMMITSTACK128, 'Extreme SummitStack-128'],
[IFACE_TYPE_SUMMITSTACK256, 'Extreme SummitStack-256'],
[IFACE_TYPE_SUMMITSTACK512, 'Extreme SummitStack-512'],
]
],
[
'Other',
[
[IFACE_TYPE_OTHER, 'Other'],
]
],
]
VIRTUAL_IFACE_TYPES = [ VIRTUAL_IFACE_TYPES = [
IFACE_TYPE_VIRTUAL, InterfaceTypeChoices.TYPE_VIRTUAL,
IFACE_TYPE_LAG, InterfaceTypeChoices.TYPE_LAG,
] ]
WIRELESS_IFACE_TYPES = [ WIRELESS_IFACE_TYPES = [
IFACE_TYPE_80211A, InterfaceTypeChoices.TYPE_80211A,
IFACE_TYPE_80211G, InterfaceTypeChoices.TYPE_80211G,
IFACE_TYPE_80211N, InterfaceTypeChoices.TYPE_80211N,
IFACE_TYPE_80211AC, InterfaceTypeChoices.TYPE_80211AC,
IFACE_TYPE_80211AD, InterfaceTypeChoices.TYPE_80211AD,
] ]
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
IFACE_MODE_ACCESS = 100
IFACE_MODE_TAGGED = 200
IFACE_MODE_TAGGED_ALL = 300
IFACE_MODE_CHOICES = [
[IFACE_MODE_ACCESS, 'Access'],
[IFACE_MODE_TAGGED, 'Tagged'],
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
]
# Pass-through port types
PORT_TYPE_8P8C = 1000
PORT_TYPE_110_PUNCH = 1100
PORT_TYPE_BNC = 1200
PORT_TYPE_ST = 2000
PORT_TYPE_SC = 2100
PORT_TYPE_SC_APC = 2110
PORT_TYPE_FC = 2200
PORT_TYPE_LC = 2300
PORT_TYPE_LC_APC = 2310
PORT_TYPE_MTRJ = 2400
PORT_TYPE_MPO = 2500
PORT_TYPE_LSH = 2600
PORT_TYPE_LSH_APC = 2610
PORT_TYPE_CHOICES = [
[
'Copper',
[
[PORT_TYPE_8P8C, '8P8C'],
[PORT_TYPE_110_PUNCH, '110 Punch'],
[PORT_TYPE_BNC, 'BNC'],
],
],
[
'Fiber Optic',
[
[PORT_TYPE_FC, 'FC'],
[PORT_TYPE_LC, 'LC'],
[PORT_TYPE_LC_APC, 'LC/APC'],
[PORT_TYPE_LSH, 'LSH'],
[PORT_TYPE_LSH_APC, 'LSH/APC'],
[PORT_TYPE_MPO, 'MPO'],
[PORT_TYPE_MTRJ, 'MTRJ'],
[PORT_TYPE_SC, 'SC'],
[PORT_TYPE_SC_APC, 'SC/APC'],
[PORT_TYPE_ST, 'ST'],
]
]
]
# Device statuses
DEVICE_STATUS_OFFLINE = 0
DEVICE_STATUS_ACTIVE = 1
DEVICE_STATUS_PLANNED = 2
DEVICE_STATUS_STAGED = 3
DEVICE_STATUS_FAILED = 4
DEVICE_STATUS_INVENTORY = 5
DEVICE_STATUS_DECOMMISSIONING = 6
DEVICE_STATUS_CHOICES = [
[DEVICE_STATUS_ACTIVE, 'Active'],
[DEVICE_STATUS_OFFLINE, 'Offline'],
[DEVICE_STATUS_PLANNED, 'Planned'],
[DEVICE_STATUS_STAGED, 'Staged'],
[DEVICE_STATUS_FAILED, 'Failed'],
[DEVICE_STATUS_INVENTORY, 'Inventory'],
[DEVICE_STATUS_DECOMMISSIONING, 'Decommissioning'],
]
# 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/rack statuses
STATUS_CLASSES = {
0: 'warning',
1: 'success',
2: 'info',
3: 'primary',
4: 'danger',
5: 'default',
6: 'warning',
}
# Console/power/interface connection statuses # Console/power/interface connection statuses
CONNECTION_STATUS_PLANNED = False CONNECTION_STATUS_PLANNED = False
CONNECTION_STATUS_CONNECTED = True CONNECTION_STATUS_CONNECTED = True
@ -390,56 +34,6 @@ CABLE_TERMINATION_TYPES = [
'circuittermination', 'circuittermination',
] ]
# Cable types
CABLE_TYPE_CAT3 = 1300
CABLE_TYPE_CAT5 = 1500
CABLE_TYPE_CAT5E = 1510
CABLE_TYPE_CAT6 = 1600
CABLE_TYPE_CAT6A = 1610
CABLE_TYPE_CAT7 = 1700
CABLE_TYPE_DAC_ACTIVE = 1800
CABLE_TYPE_DAC_PASSIVE = 1810
CABLE_TYPE_COAXIAL = 1900
CABLE_TYPE_MMF = 3000
CABLE_TYPE_MMF_OM1 = 3010
CABLE_TYPE_MMF_OM2 = 3020
CABLE_TYPE_MMF_OM3 = 3030
CABLE_TYPE_MMF_OM4 = 3040
CABLE_TYPE_SMF = 3500
CABLE_TYPE_SMF_OS1 = 3510
CABLE_TYPE_SMF_OS2 = 3520
CABLE_TYPE_AOC = 3800
CABLE_TYPE_POWER = 5000
CABLE_TYPE_CHOICES = (
(
'Copper', (
(CABLE_TYPE_CAT3, 'CAT3'),
(CABLE_TYPE_CAT5, 'CAT5'),
(CABLE_TYPE_CAT5E, 'CAT5e'),
(CABLE_TYPE_CAT6, 'CAT6'),
(CABLE_TYPE_CAT6A, 'CAT6a'),
(CABLE_TYPE_CAT7, 'CAT7'),
(CABLE_TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
(CABLE_TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
(CABLE_TYPE_COAXIAL, 'Coaxial'),
),
),
(
'Fiber', (
(CABLE_TYPE_MMF, 'Multimode Fiber'),
(CABLE_TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
(CABLE_TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
(CABLE_TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
(CABLE_TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
(CABLE_TYPE_AOC, 'Active Optical Cabling (AOC)'),
),
),
(CABLE_TYPE_POWER, 'Power'),
)
CABLE_TERMINATION_TYPE_CHOICES = { CABLE_TERMINATION_TYPE_CHOICES = {
# (API endpoint, human-friendly name) # (API endpoint, human-friendly name)
'consoleport': ('console-ports', 'Console port'), 'consoleport': ('console-ports', 'Console port'),
@ -461,57 +55,3 @@ COMPATIBLE_TERMINATION_TYPES = {
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'], 'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
'circuittermination': ['interface', 'frontport', 'rearport'], 'circuittermination': ['interface', 'frontport', 'rearport'],
} }
LENGTH_UNIT_METER = 1200
LENGTH_UNIT_CENTIMETER = 1100
LENGTH_UNIT_MILLIMETER = 1000
LENGTH_UNIT_FOOT = 2100
LENGTH_UNIT_INCH = 2000
CABLE_LENGTH_UNIT_CHOICES = (
(LENGTH_UNIT_METER, 'Meters'),
(LENGTH_UNIT_CENTIMETER, 'Centimeters'),
(LENGTH_UNIT_FOOT, 'Feet'),
(LENGTH_UNIT_INCH, 'Inches'),
)
RACK_DIMENSION_UNIT_CHOICES = (
(LENGTH_UNIT_MILLIMETER, 'Millimeters'),
(LENGTH_UNIT_INCH, 'Inches'),
)
# Power feeds
POWERFEED_TYPE_PRIMARY = 1
POWERFEED_TYPE_REDUNDANT = 2
POWERFEED_TYPE_CHOICES = (
(POWERFEED_TYPE_PRIMARY, 'Primary'),
(POWERFEED_TYPE_REDUNDANT, 'Redundant'),
)
POWERFEED_SUPPLY_AC = 1
POWERFEED_SUPPLY_DC = 2
POWERFEED_SUPPLY_CHOICES = (
(POWERFEED_SUPPLY_AC, 'AC'),
(POWERFEED_SUPPLY_DC, 'DC'),
)
POWERFEED_PHASE_SINGLE = 1
POWERFEED_PHASE_3PHASE = 3
POWERFEED_PHASE_CHOICES = (
(POWERFEED_PHASE_SINGLE, 'Single phase'),
(POWERFEED_PHASE_3PHASE, 'Three-phase'),
)
POWERFEED_STATUS_OFFLINE = 0
POWERFEED_STATUS_ACTIVE = 1
POWERFEED_STATUS_PLANNED = 2
POWERFEED_STATUS_FAILED = 4
POWERFEED_STATUS_CHOICES = (
(POWERFEED_STATUS_ACTIVE, 'Active'),
(POWERFEED_STATUS_OFFLINE, 'Offline'),
(POWERFEED_STATUS_PLANNED, 'Planned'),
(POWERFEED_STATUS_FAILED, 'Failed'),
)
POWERFEED_LEG_A = 1
POWERFEED_LEG_B = 2
POWERFEED_LEG_C = 3
POWERFEED_LEG_CHOICES = (
(POWERFEED_LEG_A, 'A'),
(POWERFEED_LEG_B, 'B'),
(POWERFEED_LEG_C, 'C'),
)

View File

@ -49,7 +49,7 @@ class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
label='Search', label='Search',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=SITE_STATUS_CHOICES, choices=SiteStatusChoices,
null_value=None null_value=None
) )
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
@ -147,7 +147,7 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
label='Group', label='Group',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=RACK_STATUS_CHOICES, choices=RackStatusChoices,
null_value=None null_value=None
) )
role_id = django_filters.ModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
@ -511,7 +511,7 @@ class DeviceFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilter
label='Device model (slug)', label='Device model (slug)',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=DEVICE_STATUS_CHOICES, choices=DeviceStatusChoices,
null_value=None null_value=None
) )
is_full_depth = django_filters.BooleanFilter( is_full_depth = django_filters.BooleanFilter(
@ -663,7 +663,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
class ConsolePortFilter(DeviceComponentFilterSet): class ConsolePortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
null_value=None null_value=None
) )
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
@ -679,7 +679,7 @@ class ConsolePortFilter(DeviceComponentFilterSet):
class ConsoleServerPortFilter(DeviceComponentFilterSet): class ConsoleServerPortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
null_value=None null_value=None
) )
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
@ -695,7 +695,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
class PowerPortFilter(DeviceComponentFilterSet): class PowerPortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=PowerPortTypes.CHOICES, choices=PowerPortTypeChoices,
null_value=None null_value=None
) )
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
@ -711,7 +711,7 @@ class PowerPortFilter(DeviceComponentFilterSet):
class PowerOutletFilter(DeviceComponentFilterSet): class PowerOutletFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
null_value=None null_value=None
) )
cabled = django_filters.BooleanFilter( cabled = django_filters.BooleanFilter(
@ -789,7 +789,7 @@ class InterfaceFilter(django_filters.FilterSet):
label='Assigned VID' label='Assigned VID'
) )
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=IFACE_TYPE_CHOICES, choices=InterfaceTypeChoices,
null_value=None null_value=None
) )
@ -980,7 +980,7 @@ class CableFilter(django_filters.FilterSet):
label='Search', label='Search',
) )
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
choices=CABLE_TYPE_CHOICES choices=CableTypeChoices
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=CONNECTION_STATUS_CHOICES choices=CONNECTION_STATUS_CHOICES

View File

@ -1910,7 +1910,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": 1, "position": 1,
"face": 0, "face": "front",
"status": true, "status": true,
"primary_ip4": 1, "primary_ip4": 1,
"primary_ip6": null, "primary_ip6": null,
@ -1931,7 +1931,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": 17, "position": 17,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": 5, "primary_ip4": 5,
"primary_ip6": null, "primary_ip6": null,
@ -1952,7 +1952,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": 33, "position": 33,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -1973,7 +1973,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": 34, "position": 34,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -1994,7 +1994,7 @@
"site": 1, "site": 1,
"rack": 2, "rack": 2,
"position": 34, "position": 34,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -2015,7 +2015,7 @@
"site": 1, "site": 1,
"rack": 2, "rack": 2,
"position": 33, "position": 33,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -2036,7 +2036,7 @@
"site": 1, "site": 1,
"rack": 2, "rack": 2,
"position": 1, "position": 1,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": 3, "primary_ip4": 3,
"primary_ip6": null, "primary_ip6": null,
@ -2057,7 +2057,7 @@
"site": 1, "site": 1,
"rack": 2, "rack": 2,
"position": 17, "position": 17,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": 19, "primary_ip4": 19,
"primary_ip6": null, "primary_ip6": null,
@ -2078,7 +2078,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": 42, "position": 42,
"face": 0, "face": "rear",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -2099,7 +2099,7 @@
"site": 1, "site": 1,
"rack": 1, "rack": 1,
"position": null, "position": null,
"face": null, "face": "",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,
@ -2120,7 +2120,7 @@
"site": 1, "site": 1,
"rack": 2, "rack": 2,
"position": null, "position": null,
"face": null, "face": "",
"status": true, "status": true,
"primary_ip4": null, "primary_ip4": null,
"primary_ip6": null, "primary_ip6": null,

View File

@ -93,13 +93,13 @@ class InterfaceCommonForm:
tagged_vlans = self.cleaned_data['tagged_vlans'] tagged_vlans = self.cleaned_data['tagged_vlans']
# Untagged interfaces cannot be assigned tagged VLANs # Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans: if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
raise forms.ValidationError({ raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned." 'mode': "An access interface cannot have tagged VLANs assigned."
}) })
# Remove all tagged VLAN assignments from "tagged all" interfaces # Remove all tagged VLAN assignments from "tagged all" interfaces
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL: elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
self.cleaned_data['tagged_vlans'] = [] self.cleaned_data['tagged_vlans'] = []
@ -250,7 +250,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class SiteCSVForm(forms.ModelForm): class SiteCSVForm(forms.ModelForm):
status = CSVChoiceField( status = CSVChoiceField(
choices=SITE_STATUS_CHOICES, choices=SiteStatusChoices,
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
@ -289,7 +289,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
widget=forms.MultipleHiddenInput widget=forms.MultipleHiddenInput
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(SITE_STATUS_CHOICES), choices=add_blank_choice(SiteStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -338,7 +338,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
label='Search' label='Search'
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=SITE_STATUS_CHOICES, choices=SiteStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -500,7 +500,7 @@ class RackCSVForm(forms.ModelForm):
} }
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=RACK_STATUS_CHOICES, choices=RackStatusChoices,
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
@ -514,19 +514,16 @@ class RackCSVForm(forms.ModelForm):
} }
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=RACK_TYPE_CHOICES, choices=RackTypeChoices,
required=False, required=False,
help_text='Rack type' help_text='Rack type'
) )
width = forms.ChoiceField( width = forms.ChoiceField(
choices=( choices=RackWidthChoices,
(RACK_WIDTH_19IN, '19'),
(RACK_WIDTH_23IN, '23'),
),
help_text='Rail-to-rail width (in inches)' help_text='Rail-to-rail width (in inches)'
) )
outer_unit = CSVChoiceField( outer_unit = CSVChoiceField(
choices=RACK_DIMENSION_UNIT_CHOICES, choices=RackDimensionUnitChoices,
required=False, required=False,
help_text='Unit for outer dimensions' help_text='Unit for outer dimensions'
) )
@ -598,7 +595,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(RACK_STATUS_CHOICES), choices=add_blank_choice(RackStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -620,12 +617,12 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
required=False required=False
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(RACK_TYPE_CHOICES), choices=add_blank_choice(RackTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
width = forms.ChoiceField( width = forms.ChoiceField(
choices=add_blank_choice(RACK_WIDTH_CHOICES), choices=add_blank_choice(RackWidthChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -647,7 +644,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
min_value=1 min_value=1
) )
outer_unit = forms.ChoiceField( outer_unit = forms.ChoiceField(
choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES), choices=add_blank_choice(RackDimensionUnitChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -692,7 +689,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=RACK_STATUS_CHOICES, choices=RackStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -909,12 +906,10 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
value_field="slug", value_field="slug",
) )
) )
subdevice_role = forms.NullBooleanField( subdevice_role = forms.MultipleChoiceField(
choices=add_blank_choice(SubdeviceRoleChoices),
required=False, required=False,
label='Subdevice role', widget=StaticSelect2Multiple()
widget=StaticSelect2(
choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES)
)
) )
console_ports = forms.NullBooleanField( console_ports = forms.NullBooleanField(
required=False, required=False,
@ -981,7 +976,7 @@ class ConsolePortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -1003,7 +998,7 @@ class ConsoleServerPortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES), choices=add_blank_choice(ConsolePortTypeChoices),
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -1025,7 +1020,7 @@ class PowerPortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PowerPortTypes.CHOICES), choices=add_blank_choice(PowerPortTypeChoices),
required=False required=False
) )
maximum_draw = forms.IntegerField( maximum_draw = forms.IntegerField(
@ -1067,7 +1062,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PowerOutletTypes.CHOICES), choices=add_blank_choice(PowerOutletTypeChoices),
required=False required=False
) )
power_port = forms.ModelChoiceField( power_port = forms.ModelChoiceField(
@ -1075,7 +1070,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
required=False required=False
) )
feed_leg = forms.ChoiceField( feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES), choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -1108,7 +1103,7 @@ class InterfaceTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES, choices=InterfaceTypeChoices,
widget=StaticSelect2() widget=StaticSelect2()
) )
mgmt_only = forms.BooleanField( mgmt_only = forms.BooleanField(
@ -1123,7 +1118,7 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(IFACE_TYPE_CHOICES), choices=add_blank_choice(InterfaceTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -1165,7 +1160,7 @@ class FrontPortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
widget=StaticSelect2() widget=StaticSelect2()
) )
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
@ -1235,7 +1230,7 @@ class RearPortTemplateCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
positions = forms.IntegerField( positions = forms.IntegerField(
@ -1334,7 +1329,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
class InterfaceTemplateImportForm(ComponentTemplateImportForm): class InterfaceTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=InterfaceTypes.TYPE_CHOICES choices=InterfaceTypeChoices.CHOICES
) )
class Meta: class Meta:
@ -1343,15 +1338,10 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
'device_type', 'name', 'type', 'mgmt_only', 'device_type', 'name', 'type', 'mgmt_only',
] ]
def clean_type(self):
# Convert slug value to field integer value
slug = self.cleaned_data['type']
return InterfaceTypes.slug_to_integer(slug)
class FrontPortTemplateImportForm(ComponentTemplateImportForm): class FrontPortTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PortTypes.TYPE_CHOICES choices=PortTypeChoices.CHOICES
) )
rear_port = forms.ModelChoiceField( rear_port = forms.ModelChoiceField(
queryset=RearPortTemplate.objects.all(), queryset=RearPortTemplate.objects.all(),
@ -1365,15 +1355,10 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position',
] ]
def clean_type(self):
# Convert slug value to field integer value
slug = self.cleaned_data['type']
return PortTypes.slug_to_integer(slug)
class RearPortTemplateImportForm(ComponentTemplateImportForm): class RearPortTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PortTypes.TYPE_CHOICES choices=PortTypeChoices.CHOICES
) )
class Meta: class Meta:
@ -1382,11 +1367,6 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
'device_type', 'name', 'type', 'positions', 'device_type', 'name', 'type', 'positions',
] ]
def clean_type(self):
# Convert slug value to field integer value
slug = self.cleaned_data['type']
return PortTypes.slug_to_integer(slug)
class DeviceBayTemplateImportForm(ComponentTemplateImportForm): class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
@ -1702,7 +1682,7 @@ class BaseDeviceCSVForm(forms.ModelForm):
} }
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=DEVICE_STATUS_CHOICES, choices=DeviceStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
@ -1746,7 +1726,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
help_text='Name of parent rack' help_text='Name of parent rack'
) )
face = CSVChoiceField( face = CSVChoiceField(
choices=RACK_FACE_CHOICES, choices=DeviceFaceChoices,
required=False, required=False,
help_text='Mounted rack face' help_text='Mounted rack face'
) )
@ -1870,7 +1850,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(DEVICE_STATUS_CHOICES), choices=add_blank_choice(DeviceStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -1981,7 +1961,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=DEVICE_STATUS_CHOICES, choices=DeviceStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -2063,7 +2043,7 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm): class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES, choices=InterfaceTypeChoices,
widget=StaticSelect2() widget=StaticSelect2()
) )
enabled = forms.BooleanField( enabled = forms.BooleanField(
@ -2115,7 +2095,7 @@ class ConsolePortCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES), choices=add_blank_choice(ConsolePortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2172,7 +2152,7 @@ class ConsoleServerPortCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES), choices=add_blank_choice(ConsolePortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2191,7 +2171,7 @@ class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditF
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES), choices=add_blank_choice(ConsolePortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2264,7 +2244,7 @@ class PowerPortCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PowerPortTypes.CHOICES), choices=add_blank_choice(PowerPortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2344,7 +2324,7 @@ class PowerOutletCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PowerOutletTypes.CHOICES), choices=add_blank_choice(PowerOutletTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2353,7 +2333,7 @@ class PowerOutletCreateForm(ComponentForm):
required=False required=False
) )
feed_leg = forms.ChoiceField( feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES), choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False required=False
) )
description = forms.CharField( description = forms.CharField(
@ -2391,7 +2371,7 @@ class PowerOutletCSVForm(forms.ModelForm):
} }
) )
feed_leg = CSVChoiceField( feed_leg = CSVChoiceField(
choices=POWERFEED_LEG_CHOICES, choices=PowerOutletFeedLegChoices,
required=False, required=False,
) )
@ -2428,11 +2408,11 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
required=False required=False
) )
feed_leg = forms.ChoiceField( feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES), choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False, required=False,
) )
power_port = forms.ModelChoiceField( power_port = forms.ModelChoiceField(
@ -2529,12 +2509,14 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
if self.is_bound: if self.is_bound:
device = Device.objects.get(pk=self.data['device']) device = Device.objects.get(pk=self.data['device'])
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
device__in=[device, device.get_vc_master()], type=IFACE_TYPE_LAG device__in=[device, device.get_vc_master()],
type=InterfaceTypeChoices.TYPE_LAG
) )
else: else:
device = self.instance.device device = self.instance.device
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG device__in=[self.instance.device, self.instance.device.get_vc_master()],
type=InterfaceTypeChoices.TYPE_LAG
) )
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
@ -2573,7 +2555,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES, choices=InterfaceTypeChoices,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
enabled = forms.BooleanField( enabled = forms.BooleanField(
@ -2605,7 +2587,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
required=False required=False
) )
mode = forms.ChoiceField( mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES), choices=add_blank_choice(InterfaceModeChoices),
required=False, required=False,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
@ -2642,7 +2624,8 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
# Limit LAG choices to interfaces belonging to this device (or its VC master) # Limit LAG choices to interfaces belonging to this device (or its VC master)
if self.parent is not None: if self.parent is not None:
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
device__in=[self.parent, self.parent.get_vc_master()], type=IFACE_TYPE_LAG device__in=[self.parent, self.parent.get_vc_master()],
type=InterfaceTypeChoices.TYPE_LAG
) )
else: else:
self.fields['lag'].queryset = Interface.objects.none() self.fields['lag'].queryset = Interface.objects.none()
@ -2707,10 +2690,10 @@ class InterfaceCSVForm(forms.ModelForm):
} }
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=IFACE_TYPE_CHOICES, choices=InterfaceTypeChoices,
) )
mode = CSVChoiceField( mode = CSVChoiceField(
choices=IFACE_MODE_CHOICES, choices=InterfaceModeChoices,
required=False, required=False,
) )
@ -2732,7 +2715,7 @@ class InterfaceCSVForm(forms.ModelForm):
if device: if device:
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
device__in=[device, device.get_vc_master()], type=IFACE_TYPE_LAG device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG
) )
else: else:
self.fields['lag'].queryset = Interface.objects.none() self.fields['lag'].queryset = Interface.objects.none()
@ -2751,7 +2734,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(IFACE_TYPE_CHOICES), choices=add_blank_choice(InterfaceTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2785,7 +2768,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
required=False required=False
) )
mode = forms.ChoiceField( mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES), choices=add_blank_choice(InterfaceModeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -2821,7 +2804,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
if device is not None: if device is not None:
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
device__in=[device, device.get_vc_master()], device__in=[device, device.get_vc_master()],
type=IFACE_TYPE_LAG type=InterfaceTypeChoices.TYPE_LAG
) )
else: else:
self.fields['lag'].choices = [] self.fields['lag'].choices = []
@ -2911,7 +2894,7 @@ class FrontPortCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
@ -2983,7 +2966,7 @@ class FrontPortCSVForm(forms.ModelForm):
} }
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
) )
class Meta: class Meta:
@ -3019,7 +3002,7 @@ class FrontPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PORT_TYPE_CHOICES), choices=add_blank_choice(PortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -3077,7 +3060,7 @@ class RearPortCreateForm(ComponentForm):
label='Name' label='Name'
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
positions = forms.IntegerField( positions = forms.IntegerField(
@ -3101,7 +3084,7 @@ class RearPortCSVForm(forms.ModelForm):
} }
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=PORT_TYPE_CHOICES, choices=PortTypeChoices,
) )
class Meta: class Meta:
@ -3115,7 +3098,7 @@ class RearPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PORT_TYPE_CHOICES), choices=add_blank_choice(PortTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -3449,12 +3432,12 @@ class CableCSVForm(forms.ModelForm):
help_text='Connection status' help_text='Connection status'
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=CABLE_TYPE_CHOICES, choices=CableTypeChoices,
required=False, required=False,
help_text='Cable type' help_text='Cable type'
) )
length_unit = CSVChoiceField( length_unit = CSVChoiceField(
choices=CABLE_LENGTH_UNIT_CHOICES, choices=CableLengthUnitChoices,
required=False, required=False,
help_text='Length unit' help_text='Length unit'
) )
@ -3534,7 +3517,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
widget=forms.MultipleHiddenInput widget=forms.MultipleHiddenInput
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(CABLE_TYPE_CHOICES), choices=add_blank_choice(CableTypeChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -3559,7 +3542,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
required=False required=False
) )
length_unit = forms.ChoiceField( length_unit = forms.ChoiceField(
choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES), choices=add_blank_choice(CableLengthUnitChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -3608,7 +3591,7 @@ class CableFilterForm(BootstrapMixin, forms.Form):
) )
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
choices=add_blank_choice(CABLE_TYPE_CHOICES), choices=add_blank_choice(CableTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -3677,7 +3660,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
rack=device_bay.device.rack, rack=device_bay.device.rack,
parent_bay__isnull=True, parent_bay__isnull=True,
device_type__u_height=0, device_type__u_height=0,
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
).exclude(pk=device_bay.device.pk) ).exclude(pk=device_bay.device.pk)
@ -3725,7 +3708,7 @@ class DeviceBayCSVForm(forms.ModelForm):
rack=device.rack, rack=device.rack,
parent_bay__isnull=True, parent_bay__isnull=True,
device_type__u_height=0, device_type__u_height=0,
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
).exclude(pk=device.pk) ).exclude(pk=device.pk)
else: else:
self.fields['installed_device'].queryset = Interface.objects.none() self.fields['installed_device'].queryset = Interface.objects.none()
@ -4214,22 +4197,22 @@ class PowerFeedCSVForm(forms.ModelForm):
help_text="Rack name (optional)" help_text="Rack name (optional)"
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=POWERFEED_STATUS_CHOICES, choices=PowerFeedStatusChoices,
required=False, required=False,
help_text='Operational status' help_text='Operational status'
) )
type = CSVChoiceField( type = CSVChoiceField(
choices=POWERFEED_TYPE_CHOICES, choices=PowerFeedTypeChoices,
required=False, required=False,
help_text='Primary or redundant' help_text='Primary or redundant'
) )
supply = CSVChoiceField( supply = CSVChoiceField(
choices=POWERFEED_SUPPLY_CHOICES, choices=PowerFeedSupplyChoices,
required=False, required=False,
help_text='AC/DC' help_text='AC/DC'
) )
phase = CSVChoiceField( phase = CSVChoiceField(
choices=POWERFEED_PHASE_CHOICES, choices=PowerFeedPhaseChoices,
required=False, required=False,
help_text='Single or three-phase' help_text='Single or three-phase'
) )
@ -4289,25 +4272,25 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_STATUS_CHOICES), choices=add_blank_choice(PowerFeedStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES), choices=add_blank_choice(PowerFeedTypeChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
supply = forms.ChoiceField( supply = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES), choices=add_blank_choice(PowerFeedSupplyChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
) )
phase = forms.ChoiceField( phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES), choices=add_blank_choice(PowerFeedPhaseChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2() widget=StaticSelect2()
@ -4368,22 +4351,22 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=POWERFEED_STATUS_CHOICES, choices=PowerFeedStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES), choices=add_blank_choice(PowerFeedTypeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
supply = forms.ChoiceField( supply = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES), choices=add_blank_choice(PowerFeedSupplyChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
phase = forms.ChoiceField( phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES), choices=add_blank_choice(PowerFeedPhaseChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )

View File

@ -0,0 +1,35 @@
from django.db import migrations, models
SITE_STATUS_CHOICES = (
(1, 'active'),
(2, 'planned'),
(4, 'retired'),
)
def site_status_to_slug(apps, schema_editor):
Site = apps.get_model('dcim', 'Site')
for id, slug in SITE_STATUS_CHOICES:
Site.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0077_power_types'),
]
operations = [
# Site.status
migrations.AlterField(
model_name='site',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=site_status_to_slug
),
]

View File

@ -0,0 +1,92 @@
from django.db import migrations, models
RACK_TYPE_CHOICES = (
(100, '2-post-frame'),
(200, '4-post-frame'),
(300, '4-post-cabinet'),
(1000, 'wall-frame'),
(1100, 'wall-cabinet'),
)
RACK_STATUS_CHOICES = (
(0, 'reserved'),
(1, 'available'),
(2, 'planned'),
(3, 'active'),
(4, 'deprecated'),
)
RACK_DIMENSION_CHOICES = (
(1000, 'mm'),
(2000, 'in'),
)
def rack_type_to_slug(apps, schema_editor):
Rack = apps.get_model('dcim', 'Rack')
for id, slug in RACK_TYPE_CHOICES:
Rack.objects.filter(type=str(id)).update(type=slug)
def rack_status_to_slug(apps, schema_editor):
Rack = apps.get_model('dcim', 'Rack')
for id, slug in RACK_STATUS_CHOICES:
Rack.objects.filter(status=str(id)).update(status=slug)
def rack_outer_unit_to_slug(apps, schema_editor):
Rack = apps.get_model('dcim', 'Rack')
for id, slug in RACK_DIMENSION_CHOICES:
Rack.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0078_3569_site_fields'),
]
operations = [
# Rack.type
migrations.AlterField(
model_name='rack',
name='type',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=rack_type_to_slug
),
migrations.AlterField(
model_name='rack',
name='type',
field=models.CharField(blank=True, max_length=50),
),
# Rack.status
migrations.AlterField(
model_name='rack',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=rack_status_to_slug
),
# Rack.outer_unit
migrations.AlterField(
model_name='rack',
name='outer_unit',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=rack_outer_unit_to_slug
),
migrations.AlterField(
model_name='rack',
name='outer_unit',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -0,0 +1,39 @@
from django.db import migrations, models
SUBDEVICE_ROLE_CHOICES = (
('true', 'parent'),
('false', 'child'),
)
def devicetype_subdevicerole_to_slug(apps, schema_editor):
DeviceType = apps.get_model('dcim', 'DeviceType')
for boolean, slug in SUBDEVICE_ROLE_CHOICES:
DeviceType.objects.filter(subdevice_role=boolean).update(subdevice_role=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0079_3569_rack_fields'),
]
operations = [
# DeviceType.subdevice_role
migrations.AlterField(
model_name='devicetype',
name='subdevice_role',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=devicetype_subdevicerole_to_slug
),
migrations.AlterField(
model_name='devicetype',
name='subdevice_role',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -0,0 +1,65 @@
from django.db import migrations, models
DEVICE_FACE_CHOICES = (
(0, 'front'),
(1, 'rear'),
)
DEVICE_STATUS_CHOICES = (
(0, 'offline'),
(1, 'active'),
(2, 'planned'),
(3, 'staged'),
(4, 'failed'),
(5, 'inventory'),
(6, 'decommissioning'),
)
def device_face_to_slug(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for id, slug in DEVICE_FACE_CHOICES:
Device.objects.filter(face=str(id)).update(face=slug)
def device_status_to_slug(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for id, slug in DEVICE_STATUS_CHOICES:
Device.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0080_3569_devicetype_fields'),
]
operations = [
# Device.face
migrations.AlterField(
model_name='device',
name='face',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=device_face_to_slug
),
migrations.AlterField(
model_name='device',
name='face',
field=models.CharField(blank=True, max_length=50),
),
# Device.status
migrations.AlterField(
model_name='device',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=device_status_to_slug
),
]

View File

@ -0,0 +1,147 @@
from django.db import migrations, models
INTERFACE_TYPE_CHOICES = (
(0, 'virtual'),
(200, 'lag'),
(800, '100base-tx'),
(1000, '1000base-t'),
(1050, '1000base-x-gbic'),
(1100, '1000base-x-sfp'),
(1120, '2.5gbase-t'),
(1130, '5gbase-t'),
(1150, '10gbase-t'),
(1170, '10gbase-cx4'),
(1200, '10gbase-x-sfpp'),
(1300, '10gbase-x-xfp'),
(1310, '10gbase-x-xenpak'),
(1320, '10gbase-x-x2'),
(1350, '25gbase-x-sfp28'),
(1400, '40gbase-x-qsfpp'),
(1420, '50gbase-x-sfp28'),
(1500, '100gbase-x-cfp'),
(1510, '100gbase-x-cfp2'),
(1520, '100gbase-x-cfp4'),
(1550, '100gbase-x-cpak'),
(1600, '100gbase-x-qsfp28'),
(1650, '200gbase-x-cfp2'),
(1700, '200gbase-x-qsfp56'),
(1750, '400gbase-x-qsfpdd'),
(1800, '400gbase-x-osfp'),
(2600, 'ieee802.11a'),
(2610, 'ieee802.11g'),
(2620, 'ieee802.11n'),
(2630, 'ieee802.11ac'),
(2640, 'ieee802.11ad'),
(2810, 'gsm'),
(2820, 'cdma'),
(2830, 'lte'),
(6100, 'sonet-oc3'),
(6200, 'sonet-oc12'),
(6300, 'sonet-oc48'),
(6400, 'sonet-oc192'),
(6500, 'sonet-oc768'),
(6600, 'sonet-oc1920'),
(6700, 'sonet-oc3840'),
(3010, '1gfc-sfp'),
(3020, '2gfc-sfp'),
(3040, '4gfc-sfp'),
(3080, '8gfc-sfpp'),
(3160, '16gfc-sfpp'),
(3320, '32gfc-sfp28'),
(3400, '128gfc-sfp28'),
(7010, 'inifiband-sdr'),
(7020, 'inifiband-ddr'),
(7030, 'inifiband-qdr'),
(7040, 'inifiband-fdr10'),
(7050, 'inifiband-fdr'),
(7060, 'inifiband-edr'),
(7070, 'inifiband-hdr'),
(7080, 'inifiband-ndr'),
(7090, 'inifiband-xdr'),
(4000, 't1'),
(4010, 'e1'),
(4040, 't3'),
(4050, 'e3'),
(5000, 'cisco-stackwise'),
(5050, 'cisco-stackwise-plus'),
(5100, 'cisco-flexstack'),
(5150, 'cisco-flexstack-plus'),
(5200, 'juniper-vcp'),
(5300, 'extreme-summitstack'),
(5310, 'extreme-summitstack-128'),
(5320, 'extreme-summitstack-256'),
(5330, 'extreme-summitstack-512'),
)
INTERFACE_MODE_CHOICES = (
(100, 'access'),
(200, 'tagged'),
(300, 'tagged-all'),
)
def interfacetemplate_type_to_slug(apps, schema_editor):
InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate')
for id, slug in INTERFACE_TYPE_CHOICES:
InterfaceTemplate.objects.filter(type=id).update(type=slug)
def interface_type_to_slug(apps, schema_editor):
Interface = apps.get_model('dcim', 'Interface')
for id, slug in INTERFACE_TYPE_CHOICES:
Interface.objects.filter(type=id).update(type=slug)
def interface_mode_to_slug(apps, schema_editor):
Interface = apps.get_model('dcim', 'Interface')
for id, slug in INTERFACE_MODE_CHOICES:
Interface.objects.filter(mode=id).update(mode=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0081_3569_device_fields'),
]
operations = [
# InterfaceTemplate.type
migrations.AlterField(
model_name='interfacetemplate',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=interfacetemplate_type_to_slug
),
# Interface.type
migrations.AlterField(
model_name='interface',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=interface_type_to_slug
),
# Interface.mode
migrations.AlterField(
model_name='interface',
name='mode',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=interface_mode_to_slug
),
migrations.AlterField(
model_name='interface',
name='mode',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -0,0 +1,93 @@
from django.db import migrations, models
PORT_TYPE_CHOICES = (
(1000, '8p8c'),
(1100, '110-punch'),
(1200, 'bnc'),
(2000, 'st'),
(2100, 'sc'),
(2110, 'sc-apc'),
(2200, 'fc'),
(2300, 'lc'),
(2310, 'lc-apc'),
(2400, 'mtrj'),
(2500, 'mpo'),
(2600, 'lsh'),
(2610, 'lsh-apc'),
)
def frontporttemplate_type_to_slug(apps, schema_editor):
FrontPortTemplate = apps.get_model('dcim', 'FrontPortTemplate')
for id, slug in PORT_TYPE_CHOICES:
FrontPortTemplate.objects.filter(type=id).update(type=slug)
def rearporttemplate_type_to_slug(apps, schema_editor):
RearPortTemplate = apps.get_model('dcim', 'RearPortTemplate')
for id, slug in PORT_TYPE_CHOICES:
RearPortTemplate.objects.filter(type=id).update(type=slug)
def frontport_type_to_slug(apps, schema_editor):
FrontPort = apps.get_model('dcim', 'FrontPort')
for id, slug in PORT_TYPE_CHOICES:
FrontPort.objects.filter(type=id).update(type=slug)
def rearport_type_to_slug(apps, schema_editor):
RearPort = apps.get_model('dcim', 'RearPort')
for id, slug in PORT_TYPE_CHOICES:
RearPort.objects.filter(type=id).update(type=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0082_3569_interface_fields'),
]
operations = [
# FrontPortTemplate.type
migrations.AlterField(
model_name='frontporttemplate',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=frontporttemplate_type_to_slug
),
# RearPortTemplate.type
migrations.AlterField(
model_name='rearporttemplate',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=rearporttemplate_type_to_slug
),
# FrontPort.type
migrations.AlterField(
model_name='frontport',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=frontport_type_to_slug
),
# RearPort.type
migrations.AlterField(
model_name='rearport',
name='type',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=rearport_type_to_slug
),
]

View File

@ -0,0 +1,85 @@
from django.db import migrations, models
CABLE_TYPE_CHOICES = (
(1300, 'cat3'),
(1500, 'cat5'),
(1510, 'cat5e'),
(1600, 'cat6'),
(1610, 'cat6a'),
(1700, 'cat7'),
(1800, 'dac-active'),
(1810, 'dac-passive'),
(1900, 'coaxial'),
(3000, 'mmf'),
(3010, 'mmf-om1'),
(3020, 'mmf-om2'),
(3030, 'mmf-om3'),
(3040, 'mmf-om4'),
(3500, 'smf'),
(3510, 'smf-os1'),
(3520, 'smf-os2'),
(3800, 'aoc'),
(5000, 'power'),
)
CABLE_LENGTH_UNIT_CHOICES = (
(1200, 'm'),
(1100, 'cm'),
(2100, 'ft'),
(2000, 'in'),
)
def cable_type_to_slug(apps, schema_editor):
Cable = apps.get_model('dcim', 'Cable')
for id, slug in CABLE_TYPE_CHOICES:
Cable.objects.filter(type=id).update(type=slug)
def cable_length_unit_to_slug(apps, schema_editor):
Cable = apps.get_model('dcim', 'Cable')
for id, slug in CABLE_LENGTH_UNIT_CHOICES:
Cable.objects.filter(length_unit=id).update(length_unit=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0082_3569_port_fields'),
]
operations = [
# Cable.type
migrations.AlterField(
model_name='cable',
name='type',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=cable_type_to_slug
),
migrations.AlterField(
model_name='cable',
name='type',
field=models.CharField(blank=True, max_length=50),
),
# Cable.length_unit
migrations.AlterField(
model_name='cable',
name='length_unit',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=cable_length_unit_to_slug
),
migrations.AlterField(
model_name='cable',
name='length_unit',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -0,0 +1,100 @@
from django.db import migrations, models
POWERFEED_STATUS_CHOICES = (
(0, 'offline'),
(1, 'active'),
(2, 'planned'),
(4, 'failed'),
)
POWERFEED_TYPE_CHOICES = (
(1, 'primary'),
(2, 'redundant'),
)
POWERFEED_SUPPLY_CHOICES = (
(1, 'ac'),
(2, 'dc'),
)
POWERFEED_PHASE_CHOICES = (
(1, 'single-phase'),
(3, 'three-phase'),
)
def powerfeed_status_to_slug(apps, schema_editor):
PowerFeed = apps.get_model('dcim', 'PowerFeed')
for id, slug in POWERFEED_STATUS_CHOICES:
PowerFeed.objects.filter(status=id).update(status=slug)
def powerfeed_type_to_slug(apps, schema_editor):
PowerFeed = apps.get_model('dcim', 'PowerFeed')
for id, slug in POWERFEED_TYPE_CHOICES:
PowerFeed.objects.filter(type=id).update(type=slug)
def powerfeed_supply_to_slug(apps, schema_editor):
PowerFeed = apps.get_model('dcim', 'PowerFeed')
for id, slug in POWERFEED_SUPPLY_CHOICES:
PowerFeed.objects.filter(supply=id).update(supply=slug)
def powerfeed_phase_to_slug(apps, schema_editor):
PowerFeed = apps.get_model('dcim', 'PowerFeed')
for id, slug in POWERFEED_PHASE_CHOICES:
PowerFeed.objects.filter(phase=id).update(phase=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0083_3569_cable_fields'),
]
operations = [
# PowerFeed.status
migrations.AlterField(
model_name='powerfeed',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=powerfeed_status_to_slug
),
# PowerFeed.type
migrations.AlterField(
model_name='powerfeed',
name='type',
field=models.CharField(default='primary', max_length=50),
),
migrations.RunPython(
code=powerfeed_type_to_slug
),
# PowerFeed.supply
migrations.AlterField(
model_name='powerfeed',
name='supply',
field=models.CharField(default='ac', max_length=50),
),
migrations.RunPython(
code=powerfeed_supply_to_slug
),
# PowerFeed.phase
migrations.AlterField(
model_name='powerfeed',
name='phase',
field=models.CharField(default='single-phase', max_length=50),
),
migrations.RunPython(
code=powerfeed_phase_to_slug
),
]

View File

@ -0,0 +1,62 @@
from django.db import migrations, models
POWEROUTLET_FEED_LEG_CHOICES_CHOICES = (
(1, 'A'),
(2, 'B'),
(3, 'C'),
)
def poweroutlettemplate_feed_leg_to_slug(apps, schema_editor):
PowerOutletTemplate = apps.get_model('dcim', 'PowerOutletTemplate')
for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES:
PowerOutletTemplate.objects.filter(feed_leg=id).update(feed_leg=slug)
def poweroutlet_feed_leg_to_slug(apps, schema_editor):
PowerOutlet = apps.get_model('dcim', 'PowerOutlet')
for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES:
PowerOutlet.objects.filter(feed_leg=id).update(feed_leg=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('dcim', '0084_3569_powerfeed_fields'),
]
operations = [
# PowerOutletTemplate.feed_leg
migrations.AlterField(
model_name='poweroutlettemplate',
name='feed_leg',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=poweroutlettemplate_feed_leg_to_slug
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='feed_leg',
field=models.CharField(blank=True, max_length=50),
),
# PowerOutlet.feed_leg
migrations.AlterField(
model_name='poweroutlet',
name='feed_leg',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=poweroutlet_feed_leg_to_slug
),
migrations.AlterField(
model_name='poweroutlet',
name='feed_leg',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -245,9 +245,10 @@ class Site(ChangeLoggedModel, CustomFieldModel):
slug = models.SlugField( slug = models.SlugField(
unique=True unique=True
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=SITE_STATUS_CHOICES, max_length=50,
default=SITE_STATUS_ACTIVE choices=SiteStatusChoices,
default=SiteStatusChoices.STATUS_ACTIVE
) )
region = models.ForeignKey( region = models.ForeignKey(
to='dcim.Region', to='dcim.Region',
@ -331,6 +332,12 @@ class Site(ChangeLoggedModel, CustomFieldModel):
'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments',
] ]
STATUS_CLASS_MAP = {
SiteStatusChoices.STATUS_ACTIVE: 'success',
SiteStatusChoices.STATUS_PLANNED: 'info',
SiteStatusChoices.STATUS_RETIRED: 'danger',
}
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@ -362,7 +369,7 @@ class Site(ChangeLoggedModel, CustomFieldModel):
) )
def get_status_class(self): def get_status_class(self):
return STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
# #
@ -473,9 +480,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
blank=True, blank=True,
null=True null=True
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=RACK_STATUS_CHOICES, max_length=50,
default=RACK_STATUS_ACTIVE choices=RackStatusChoices,
default=RackStatusChoices.STATUS_ACTIVE
) )
role = models.ForeignKey( role = models.ForeignKey(
to='dcim.RackRole', to='dcim.RackRole',
@ -497,15 +505,15 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
verbose_name='Asset tag', verbose_name='Asset tag',
help_text='A unique tag used to identify this rack' help_text='A unique tag used to identify this rack'
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=RACK_TYPE_CHOICES, choices=RackTypeChoices,
max_length=50,
blank=True, blank=True,
null=True,
verbose_name='Type' verbose_name='Type'
) )
width = models.PositiveSmallIntegerField( width = models.PositiveSmallIntegerField(
choices=RACK_WIDTH_CHOICES, choices=RackWidthChoices,
default=RACK_WIDTH_19IN, default=RackWidthChoices.WIDTH_19IN,
verbose_name='Width', verbose_name='Width',
help_text='Rail-to-rail width' help_text='Rail-to-rail width'
) )
@ -527,10 +535,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
blank=True, blank=True,
null=True null=True
) )
outer_unit = models.PositiveSmallIntegerField( outer_unit = models.CharField(
choices=RACK_DIMENSION_UNIT_CHOICES, max_length=50,
choices=RackDimensionUnitChoices,
blank=True, blank=True,
null=True
) )
comments = models.TextField( comments = models.TextField(
blank=True blank=True
@ -552,6 +560,14 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
] ]
STATUS_CLASS_MAP = {
RackStatusChoices.STATUS_RESERVED: 'warning',
RackStatusChoices.STATUS_AVAILABLE: 'success',
RackStatusChoices.STATUS_PLANNED: 'info',
RackStatusChoices.STATUS_ACTIVE: 'primary',
RackStatusChoices.STATUS_DEPRECATED: 'danger',
}
class Meta: class Meta:
ordering = ['site', 'group', 'name'] ordering = ['site', 'group', 'name']
unique_together = [ unique_together = [
@ -568,10 +584,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
def clean(self): def clean(self):
# Validate outer dimensions and unit # Validate outer dimensions and unit
if (self.outer_width is not None or self.outer_depth is not None) and self.outer_unit is None: if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
raise ValidationError("Must specify a unit when setting an outer width/depth") raise ValidationError("Must specify a unit when setting an outer width/depth")
elif self.outer_width is None and self.outer_depth is None: elif self.outer_width is None and self.outer_depth is None:
self.outer_unit = None self.outer_unit = ''
if self.pk: if self.pk:
# Validate that Rack is tall enough to house the installed Devices # Validate that Rack is tall enough to house the installed Devices
@ -644,9 +660,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return "" return ""
def get_status_class(self): def get_status_class(self):
return STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False): def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None, remove_redundant=False):
""" """
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'} Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy. Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.
@ -678,10 +694,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return [u for u in elevation.values()] return [u for u in elevation.values()]
def get_front_elevation(self): def get_front_elevation(self):
return self.get_rack_units(face=RACK_FACE_FRONT, remove_redundant=True) return self.get_rack_units(face=DeviceFaceChoices.FACE_FRONT, remove_redundant=True)
def get_rear_elevation(self): def get_rear_elevation(self):
return self.get_rack_units(face=RACK_FACE_REAR, remove_redundant=True) return self.get_rack_units(face=DeviceFaceChoices.FACE_REAR, remove_redundant=True)
def get_available_units(self, u_height=1, rack_face=None, exclude=list()): def get_available_units(self, u_height=1, rack_face=None, exclude=list()):
""" """
@ -910,12 +926,13 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
verbose_name='Is full depth', verbose_name='Is full depth',
help_text='Device consumes both front and rear rack faces' help_text='Device consumes both front and rear rack faces'
) )
subdevice_role = models.NullBooleanField( subdevice_role = models.CharField(
default=None, max_length=50,
choices=SubdeviceRoleChoices,
blank=True,
verbose_name='Parent/child status', verbose_name='Parent/child status',
choices=SUBDEVICE_ROLE_CHOICES, help_text='Parent devices house child devices in device bays. Leave blank '
help_text='Parent devices house child devices in device bays. Select ' 'if this device type is neither a parent nor a child.'
'"None" if this device type is neither a parent nor a child.'
) )
comments = models.TextField( comments = models.TextField(
blank=True blank=True
@ -959,7 +976,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
self.part_number, self.part_number,
self.u_height, self.u_height,
self.is_full_depth, self.is_full_depth,
self.get_subdevice_role_display() if self.subdevice_role else None, self.get_subdevice_role_display(),
self.comments, self.comments,
) )
@ -979,13 +996,15 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
"{}U".format(d, d.rack, self.u_height) "{}U".format(d, d.rack, self.u_height)
}) })
if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count(): if (
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
) and self.device_bay_templates.count():
raise ValidationError({ raise ValidationError({
'subdevice_role': "Must delete all device bay templates associated with this device before " 'subdevice_role': "Must delete all device bay templates associated with this device before "
"declassifying it as a parent device." "declassifying it as a parent device."
}) })
if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD: if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
raise ValidationError({ raise ValidationError({
'u_height': "Child device types must be 0U." 'u_height': "Child device types must be 0U."
}) })
@ -996,11 +1015,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
@property @property
def is_parent_device(self): def is_parent_device(self):
return bool(self.subdevice_role) return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT
@property @property
def is_child_device(self): def is_child_device(self):
return bool(self.subdevice_role is False) return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
class ConsolePortTemplate(ComponentTemplateModel): class ConsolePortTemplate(ComponentTemplateModel):
@ -1017,7 +1036,7 @@ class ConsolePortTemplate(ComponentTemplateModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
blank=True blank=True
) )
@ -1052,7 +1071,7 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
blank=True blank=True
) )
@ -1087,7 +1106,7 @@ class PowerPortTemplate(ComponentTemplateModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerPortTypes.CHOICES, choices=PowerPortTypeChoices,
blank=True blank=True
) )
maximum_draw = models.PositiveSmallIntegerField( maximum_draw = models.PositiveSmallIntegerField(
@ -1135,7 +1154,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
blank=True blank=True
) )
power_port = models.ForeignKey( power_port = models.ForeignKey(
@ -1145,10 +1164,10 @@ class PowerOutletTemplate(ComponentTemplateModel):
null=True, null=True,
related_name='poweroutlet_templates' related_name='poweroutlet_templates'
) )
feed_leg = models.PositiveSmallIntegerField( feed_leg = models.CharField(
choices=POWERFEED_LEG_CHOICES, max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True, blank=True,
null=True,
help_text="Phase (for three-phase feeds)" help_text="Phase (for three-phase feeds)"
) )
@ -1194,9 +1213,9 @@ class InterfaceTemplate(ComponentTemplateModel):
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=IFACE_TYPE_CHOICES, max_length=50,
default=IFACE_TYPE_10GE_SFP_PLUS choices=InterfaceTypeChoices
) )
mgmt_only = models.BooleanField( mgmt_only = models.BooleanField(
default=False, default=False,
@ -1233,8 +1252,9 @@ class FrontPortTemplate(ComponentTemplateModel):
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=PORT_TYPE_CHOICES max_length=50,
choices=PortTypeChoices
) )
rear_port = models.ForeignKey( rear_port = models.ForeignKey(
to='dcim.RearPortTemplate', to='dcim.RearPortTemplate',
@ -1300,8 +1320,9 @@ class RearPortTemplate(ComponentTemplateModel):
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=PORT_TYPE_CHOICES max_length=50,
choices=PortTypeChoices
) )
positions = models.PositiveSmallIntegerField( positions = models.PositiveSmallIntegerField(
default=1, default=1,
@ -1526,16 +1547,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
verbose_name='Position (U)', verbose_name='Position (U)',
help_text='The lowest-numbered unit occupied by the device' help_text='The lowest-numbered unit occupied by the device'
) )
face = models.PositiveSmallIntegerField( face = models.CharField(
max_length=50,
blank=True, blank=True,
null=True, choices=DeviceFaceChoices,
choices=RACK_FACE_CHOICES,
verbose_name='Rack face' verbose_name='Rack face'
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=DEVICE_STATUS_CHOICES, max_length=50,
default=DEVICE_STATUS_ACTIVE, choices=DeviceStatusChoices,
verbose_name='Status' default=DeviceStatusChoices.STATUS_ACTIVE
) )
primary_ip4 = models.OneToOneField( primary_ip4 = models.OneToOneField(
to='ipam.IPAddress', to='ipam.IPAddress',
@ -1597,6 +1618,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', 'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
] ]
STATUS_CLASS_MAP = {
DeviceStatusChoices.STATUS_OFFLINE: 'warning',
DeviceStatusChoices.STATUS_ACTIVE: 'success',
DeviceStatusChoices.STATUS_PLANNED: 'info',
DeviceStatusChoices.STATUS_STAGED: 'primary',
DeviceStatusChoices.STATUS_FAILED: 'danger',
DeviceStatusChoices.STATUS_INVENTORY: 'default',
DeviceStatusChoices.STATUS_DECOMMISSIONING: 'warning',
}
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [ unique_together = [
@ -1625,7 +1656,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
}) })
if self.rack is None: if self.rack is None:
if self.face is not None: if self.face:
raise ValidationError({ raise ValidationError({
'face': "Cannot select a rack face without assigning a rack.", 'face': "Cannot select a rack face without assigning a rack.",
}) })
@ -1635,7 +1666,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
}) })
# Validate position/face combination # Validate position/face combination
if self.position and self.face is None: if self.position and not self.face:
raise ValidationError({ raise ValidationError({
'face': "Must specify rack face when defining rack position.", 'face': "Must specify rack face when defining rack position.",
}) })
@ -1850,7 +1881,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, 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 STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
# #
@ -1871,7 +1902,7 @@ class ConsolePort(CableTermination, ComponentModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
blank=True blank=True
) )
connected_endpoint = models.OneToOneField( connected_endpoint = models.OneToOneField(
@ -1928,7 +1959,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypes.CHOICES, choices=ConsolePortTypeChoices,
blank=True blank=True
) )
connection_status = models.NullBooleanField( connection_status = models.NullBooleanField(
@ -1977,7 +2008,7 @@ class PowerPort(CableTermination, ComponentModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerPortTypes.CHOICES, choices=PowerPortTypeChoices,
blank=True blank=True
) )
maximum_draw = models.PositiveSmallIntegerField( maximum_draw = models.PositiveSmallIntegerField(
@ -2077,8 +2108,8 @@ class PowerPort(CableTermination, ComponentModel):
} }
# Calculate per-leg aggregates for three-phase feeds # Calculate per-leg aggregates for three-phase feeds
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE: if self._connected_powerfeed and self._connected_powerfeed.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
for leg, leg_name in POWERFEED_LEG_CHOICES: for leg, leg_name in PowerOutletFeedLegChoices:
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True) outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate( utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
maximum_draw_total=Sum('maximum_draw'), maximum_draw_total=Sum('maximum_draw'),
@ -2120,7 +2151,7 @@ class PowerOutlet(CableTermination, ComponentModel):
) )
type = models.CharField( type = models.CharField(
max_length=50, max_length=50,
choices=PowerOutletTypes.CHOICES, choices=PowerOutletTypeChoices,
blank=True blank=True
) )
power_port = models.ForeignKey( power_port = models.ForeignKey(
@ -2130,10 +2161,10 @@ class PowerOutlet(CableTermination, ComponentModel):
null=True, null=True,
related_name='poweroutlets' related_name='poweroutlets'
) )
feed_leg = models.PositiveSmallIntegerField( feed_leg = models.CharField(
choices=POWERFEED_LEG_CHOICES, max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True, blank=True,
null=True,
help_text="Phase (for three-phase feeds)" help_text="Phase (for three-phase feeds)"
) )
connection_status = models.NullBooleanField( connection_status = models.NullBooleanField(
@ -2226,9 +2257,9 @@ class Interface(CableTermination, ComponentModel):
blank=True, blank=True,
verbose_name='Parent LAG' verbose_name='Parent LAG'
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=IFACE_TYPE_CHOICES, max_length=50,
default=IFACE_TYPE_10GE_SFP_PLUS choices=InterfaceTypeChoices
) )
enabled = models.BooleanField( enabled = models.BooleanField(
default=True default=True
@ -2249,10 +2280,10 @@ class Interface(CableTermination, ComponentModel):
verbose_name='OOB Management', verbose_name='OOB Management',
help_text='This interface is used only for out-of-band management' help_text='This interface is used only for out-of-band management'
) )
mode = models.PositiveSmallIntegerField( mode = models.CharField(
choices=IFACE_MODE_CHOICES, max_length=50,
choices=InterfaceModeChoices,
blank=True, blank=True,
null=True
) )
untagged_vlan = models.ForeignKey( untagged_vlan = models.ForeignKey(
to='ipam.VLAN', to='ipam.VLAN',
@ -2311,7 +2342,7 @@ class Interface(CableTermination, ComponentModel):
raise ValidationError("An interface must belong to either a device or a virtual machine.") raise ValidationError("An interface must belong to either a device or a virtual machine.")
# VM interfaces must be virtual # VM interfaces must be virtual
if self.virtual_machine and self.type is not IFACE_TYPE_VIRTUAL: if self.virtual_machine and self.type is not InterfaceTypeChoices.TYPE_VIRTUAL:
raise ValidationError({ raise ValidationError({
'type': "Virtual machines can only have virtual interfaces." 'type': "Virtual machines can only have virtual interfaces."
}) })
@ -2340,7 +2371,7 @@ class Interface(CableTermination, ComponentModel):
}) })
# Only a LAG can have LAG members # Only a LAG can have LAG members
if self.type != IFACE_TYPE_LAG and self.member_interfaces.exists(): if self.type != InterfaceTypeChoices.TYPE_LAG and self.member_interfaces.exists():
raise ValidationError({ raise ValidationError({
'type': "Cannot change interface type; it has LAG members ({}).".format( 'type': "Cannot change interface type; it has LAG members ({}).".format(
", ".join([iface.name for iface in self.member_interfaces.all()]) ", ".join([iface.name for iface in self.member_interfaces.all()])
@ -2361,7 +2392,7 @@ class Interface(CableTermination, ComponentModel):
self.untagged_vlan = None self.untagged_vlan = None
# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.) # Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
if self.pk and self.mode is not IFACE_MODE_TAGGED: if self.pk and self.mode is not InterfaceModeChoices.MODE_TAGGED:
self.tagged_vlans.clear() self.tagged_vlans.clear()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@ -2423,7 +2454,7 @@ class Interface(CableTermination, ComponentModel):
@property @property
def is_lag(self): def is_lag(self):
return self.type == IFACE_TYPE_LAG return self.type == InterfaceTypeChoices.TYPE_LAG
@property @property
def count_ipaddresses(self): def count_ipaddresses(self):
@ -2446,8 +2477,9 @@ class FrontPort(CableTermination, ComponentModel):
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=PORT_TYPE_CHOICES max_length=50,
choices=PortTypeChoices
) )
rear_port = models.ForeignKey( rear_port = models.ForeignKey(
to='dcim.RearPort', to='dcim.RearPort',
@ -2513,8 +2545,9 @@ class RearPort(CableTermination, ComponentModel):
name = models.CharField( name = models.CharField(
max_length=64 max_length=64
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=PORT_TYPE_CHOICES max_length=50,
choices=PortTypeChoices
) )
positions = models.PositiveSmallIntegerField( positions = models.PositiveSmallIntegerField(
default=1, default=1,
@ -2776,10 +2809,10 @@ class Cable(ChangeLoggedModel):
ct_field='termination_b_type', ct_field='termination_b_type',
fk_field='termination_b_id' fk_field='termination_b_id'
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=CABLE_TYPE_CHOICES, max_length=50,
blank=True, choices=CableTypeChoices,
null=True blank=True
) )
status = models.BooleanField( status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES, choices=CONNECTION_STATUS_CHOICES,
@ -2796,10 +2829,10 @@ class Cable(ChangeLoggedModel):
blank=True, blank=True,
null=True null=True
) )
length_unit = models.PositiveSmallIntegerField( length_unit = models.CharField(
choices=CABLE_LENGTH_UNIT_CHOICES, max_length=50,
choices=CableLengthUnitChoices,
blank=True, blank=True,
null=True
) )
# Stores the normalized length (in meters) for database ordering # Stores the normalized length (in meters) for database ordering
_abs_length = models.DecimalField( _abs_length = models.DecimalField(
@ -2927,10 +2960,10 @@ class Cable(ChangeLoggedModel):
)) ))
# Validate length and length_unit # Validate length and length_unit
if self.length is not None and self.length_unit is None: if self.length is not None and not self.length_unit:
raise ValidationError("Must specify a unit when setting a cable length") raise ValidationError("Must specify a unit when setting a cable length")
elif self.length is None: elif self.length is None:
self.length_unit = None self.length_unit = ''
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -3074,21 +3107,25 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
name = models.CharField( name = models.CharField(
max_length=50 max_length=50
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=POWERFEED_STATUS_CHOICES, max_length=50,
default=POWERFEED_STATUS_ACTIVE choices=PowerFeedStatusChoices,
default=PowerFeedStatusChoices.STATUS_ACTIVE
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=POWERFEED_TYPE_CHOICES, max_length=50,
default=POWERFEED_TYPE_PRIMARY choices=PowerFeedTypeChoices,
default=PowerFeedTypeChoices.TYPE_PRIMARY
) )
supply = models.PositiveSmallIntegerField( supply = models.CharField(
choices=POWERFEED_SUPPLY_CHOICES, max_length=50,
default=POWERFEED_SUPPLY_AC choices=PowerFeedSupplyChoices,
default=PowerFeedSupplyChoices.SUPPLY_AC
) )
phase = models.PositiveSmallIntegerField( phase = models.CharField(
choices=POWERFEED_PHASE_CHOICES, max_length=50,
default=POWERFEED_PHASE_SINGLE choices=PowerFeedPhaseChoices,
default=PowerFeedPhaseChoices.PHASE_SINGLE
) )
voltage = models.PositiveSmallIntegerField( voltage = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)], validators=[MinValueValidator(1)],
@ -3123,6 +3160,18 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
'amperage', 'max_utilization', 'comments', 'amperage', 'max_utilization', 'comments',
] ]
STATUS_CLASS_MAP = {
PowerFeedStatusChoices.STATUS_OFFLINE: 'warning',
PowerFeedStatusChoices.STATUS_ACTIVE: 'success',
PowerFeedStatusChoices.STATUS_PLANNED: 'info',
PowerFeedStatusChoices.STATUS_FAILED: 'danger',
}
TYPE_CLASS_MAP = {
PowerFeedTypeChoices.TYPE_PRIMARY: 'success',
PowerFeedTypeChoices.TYPE_REDUNDANT: 'info',
}
class Meta: class Meta:
ordering = ['power_panel', 'name'] ordering = ['power_panel', 'name']
unique_together = ['power_panel', 'name'] unique_together = ['power_panel', 'name']
@ -3162,7 +3211,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
# Cache the available_power property on the instance # Cache the available_power property on the instance
kva = self.voltage * self.amperage * (self.max_utilization / 100) kva = self.voltage * self.amperage * (self.max_utilization / 100)
if self.phase == POWERFEED_PHASE_3PHASE: if self.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
self.available_power = round(kva * 1.732) self.available_power = round(kva * 1.732)
else: else:
self.available_power = round(kva) self.available_power = round(kva)
@ -3170,7 +3219,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_type_class(self): def get_type_class(self):
return STATUS_CLASSES[self.type] return self.TYPE_CLASS_MAP.get(self.type)
def get_status_class(self): def get_status_class(self):
return STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)

View File

@ -156,10 +156,6 @@ DEVICE_PRIMARY_IP = """
{{ record.primary_ip4.address.ip|default:"" }} {{ record.primary_ip4.address.ip|default:"" }}
""" """
SUBDEVICE_ROLE_TEMPLATE = """
{% if record.subdevice_role == True %}Parent{% elif record.subdevice_role == False %}Child{% else %}—{% endif %}
"""
DEVICETYPE_INSTANCES_TEMPLATE = """ DEVICETYPE_INSTANCES_TEMPLATE = """
<a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a> <a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a>
""" """
@ -391,10 +387,6 @@ class DeviceTypeTable(BaseTable):
verbose_name='Device Type' verbose_name='Device Type'
) )
is_full_depth = BooleanColumn(verbose_name='Full Depth') is_full_depth = BooleanColumn(verbose_name='Full Depth')
subdevice_role = tables.TemplateColumn(
template_code=SUBDEVICE_ROLE_TEMPLATE,
verbose_name='Subdevice Role'
)
instance_count = tables.TemplateColumn( instance_count = tables.TemplateColumn(
template_code=DEVICETYPE_INSTANCES_TEMPLATE, template_code=DEVICETYPE_INSTANCES_TEMPLATE,
verbose_name='Instances' verbose_name='Instances'

View File

@ -3,6 +3,7 @@ from netaddr import IPNetwork
from rest_framework import status from rest_framework import status
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.models import ( from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@ -180,7 +181,7 @@ class SiteTest(APITestCase):
'name': 'Test Site 4', 'name': 'Test Site 4',
'slug': 'test-site-4', 'slug': 'test-site-4',
'region': self.region1.pk, 'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
} }
url = reverse('dcim-api:site-list') url = reverse('dcim-api:site-list')
@ -200,19 +201,19 @@ class SiteTest(APITestCase):
'name': 'Test Site 4', 'name': 'Test Site 4',
'slug': 'test-site-4', 'slug': 'test-site-4',
'region': self.region1.pk, 'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
{ {
'name': 'Test Site 5', 'name': 'Test Site 5',
'slug': 'test-site-5', 'slug': 'test-site-5',
'region': self.region1.pk, 'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
{ {
'name': 'Test Site 6', 'name': 'Test Site 6',
'slug': 'test-site-6', 'slug': 'test-site-6',
'region': self.region1.pk, 'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE, 'status': SiteStatusChoices.STATUS_ACTIVE,
}, },
] ]
@ -2473,7 +2474,7 @@ class InterfaceTest(APITestCase):
data = { data = {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan3.id, 'untagged_vlan': self.vlan3.id,
'tagged_vlans': [self.vlan1.id, self.vlan2.id], 'tagged_vlans': [self.vlan1.id, self.vlan2.id],
} }
@ -2520,21 +2521,21 @@ class InterfaceTest(APITestCase):
{ {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },
{ {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 5', 'name': 'Test Interface 5',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },
{ {
'device': self.device.pk, 'device': self.device.pk,
'name': 'Test Interface 6', 'name': 'Test Interface 6',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },
@ -2553,7 +2554,7 @@ class InterfaceTest(APITestCase):
def test_update_interface(self): def test_update_interface(self):
lag_interface = Interface.objects.create( lag_interface = Interface.objects.create(
device=self.device, name='Test LAG Interface', type=IFACE_TYPE_LAG device=self.device, name='Test LAG Interface', type=InterfaceTypeChoices.TYPE_LAG
) )
data = { data = {
@ -2590,11 +2591,11 @@ class DeviceBayTest(APITestCase):
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
self.devicetype1 = DeviceType.objects.create( self.devicetype1 = DeviceType.objects.create(
manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type', manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type',
subdevice_role=SUBDEVICE_ROLE_PARENT subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
) )
self.devicetype2 = DeviceType.objects.create( self.devicetype2 = DeviceType.objects.create(
manufacturer=manufacturer, model='Child Device Type', slug='child-device-type', manufacturer=manufacturer, model='Child Device Type', slug='child-device-type',
subdevice_role=SUBDEVICE_ROLE_CHILD subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
) )
devicerole = DeviceRole.objects.create( devicerole = DeviceRole.objects.create(
name='Test Device Role 1', slug='test-device-role-1', color='ff0000' name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
@ -2841,7 +2842,7 @@ class CableTest(APITestCase):
) )
for device in [self.device1, self.device2]: for device in [self.device1, self.device2]:
for i in range(0, 10): for i in range(0, 10):
Interface(device=device, type=IFACE_TYPE_1GE_FIXED, name='eth{}'.format(i)).save() Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth{}'.format(i)).save()
self.cable1 = Cable( self.cable1 = Cable(
termination_a=self.device1.interfaces.get(name='eth0'), termination_a=self.device1.interfaces.get(name='eth0'),
@ -3033,16 +3034,16 @@ class ConnectionTest(APITestCase):
device=self.device2, name='Test Console Server Port 1' device=self.device2, name='Test Console Server Port 1'
) )
rearport1 = RearPort.objects.create( rearport1 = RearPort.objects.create(
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
) )
frontport1 = FrontPort.objects.create( frontport1 = FrontPort.objects.create(
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1 device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
) )
rearport2 = RearPort.objects.create( rearport2 = RearPort.objects.create(
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
) )
frontport2 = FrontPort.objects.create( frontport2 = FrontPort.objects.create(
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2 device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
) )
url = reverse('dcim-api:cable-list') url = reverse('dcim-api:cable-list')
@ -3161,16 +3162,16 @@ class ConnectionTest(APITestCase):
device=self.device2, name='Test Interface 2' device=self.device2, name='Test Interface 2'
) )
rearport1 = RearPort.objects.create( rearport1 = RearPort.objects.create(
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
) )
frontport1 = FrontPort.objects.create( frontport1 = FrontPort.objects.create(
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1 device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
) )
rearport2 = RearPort.objects.create( rearport2 = RearPort.objects.create(
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
) )
frontport2 = FrontPort.objects.create( frontport2 = FrontPort.objects.create(
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2 device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
) )
url = reverse('dcim-api:cable-list') url = reverse('dcim-api:cable-list')
@ -3272,16 +3273,16 @@ class ConnectionTest(APITestCase):
circuit=circuit, term_side='A', site=self.site, port_speed=10000 circuit=circuit, term_side='A', site=self.site, port_speed=10000
) )
rearport1 = RearPort.objects.create( rearport1 = RearPort.objects.create(
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
) )
frontport1 = FrontPort.objects.create( frontport1 = FrontPort.objects.create(
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1 device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
) )
rearport2 = RearPort.objects.create( rearport2 = RearPort.objects.create(
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
) )
frontport2 = FrontPort.objects.create( frontport2 = FrontPort.objects.create(
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2 device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
) )
url = reverse('dcim-api:cable-list') url = reverse('dcim-api:cable-list')
@ -3410,23 +3411,23 @@ class VirtualChassisTest(APITestCase):
device_type=device_type, device_role=device_role, name='StackSwitch9', site=site device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
) )
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device1, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device1, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device2, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device2, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device3, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device3, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device4, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device4, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device5, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device5, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device6, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device6, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device7, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device7, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device8, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device8, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
for i in range(0, 13): for i in range(0, 13):
Interface.objects.create(device=self.device9, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED) Interface.objects.create(device=self.device9, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
# Create two VirtualChassis with three members each # Create two VirtualChassis with three members each
self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1') self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1')
@ -3678,22 +3679,22 @@ class PowerFeedTest(APITestCase):
site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 2' site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 2'
) )
self.powerfeed1 = PowerFeed.objects.create( self.powerfeed1 = PowerFeed.objects.create(
power_panel=self.powerpanel1, rack=self.rack1, name='Test Power Feed 1A', type=POWERFEED_TYPE_PRIMARY power_panel=self.powerpanel1, rack=self.rack1, name='Test Power Feed 1A', type=PowerFeedTypeChoices.TYPE_PRIMARY
) )
self.powerfeed2 = PowerFeed.objects.create( self.powerfeed2 = PowerFeed.objects.create(
power_panel=self.powerpanel2, rack=self.rack1, name='Test Power Feed 1B', type=POWERFEED_TYPE_REDUNDANT power_panel=self.powerpanel2, rack=self.rack1, name='Test Power Feed 1B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
) )
self.powerfeed3 = PowerFeed.objects.create( self.powerfeed3 = PowerFeed.objects.create(
power_panel=self.powerpanel1, rack=self.rack2, name='Test Power Feed 2A', type=POWERFEED_TYPE_PRIMARY power_panel=self.powerpanel1, rack=self.rack2, name='Test Power Feed 2A', type=PowerFeedTypeChoices.TYPE_PRIMARY
) )
self.powerfeed4 = PowerFeed.objects.create( self.powerfeed4 = PowerFeed.objects.create(
power_panel=self.powerpanel2, rack=self.rack2, name='Test Power Feed 2B', type=POWERFEED_TYPE_REDUNDANT power_panel=self.powerpanel2, rack=self.rack2, name='Test Power Feed 2B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
) )
self.powerfeed5 = PowerFeed.objects.create( self.powerfeed5 = PowerFeed.objects.create(
power_panel=self.powerpanel1, rack=self.rack3, name='Test Power Feed 3A', type=POWERFEED_TYPE_PRIMARY power_panel=self.powerpanel1, rack=self.rack3, name='Test Power Feed 3A', type=PowerFeedTypeChoices.TYPE_PRIMARY
) )
self.powerfeed6 = PowerFeed.objects.create( self.powerfeed6 = PowerFeed.objects.create(
power_panel=self.powerpanel2, rack=self.rack3, name='Test Power Feed 3B', type=POWERFEED_TYPE_REDUNDANT power_panel=self.powerpanel2, rack=self.rack3, name='Test Power Feed 3B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
) )
def test_get_powerfeed(self): def test_get_powerfeed(self):
@ -3726,7 +3727,7 @@ class PowerFeedTest(APITestCase):
'name': 'Test Power Feed 4A', 'name': 'Test Power Feed 4A',
'power_panel': self.powerpanel1.pk, 'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk, 'rack': self.rack4.pk,
'type': POWERFEED_TYPE_PRIMARY, 'type': PowerFeedTypeChoices.TYPE_PRIMARY,
} }
url = reverse('dcim-api:powerfeed-list') url = reverse('dcim-api:powerfeed-list')
@ -3746,13 +3747,13 @@ class PowerFeedTest(APITestCase):
'name': 'Test Power Feed 4A', 'name': 'Test Power Feed 4A',
'power_panel': self.powerpanel1.pk, 'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk, 'rack': self.rack4.pk,
'type': POWERFEED_TYPE_PRIMARY, 'type': PowerFeedTypeChoices.TYPE_PRIMARY,
}, },
{ {
'name': 'Test Power Feed 4B', 'name': 'Test Power Feed 4B',
'power_panel': self.powerpanel1.pk, 'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk, 'rack': self.rack4.pk,
'type': POWERFEED_TYPE_REDUNDANT, 'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
}, },
] ]
@ -3769,7 +3770,7 @@ class PowerFeedTest(APITestCase):
data = { data = {
'name': 'Test Power Feed X', 'name': 'Test Power Feed X',
'rack': self.rack4.pk, 'rack': self.rack4.pk,
'type': POWERFEED_TYPE_REDUNDANT, 'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
} }
url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk}) url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})

View File

@ -21,10 +21,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'qfx5100-48s'), 'device_type': get_id(DeviceType, 'qfx5100-48s'),
'site': get_id(Site, 'test1'), 'site': get_id(Site, 'test1'),
'rack': '1', 'rack': '1',
'face': RACK_FACE_FRONT, 'face': DeviceFaceChoices.FACE_FRONT,
'position': 41, 'position': 41,
'platform': get_id(Platform, 'juniper-junos'), 'platform': get_id(Platform, 'juniper-junos'),
'status': DEVICE_STATUS_ACTIVE, 'status': DeviceStatusChoices.STATUS_ACTIVE,
}) })
self.assertTrue(test.is_valid(), test.fields['position'].choices) self.assertTrue(test.is_valid(), test.fields['position'].choices)
self.assertTrue(test.save()) self.assertTrue(test.save())
@ -38,10 +38,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'qfx5100-48s'), 'device_type': get_id(DeviceType, 'qfx5100-48s'),
'site': get_id(Site, 'test1'), 'site': get_id(Site, 'test1'),
'rack': '1', 'rack': '1',
'face': RACK_FACE_FRONT, 'face': DeviceFaceChoices.FACE_FRONT,
'position': 1, 'position': 1,
'platform': get_id(Platform, 'juniper-junos'), 'platform': get_id(Platform, 'juniper-junos'),
'status': DEVICE_STATUS_ACTIVE, 'status': DeviceStatusChoices.STATUS_ACTIVE,
}) })
self.assertFalse(test.is_valid()) self.assertFalse(test.is_valid())
@ -54,10 +54,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'cwg-24vym415c9'), 'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
'site': get_id(Site, 'test1'), 'site': get_id(Site, 'test1'),
'rack': '1', 'rack': '1',
'face': None, 'face': '',
'position': None, 'position': None,
'platform': None, 'platform': None,
'status': DEVICE_STATUS_ACTIVE, 'status': DeviceStatusChoices.STATUS_ACTIVE,
}) })
self.assertTrue(test.is_valid()) self.assertTrue(test.is_valid())
self.assertTrue(test.save()) self.assertTrue(test.save())
@ -71,10 +71,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'cwg-24vym415c9'), 'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
'site': get_id(Site, 'test1'), 'site': get_id(Site, 'test1'),
'rack': '1', 'rack': '1',
'face': RACK_FACE_REAR, 'face': DeviceFaceChoices.FACE_REAR,
'position': None, 'position': None,
'platform': None, 'platform': None,
'status': DEVICE_STATUS_ACTIVE, 'status': DeviceStatusChoices.STATUS_ACTIVE,
}) })
self.assertTrue(test.is_valid()) self.assertTrue(test.is_valid())
self.assertTrue(test.save()) self.assertTrue(test.save())

View File

@ -87,7 +87,7 @@ class RackTestCase(TestCase):
site=self.site1, site=self.site1,
rack=rack1, rack=rack1,
position=43, position=43,
face=RACK_FACE_FRONT, face=DeviceFaceChoices.FACE_FRONT,
) )
device1.save() device1.save()
@ -117,7 +117,7 @@ class RackTestCase(TestCase):
site=self.site1, site=self.site1,
rack=self.rack, rack=self.rack,
position=10, position=10,
face=RACK_FACE_REAR, face=DeviceFaceChoices.FACE_REAR,
) )
device1.save() device1.save()
@ -146,7 +146,7 @@ class RackTestCase(TestCase):
site=self.site1, site=self.site1,
rack=self.rack, rack=self.rack,
position=None, position=None,
face=None, face='',
) )
self.assertTrue(pdu) self.assertTrue(pdu)
@ -187,20 +187,20 @@ class DeviceTestCase(TestCase):
device_type=self.device_type, device_type=self.device_type,
name='Power Outlet 1', name='Power Outlet 1',
power_port=ppt, power_port=ppt,
feed_leg=POWERFEED_LEG_A feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
).save() ).save()
InterfaceTemplate( InterfaceTemplate(
device_type=self.device_type, device_type=self.device_type,
name='Interface 1', name='Interface 1',
type=IFACE_TYPE_1GE_FIXED, type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mgmt_only=True mgmt_only=True
).save() ).save()
rpt = RearPortTemplate( rpt = RearPortTemplate(
device_type=self.device_type, device_type=self.device_type,
name='Rear Port 1', name='Rear Port 1',
type=PORT_TYPE_8P8C, type=PortTypeChoices.TYPE_8P8C,
positions=8 positions=8
) )
rpt.save() rpt.save()
@ -208,7 +208,7 @@ class DeviceTestCase(TestCase):
FrontPortTemplate( FrontPortTemplate(
device_type=self.device_type, device_type=self.device_type,
name='Front Port 1', name='Front Port 1',
type=PORT_TYPE_8P8C, type=PortTypeChoices.TYPE_8P8C,
rear_port=rpt, rear_port=rpt,
rear_port_position=2 rear_port_position=2
).save() ).save()
@ -251,27 +251,27 @@ class DeviceTestCase(TestCase):
device=d, device=d,
name='Power Outlet 1', name='Power Outlet 1',
power_port=pp, power_port=pp,
feed_leg=POWERFEED_LEG_A feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
) )
Interface.objects.get( Interface.objects.get(
device=d, device=d,
name='Interface 1', name='Interface 1',
type=IFACE_TYPE_1GE_FIXED, type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mgmt_only=True mgmt_only=True
) )
rp = RearPort.objects.get( rp = RearPort.objects.get(
device=d, device=d,
name='Rear Port 1', name='Rear Port 1',
type=PORT_TYPE_8P8C, type=PortTypeChoices.TYPE_8P8C,
positions=8 positions=8
) )
FrontPort.objects.get( FrontPort.objects.get(
device=d, device=d,
name='Front Port 1', name='Front Port 1',
type=PORT_TYPE_8P8C, type=PortTypeChoices.TYPE_8P8C,
rear_port=rp, rear_port=rp,
rear_port_position=2 rear_port_position=2
) )
@ -379,7 +379,7 @@ class CableTestCase(TestCase):
""" """
A cable cannot terminate to a virtual interface A cable cannot terminate to a virtual interface
""" """
virtual_interface = Interface(device=self.device1, name="V1", type=IFACE_TYPE_VIRTUAL) virtual_interface = Interface(device=self.device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
cable = Cable(termination_a=self.interface2, termination_b=virtual_interface) cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
cable.clean() cable.clean()
@ -388,7 +388,7 @@ class CableTestCase(TestCase):
""" """
A cable cannot terminate to a wireless interface A cable cannot terminate to a wireless interface
""" """
wireless_interface = Interface(device=self.device1, name="W1", type=IFACE_TYPE_80211A) wireless_interface = Interface(device=self.device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
cable = Cable(termination_a=self.interface2, termination_b=wireless_interface) cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
cable.clean() cable.clean()
@ -421,16 +421,16 @@ class CablePathTestCase(TestCase):
device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site
) )
self.rear_port1 = RearPort.objects.create( self.rear_port1 = RearPort.objects.create(
device=self.panel1, name='Rear Port 1', type=PORT_TYPE_8P8C device=self.panel1, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C
) )
self.front_port1 = FrontPort.objects.create( self.front_port1 = FrontPort.objects.create(
device=self.panel1, name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=self.rear_port1 device=self.panel1, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port1
) )
self.rear_port2 = RearPort.objects.create( self.rear_port2 = RearPort.objects.create(
device=self.panel2, name='Rear Port 2', type=PORT_TYPE_8P8C device=self.panel2, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C
) )
self.front_port2 = FrontPort.objects.create( self.front_port2 = FrontPort.objects.create(
device=self.panel2, name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=self.rear_port2 device=self.panel2, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port2
) )
def test_path_completion(self): def test_path_completion(self):

View File

@ -255,15 +255,15 @@ power-outlets:
- name: Power Outlet 1 - name: Power Outlet 1
type: iec-60320-c13 type: iec-60320-c13
power_port: Power Port 1 power_port: Power Port 1
feed_leg: 1 feed_leg: A
- name: Power Outlet 2 - name: Power Outlet 2
type: iec-60320-c13 type: iec-60320-c13
power_port: Power Port 1 power_port: Power Port 1
feed_leg: 1 feed_leg: A
- name: Power Outlet 3 - name: Power Outlet 3
type: iec-60320-c13 type: iec-60320-c13
power_port: Power Port 1 power_port: Power Port 1
feed_leg: 1 feed_leg: A
interfaces: interfaces:
- name: Interface 1 - name: Interface 1
type: 1000base-t type: 1000base-t
@ -326,29 +326,29 @@ device-bays:
self.assertEqual(dt.consoleport_templates.count(), 3) self.assertEqual(dt.consoleport_templates.count(), 3)
cp1 = ConsolePortTemplate.objects.first() cp1 = ConsolePortTemplate.objects.first()
self.assertEqual(cp1.name, 'Console Port 1') self.assertEqual(cp1.name, 'Console Port 1')
self.assertEqual(cp1.type, ConsolePortTypes.TYPE_DE9) self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
self.assertEqual(dt.consoleserverport_templates.count(), 3) self.assertEqual(dt.consoleserverport_templates.count(), 3)
csp1 = ConsoleServerPortTemplate.objects.first() csp1 = ConsoleServerPortTemplate.objects.first()
self.assertEqual(csp1.name, 'Console Server Port 1') self.assertEqual(csp1.name, 'Console Server Port 1')
self.assertEqual(csp1.type, ConsolePortTypes.TYPE_RJ45) self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
self.assertEqual(dt.powerport_templates.count(), 3) self.assertEqual(dt.powerport_templates.count(), 3)
pp1 = PowerPortTemplate.objects.first() pp1 = PowerPortTemplate.objects.first()
self.assertEqual(pp1.name, 'Power Port 1') self.assertEqual(pp1.name, 'Power Port 1')
self.assertEqual(pp1.type, PowerPortTypes.TYPE_IEC_C14) self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
self.assertEqual(dt.poweroutlet_templates.count(), 3) self.assertEqual(dt.poweroutlet_templates.count(), 3)
po1 = PowerOutletTemplate.objects.first() po1 = PowerOutletTemplate.objects.first()
self.assertEqual(po1.name, 'Power Outlet 1') self.assertEqual(po1.name, 'Power Outlet 1')
self.assertEqual(po1.type, PowerOutletTypes.TYPE_IEC_C13) self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
self.assertEqual(po1.power_port, pp1) self.assertEqual(po1.power_port, pp1)
self.assertEqual(po1.feed_leg, POWERFEED_LEG_A) self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
self.assertEqual(dt.interface_templates.count(), 3) self.assertEqual(dt.interface_templates.count(), 3)
iface1 = InterfaceTemplate.objects.first() iface1 = InterfaceTemplate.objects.first()
self.assertEqual(iface1.name, 'Interface 1') self.assertEqual(iface1.name, 'Interface 1')
self.assertEqual(iface1.type, IFACE_TYPE_1GE_FIXED) self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
self.assertTrue(iface1.mgmt_only) self.assertTrue(iface1.mgmt_only)
self.assertEqual(dt.rearport_templates.count(), 3) self.assertEqual(dt.rearport_templates.count(), 3)
@ -514,28 +514,28 @@ class CableTestCase(TestCase):
device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole) device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole)
device2.save() device2.save()
iface1 = Interface(device=device1, name='Interface 1', type=IFACE_TYPE_1GE_FIXED) iface1 = Interface(device=device1, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface1.save() iface1.save()
iface2 = Interface(device=device1, name='Interface 2', type=IFACE_TYPE_1GE_FIXED) iface2 = Interface(device=device1, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface2.save() iface2.save()
iface3 = Interface(device=device1, name='Interface 3', type=IFACE_TYPE_1GE_FIXED) iface3 = Interface(device=device1, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface3.save() iface3.save()
iface4 = Interface(device=device2, name='Interface 1', type=IFACE_TYPE_1GE_FIXED) iface4 = Interface(device=device2, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface4.save() iface4.save()
iface5 = Interface(device=device2, name='Interface 2', type=IFACE_TYPE_1GE_FIXED) iface5 = Interface(device=device2, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface5.save() iface5.save()
iface6 = Interface(device=device2, name='Interface 3', type=IFACE_TYPE_1GE_FIXED) iface6 = Interface(device=device2, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
iface6.save() iface6.save()
Cable(termination_a=iface1, termination_b=iface4, type=CABLE_TYPE_CAT6).save() Cable(termination_a=iface1, termination_b=iface4, type=CableTypeChoices.TYPE_CAT6).save()
Cable(termination_a=iface2, termination_b=iface5, type=CABLE_TYPE_CAT6).save() Cable(termination_a=iface2, termination_b=iface5, type=CableTypeChoices.TYPE_CAT6).save()
Cable(termination_a=iface3, termination_b=iface6, type=CABLE_TYPE_CAT6).save() Cable(termination_a=iface3, termination_b=iface6, type=CableTypeChoices.TYPE_CAT6).save()
def test_cable_list(self): def test_cable_list(self):
url = reverse('dcim:cable_list') url = reverse('dcim:cable_list')
params = { params = {
"type": CABLE_TYPE_CAT6, "type": CableTypeChoices.TYPE_CAT6,
} }
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))

View File

@ -5,7 +5,7 @@ from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from extras.constants import * from extras.choices import *
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
from utilities.api import ValidatedModelSerializer from utilities.api import ValidatedModelSerializer
@ -37,7 +37,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
if value not in [None, '']: if value not in [None, '']:
# Validate integer # Validate integer
if cf.type == CF_TYPE_INTEGER: if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
try: try:
int(value) int(value)
except ValueError: except ValueError:
@ -46,13 +46,13 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
) )
# Validate boolean # Validate boolean
if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]: if cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value not in [True, False, 1, 0]:
raise ValidationError( raise ValidationError(
"Invalid value for boolean field {}: {}".format(field_name, value) "Invalid value for boolean field {}: {}".format(field_name, value)
) )
# Validate date # Validate date
if cf.type == CF_TYPE_DATE: if cf.type == CustomFieldTypeChoices.TYPE_DATE:
try: try:
datetime.strptime(value, '%Y-%m-%d') datetime.strptime(value, '%Y-%m-%d')
except ValueError: except ValueError:
@ -61,7 +61,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
) )
# Validate selected choice # Validate selected choice
if cf.type == CF_TYPE_SELECT: if cf.type == CustomFieldTypeChoices.TYPE_SELECT:
try: try:
value = int(value) value = int(value)
except ValueError: except ValueError:
@ -100,7 +100,7 @@ class CustomFieldModelSerializer(ValidatedModelSerializer):
instance.custom_fields = {} instance.custom_fields = {}
for field in fields: for field in fields:
value = instance.cf.get(field.name) value = instance.cf.get(field.name)
if field.type == CF_TYPE_SELECT and value is not None: if field.type == CustomFieldTypeChoices.TYPE_SELECT and value is not None:
instance.custom_fields[field.name] = CustomFieldChoiceSerializer(value).data instance.custom_fields[field.name] = CustomFieldChoiceSerializer(value).data
else: else:
instance.custom_fields[field.name] = value instance.custom_fields[field.name] = value

View File

@ -8,6 +8,7 @@ from dcim.api.nested_serializers import (
NestedRegionSerializer, NestedSiteSerializer, NestedRegionSerializer, NestedSiteSerializer,
) )
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
from extras.choices import *
from extras.constants import * from extras.constants import *
from extras.models import ( from extras.models import (
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag, ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
@ -56,8 +57,8 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
class ExportTemplateSerializer(ValidatedModelSerializer): class ExportTemplateSerializer(ValidatedModelSerializer):
template_language = ChoiceField( template_language = ChoiceField(
choices=TEMPLATE_LANGUAGE_CHOICES, choices=ExportTemplateLanguageChoices,
default=TEMPLATE_LANGUAGE_JINJA2 default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
) )
class Meta: class Meta:
@ -255,7 +256,7 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
read_only=True read_only=True
) )
action = ChoiceField( action = ChoiceField(
choices=OBJECTCHANGE_ACTION_CHOICES, choices=ObjectChangeActionChoices,
read_only=True read_only=True
) )
changed_object_type = ContentTypeField( changed_object_type = ContentTypeField(

140
netbox/extras/choices.py Normal file
View File

@ -0,0 +1,140 @@
from utilities.choices import ChoiceSet
#
# CustomFields
#
class CustomFieldTypeChoices(ChoiceSet):
TYPE_TEXT = 'text'
TYPE_INTEGER = 'integer'
TYPE_BOOLEAN = 'boolean'
TYPE_DATE = 'date'
TYPE_URL = 'url'
TYPE_SELECT = 'select'
CHOICES = (
(TYPE_TEXT, 'Text'),
(TYPE_INTEGER, 'Integer'),
(TYPE_BOOLEAN, 'Boolean (true/false)'),
(TYPE_DATE, 'Date'),
(TYPE_URL, 'URL'),
(TYPE_SELECT, 'Selection'),
)
LEGACY_MAP = {
TYPE_TEXT: 100,
TYPE_INTEGER: 200,
TYPE_BOOLEAN: 300,
TYPE_DATE: 400,
TYPE_URL: 500,
TYPE_SELECT: 600,
}
class CustomFieldFilterLogicChoices(ChoiceSet):
FILTER_DISABLED = 'disabled'
FILTER_LOOSE = 'loose'
FILTER_EXACT = 'exact'
CHOICES = (
(FILTER_DISABLED, 'Disabled'),
(FILTER_LOOSE, 'Loose'),
(FILTER_EXACT, 'Exact'),
)
LEGACY_MAP = {
FILTER_DISABLED: 0,
FILTER_LOOSE: 1,
FILTER_EXACT: 2,
}
#
# CustomLinks
#
class CustomLinkButtonClassChoices(ChoiceSet):
CLASS_DEFAULT = 'default'
CLASS_PRIMARY = 'primary'
CLASS_SUCCESS = 'success'
CLASS_INFO = 'info'
CLASS_WARNING = 'warning'
CLASS_DANGER = 'danger'
CLASS_LINK = 'link'
CHOICES = (
(CLASS_DEFAULT, 'Default'),
(CLASS_PRIMARY, 'Primary (blue)'),
(CLASS_SUCCESS, 'Success (green)'),
(CLASS_INFO, 'Info (aqua)'),
(CLASS_WARNING, 'Warning (orange)'),
(CLASS_DANGER, 'Danger (red)'),
(CLASS_LINK, 'None (link)'),
)
#
# ObjectChanges
#
class ObjectChangeActionChoices(ChoiceSet):
ACTION_CREATE = 'create'
ACTION_UPDATE = 'update'
ACTION_DELETE = 'delete'
CHOICES = (
(ACTION_CREATE, 'Created'),
(ACTION_UPDATE, 'Updated'),
(ACTION_DELETE, 'Deleted'),
)
LEGACY_MAP = {
ACTION_CREATE: 1,
ACTION_UPDATE: 2,
ACTION_DELETE: 3,
}
#
# ExportTemplates
#
class ExportTemplateLanguageChoices(ChoiceSet):
LANGUAGE_DJANGO = 'django'
LANGUAGE_JINJA2 = 'jinja2'
CHOICES = (
(LANGUAGE_DJANGO, 'Django'),
(LANGUAGE_JINJA2, 'Jinja2'),
)
LEGACY_MAP = {
LANGUAGE_DJANGO: 10,
LANGUAGE_JINJA2: 20,
}
#
# Webhooks
#
class WebhookContentTypeChoices(ChoiceSet):
CONTENTTYPE_JSON = 'application/json'
CONTENTTYPE_FORMDATA = 'application/x-www-form-urlencoded'
CHOICES = (
(CONTENTTYPE_JSON, 'JSON'),
(CONTENTTYPE_FORMDATA, 'Form data'),
)
LEGACY_MAP = {
CONTENTTYPE_JSON: 1,
CONTENTTYPE_FORMDATA: 2,
}

View File

@ -19,32 +19,6 @@ CUSTOMFIELD_MODELS = [
'virtualization.virtualmachine', 'virtualization.virtualmachine',
] ]
# Custom field types
CF_TYPE_TEXT = 100
CF_TYPE_INTEGER = 200
CF_TYPE_BOOLEAN = 300
CF_TYPE_DATE = 400
CF_TYPE_URL = 500
CF_TYPE_SELECT = 600
CUSTOMFIELD_TYPE_CHOICES = (
(CF_TYPE_TEXT, 'Text'),
(CF_TYPE_INTEGER, 'Integer'),
(CF_TYPE_BOOLEAN, 'Boolean (true/false)'),
(CF_TYPE_DATE, 'Date'),
(CF_TYPE_URL, 'URL'),
(CF_TYPE_SELECT, 'Selection'),
)
# Custom field filter logic choices
CF_FILTER_DISABLED = 0
CF_FILTER_LOOSE = 1
CF_FILTER_EXACT = 2
CF_FILTER_CHOICES = (
(CF_FILTER_DISABLED, 'Disabled'),
(CF_FILTER_LOOSE, 'Loose'),
(CF_FILTER_EXACT, 'Exact'),
)
# Custom links # Custom links
CUSTOMLINK_MODELS = [ CUSTOMLINK_MODELS = [
'circuits.circuit', 'circuits.circuit',
@ -68,23 +42,6 @@ CUSTOMLINK_MODELS = [
'virtualization.virtualmachine', 'virtualization.virtualmachine',
] ]
BUTTON_CLASS_DEFAULT = 'default'
BUTTON_CLASS_PRIMARY = 'primary'
BUTTON_CLASS_SUCCESS = 'success'
BUTTON_CLASS_INFO = 'info'
BUTTON_CLASS_WARNING = 'warning'
BUTTON_CLASS_DANGER = 'danger'
BUTTON_CLASS_LINK = 'link'
BUTTON_CLASS_CHOICES = (
(BUTTON_CLASS_DEFAULT, 'Default'),
(BUTTON_CLASS_PRIMARY, 'Primary (blue)'),
(BUTTON_CLASS_SUCCESS, 'Success (green)'),
(BUTTON_CLASS_INFO, 'Info (aqua)'),
(BUTTON_CLASS_WARNING, 'Warning (orange)'),
(BUTTON_CLASS_DANGER, 'Danger (red)'),
(BUTTON_CLASS_LINK, 'None (link)'),
)
# Graph types # Graph types
GRAPH_TYPE_INTERFACE = 100 GRAPH_TYPE_INTERFACE = 100
GRAPH_TYPE_DEVICE = 150 GRAPH_TYPE_DEVICE = 150
@ -128,42 +85,6 @@ EXPORTTEMPLATE_MODELS = [
'virtualization.virtualmachine', 'virtualization.virtualmachine',
] ]
# ExportTemplate language choices
TEMPLATE_LANGUAGE_DJANGO = 10
TEMPLATE_LANGUAGE_JINJA2 = 20
TEMPLATE_LANGUAGE_CHOICES = (
(TEMPLATE_LANGUAGE_DJANGO, 'Django'),
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
)
# Change log actions
OBJECTCHANGE_ACTION_CREATE = 1
OBJECTCHANGE_ACTION_UPDATE = 2
OBJECTCHANGE_ACTION_DELETE = 3
OBJECTCHANGE_ACTION_CHOICES = (
(OBJECTCHANGE_ACTION_CREATE, 'Created'),
(OBJECTCHANGE_ACTION_UPDATE, 'Updated'),
(OBJECTCHANGE_ACTION_DELETE, 'Deleted'),
)
# User action types
ACTION_CREATE = 1
ACTION_IMPORT = 2
ACTION_EDIT = 3
ACTION_BULK_EDIT = 4
ACTION_DELETE = 5
ACTION_BULK_DELETE = 6
ACTION_BULK_CREATE = 7
ACTION_CHOICES = (
(ACTION_CREATE, 'created'),
(ACTION_BULK_CREATE, 'bulk created'),
(ACTION_IMPORT, 'imported'),
(ACTION_EDIT, 'modified'),
(ACTION_BULK_EDIT, 'bulk edited'),
(ACTION_DELETE, 'deleted'),
(ACTION_BULK_DELETE, 'bulk deleted'),
)
# Report logging levels # Report logging levels
LOG_DEFAULT = 0 LOG_DEFAULT = 0
LOG_SUCCESS = 10 LOG_SUCCESS = 10
@ -178,14 +99,6 @@ LOG_LEVEL_CODES = {
LOG_FAILURE: 'failure', LOG_FAILURE: 'failure',
} }
# webhook content types
WEBHOOK_CT_JSON = 1
WEBHOOK_CT_X_WWW_FORM_ENCODED = 2
WEBHOOK_CT_CHOICES = (
(WEBHOOK_CT_JSON, 'application/json'),
(WEBHOOK_CT_X_WWW_FORM_ENCODED, 'application/x-www-form-urlencoded'),
)
# Models which support registered webhooks # Models which support registered webhooks
WEBHOOK_MODELS = [ WEBHOOK_MODELS = [
'circuits.circuit', 'circuits.circuit',

View File

@ -4,6 +4,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from .choices import *
from .constants import * from .constants import *
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
@ -25,7 +26,7 @@ class CustomFieldFilter(django_filters.Filter):
return queryset return queryset
# Selection fields get special treatment (values must be integers) # Selection fields get special treatment (values must be integers)
if self.cf_type == CF_TYPE_SELECT: if self.cf_type == CustomFieldTypeChoices.TYPE_SELECT:
try: try:
# Treat 0 as None # Treat 0 as None
if int(value) == 0: if int(value) == 0:
@ -42,7 +43,8 @@ class CustomFieldFilter(django_filters.Filter):
return queryset.none() return queryset.none()
# Apply the assigned filter logic (exact or loose) # Apply the assigned filter logic (exact or loose)
if self.cf_type == CF_TYPE_BOOLEAN or self.filter_logic == CF_FILTER_EXACT: if (self.cf_type == CustomFieldTypeChoices.TYPE_BOOLEAN or
self.filter_logic == CustomFieldFilterLogicChoices.FILTER_EXACT):
queryset = queryset.filter( queryset = queryset.filter(
custom_field_values__field__name=self.field_name, custom_field_values__field__name=self.field_name,
custom_field_values__serialized_value=value custom_field_values__serialized_value=value
@ -65,7 +67,11 @@ class CustomFieldFilterSet(django_filters.FilterSet):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
obj_type = ContentType.objects.get_for_model(self._meta.model) obj_type = ContentType.objects.get_for_model(self._meta.model)
custom_fields = CustomField.objects.filter(obj_type=obj_type).exclude(filter_logic=CF_FILTER_DISABLED) custom_fields = CustomField.objects.filter(
obj_type=obj_type
).exclude(
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
)
for cf in custom_fields: for cf in custom_fields:
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf) self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)

View File

@ -13,6 +13,7 @@ from utilities.forms import (
CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2, CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2,
BOOLEAN_WITH_BLANK_CHOICES, BOOLEAN_WITH_BLANK_CHOICES,
) )
from .choices import *
from .constants import * from .constants import *
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
@ -28,18 +29,18 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
field_dict = OrderedDict() field_dict = OrderedDict()
custom_fields = CustomField.objects.filter(obj_type=content_type) custom_fields = CustomField.objects.filter(obj_type=content_type)
if filterable_only: if filterable_only:
custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED) custom_fields = custom_fields.exclude(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED)
for cf in custom_fields: for cf in custom_fields:
field_name = 'cf_{}'.format(str(cf.name)) field_name = 'cf_{}'.format(str(cf.name))
initial = cf.default if not bulk_edit else None initial = cf.default if not bulk_edit else None
# Integer # Integer
if cf.type == CF_TYPE_INTEGER: if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
field = forms.IntegerField(required=cf.required, initial=initial) field = forms.IntegerField(required=cf.required, initial=initial)
# Boolean # Boolean
elif cf.type == CF_TYPE_BOOLEAN: elif cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
choices = ( choices = (
(None, '---------'), (None, '---------'),
(1, 'True'), (1, 'True'),
@ -56,11 +57,11 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
) )
# Date # Date
elif cf.type == CF_TYPE_DATE: elif cf.type == CustomFieldTypeChoices.TYPE_DATE:
field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD") field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
# Select # Select
elif cf.type == CF_TYPE_SELECT: elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()] choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
if not cf.required or bulk_edit or filterable_only: if not cf.required or bulk_edit or filterable_only:
choices = [(None, '---------')] + choices choices = [(None, '---------')] + choices
@ -74,7 +75,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice) field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
# URL # URL
elif cf.type == CF_TYPE_URL: elif cf.type == CustomFieldTypeChoices.TYPE_URL:
field = LaxURLField(required=cf.required, initial=initial) field = LaxURLField(required=cf.required, initial=initial)
# Text # Text
@ -400,7 +401,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
) )
) )
action = forms.ChoiceField( action = forms.ChoiceField(
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES), choices=add_blank_choice(ObjectChangeActionChoices),
required=False required=False
) )
user = forms.ModelChoiceField( user = forms.ModelChoiceField(

View File

@ -10,7 +10,7 @@ from django.utils import timezone
from django_prometheus.models import model_deletes, model_inserts, model_updates from django_prometheus.models import model_deletes, model_inserts, model_updates
from utilities.querysets import DummyQuerySet from utilities.querysets import DummyQuerySet
from .constants import * from .choices import ObjectChangeActionChoices
from .models import ObjectChange from .models import ObjectChange
from .signals import purge_changelog from .signals import purge_changelog
from .webhooks import enqueue_webhooks from .webhooks import enqueue_webhooks
@ -23,7 +23,7 @@ def handle_changed_object(sender, instance, **kwargs):
Fires when an object is created or updated. Fires when an object is created or updated.
""" """
# Queue the object for processing once the request completes # Queue the object for processing once the request completes
action = OBJECTCHANGE_ACTION_CREATE if kwargs['created'] else OBJECTCHANGE_ACTION_UPDATE action = ObjectChangeActionChoices.ACTION_CREATE if kwargs['created'] else ObjectChangeActionChoices.ACTION_UPDATE
_thread_locals.changed_objects.append( _thread_locals.changed_objects.append(
(instance, action) (instance, action)
) )
@ -46,7 +46,7 @@ def handle_deleted_object(sender, instance, **kwargs):
# Queue the copy of the object for processing once the request completes # Queue the copy of the object for processing once the request completes
_thread_locals.changed_objects.append( _thread_locals.changed_objects.append(
(copy, OBJECTCHANGE_ACTION_DELETE) (copy, ObjectChangeActionChoices.ACTION_DELETE)
) )
@ -101,7 +101,7 @@ class ObjectChangeMiddleware(object):
for instance, action in _thread_locals.changed_objects: for instance, action in _thread_locals.changed_objects:
# Refresh cached custom field values # Refresh cached custom field values
if action in [OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_UPDATE]: if action in [ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE]:
if hasattr(instance, 'cache_custom_fields'): if hasattr(instance, 'cache_custom_fields'):
instance.cache_custom_fields() instance.cache_custom_fields()
@ -116,11 +116,11 @@ class ObjectChangeMiddleware(object):
enqueue_webhooks(instance, request.user, request.id, action) enqueue_webhooks(instance, request.user, request.id, action)
# Increment metric counters # Increment metric counters
if action == OBJECTCHANGE_ACTION_CREATE: if action == ObjectChangeActionChoices.ACTION_CREATE:
model_inserts.labels(instance._meta.model_name).inc() model_inserts.labels(instance._meta.model_name).inc()
elif action == OBJECTCHANGE_ACTION_UPDATE: elif action == ObjectChangeActionChoices.ACTION_UPDATE:
model_updates.labels(instance._meta.model_name).inc() model_updates.labels(instance._meta.model_name).inc()
elif action == OBJECTCHANGE_ACTION_DELETE: elif action == ObjectChangeActionChoices.ACTION_DELETE:
model_deletes.labels(instance._meta.model_name).inc() model_deletes.labels(instance._meta.model_name).inc()
# Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in # Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in

View File

@ -8,8 +8,6 @@ import django.db.models.deletion
import extras.models import extras.models
from django.db.utils import OperationalError from django.db.utils import OperationalError
from extras.constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_FILTER_LOOSE, CF_TYPE_SELECT
def verify_postgresql_version(apps, schema_editor): def verify_postgresql_version(apps, schema_editor):
""" """

View File

@ -2,21 +2,19 @@
# Generated by Django 1.11.9 on 2018-02-21 19:48 # Generated by Django 1.11.9 on 2018-02-21 19:48
from django.db import migrations, models from django.db import migrations, models
from extras.constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_FILTER_LOOSE, CF_TYPE_SELECT
def is_filterable_to_filter_logic(apps, schema_editor): def is_filterable_to_filter_logic(apps, schema_editor):
CustomField = apps.get_model('extras', 'CustomField') CustomField = apps.get_model('extras', 'CustomField')
CustomField.objects.filter(is_filterable=False).update(filter_logic=CF_FILTER_DISABLED) CustomField.objects.filter(is_filterable=False).update(filter_logic=0)
CustomField.objects.filter(is_filterable=True).update(filter_logic=CF_FILTER_LOOSE) CustomField.objects.filter(is_filterable=True).update(filter_logic=1)
# Select fields match on primary key only # Select fields match on primary key only
CustomField.objects.filter(is_filterable=True, type=CF_TYPE_SELECT).update(filter_logic=CF_FILTER_EXACT) CustomField.objects.filter(is_filterable=True, type=600).update(filter_logic=2)
def filter_logic_to_is_filterable(apps, schema_editor): def filter_logic_to_is_filterable(apps, schema_editor):
CustomField = apps.get_model('extras', 'CustomField') CustomField = apps.get_model('extras', 'CustomField')
CustomField.objects.filter(filter_logic=CF_FILTER_DISABLED).update(is_filterable=False) CustomField.objects.filter(filter_logic=0).update(is_filterable=False)
CustomField.objects.exclude(filter_logic=CF_FILTER_DISABLED).update(is_filterable=True) CustomField.objects.exclude(filter_logic=0).update(is_filterable=True)
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -0,0 +1,69 @@
from django.db import migrations, models
import django.db.models.deletion
CUSTOMFIELD_TYPE_CHOICES = (
(100, 'text'),
(200, 'integer'),
(300, 'boolean'),
(400, 'date'),
(500, 'url'),
(600, 'select')
)
CUSTOMFIELD_FILTER_LOGIC_CHOICES = (
(0, 'disabled'),
(1, 'integer'),
(2, 'exact'),
)
def customfield_type_to_slug(apps, schema_editor):
CustomField = apps.get_model('extras', 'CustomField')
for id, slug in CUSTOMFIELD_TYPE_CHOICES:
CustomField.objects.filter(type=str(id)).update(type=slug)
def customfield_filter_logic_to_slug(apps, schema_editor):
CustomField = apps.get_model('extras', 'CustomField')
for id, slug in CUSTOMFIELD_FILTER_LOGIC_CHOICES:
CustomField.objects.filter(filter_logic=str(id)).update(filter_logic=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('extras', '0028_remove_topology_maps'),
]
operations = [
# CustomField.type
migrations.AlterField(
model_name='customfield',
name='type',
field=models.CharField(default='text', max_length=50),
),
migrations.RunPython(
code=customfield_type_to_slug
),
# Update CustomFieldChoice.field.limit_choices_to
migrations.AlterField(
model_name='customfieldchoice',
name='field',
field=models.ForeignKey(limit_choices_to={'type': 'select'}, on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='extras.CustomField'),
),
# CustomField.filter_logic
migrations.AlterField(
model_name='customfield',
name='filter_logic',
field=models.CharField(default='loose', max_length=50),
),
migrations.RunPython(
code=customfield_filter_logic_to_slug
),
]

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
OBJECTCHANGE_ACTION_CHOICES = (
(1, 'create'),
(2, 'update'),
(3, 'delete'),
)
def objectchange_action_to_slug(apps, schema_editor):
ObjectChange = apps.get_model('extras', 'ObjectChange')
for id, slug in OBJECTCHANGE_ACTION_CHOICES:
ObjectChange.objects.filter(action=str(id)).update(action=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('extras', '0029_3569_customfield_fields'),
]
operations = [
# ObjectChange.action
migrations.AlterField(
model_name='objectchange',
name='action',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=objectchange_action_to_slug
),
]

View File

@ -0,0 +1,35 @@
from django.db import migrations, models
EXPORTTEMPLATE_LANGUAGE_CHOICES = (
(10, 'django'),
(20, 'jinja2'),
)
def exporttemplate_language_to_slug(apps, schema_editor):
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
for id, slug in EXPORTTEMPLATE_LANGUAGE_CHOICES:
ExportTemplate.objects.filter(template_language=str(id)).update(template_language=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('extras', '0030_3569_objectchange_fields'),
]
operations = [
# ExportTemplate.template_language
migrations.AlterField(
model_name='exporttemplate',
name='template_language',
field=models.CharField(default='jinja2', max_length=50),
),
migrations.RunPython(
code=exporttemplate_language_to_slug
),
]

View File

@ -0,0 +1,35 @@
from django.db import migrations, models
WEBHOOK_CONTENTTYPE_CHOICES = (
(1, 'application/json'),
(2, 'application/x-www-form-urlencoded'),
)
def webhook_contenttype_to_slug(apps, schema_editor):
Webhook = apps.get_model('extras', 'Webhook')
for id, slug in WEBHOOK_CONTENTTYPE_CHOICES:
Webhook.objects.filter(http_content_type=str(id)).update(http_content_type=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('extras', '0031_3569_exporttemplate_fields'),
]
operations = [
# Webhook.http_content_type
migrations.AlterField(
model_name='webhook',
name='http_content_type',
field=models.CharField(default='application/json', max_length=50),
),
migrations.RunPython(
code=webhook_contenttype_to_slug
),
]

View File

@ -15,6 +15,7 @@ from taggit.models import TagBase, GenericTaggedItemBase
from utilities.fields import ColorField from utilities.fields import ColorField
from utilities.utils import deepmerge, model_names_to_filter_dict from utilities.utils import deepmerge, model_names_to_filter_dict
from .choices import *
from .constants import * from .constants import *
from .querysets import ConfigContextQuerySet from .querysets import ConfigContextQuerySet
@ -62,9 +63,10 @@ class Webhook(models.Model):
verbose_name='URL', verbose_name='URL',
help_text="A POST will be sent to this URL when the webhook is called." help_text="A POST will be sent to this URL when the webhook is called."
) )
http_content_type = models.PositiveSmallIntegerField( http_content_type = models.CharField(
choices=WEBHOOK_CT_CHOICES, max_length=50,
default=WEBHOOK_CT_JSON, choices=WebhookContentTypeChoices,
default=WebhookContentTypeChoices.CONTENTTYPE_JSON,
verbose_name='HTTP content type' verbose_name='HTTP content type'
) )
additional_headers = JSONField( additional_headers = JSONField(
@ -182,9 +184,10 @@ class CustomField(models.Model):
limit_choices_to=get_custom_field_models, limit_choices_to=get_custom_field_models,
help_text='The object(s) to which this field applies.' help_text='The object(s) to which this field applies.'
) )
type = models.PositiveSmallIntegerField( type = models.CharField(
choices=CUSTOMFIELD_TYPE_CHOICES, max_length=50,
default=CF_TYPE_TEXT choices=CustomFieldTypeChoices,
default=CustomFieldTypeChoices.TYPE_TEXT
) )
name = models.CharField( name = models.CharField(
max_length=50, max_length=50,
@ -205,9 +208,10 @@ class CustomField(models.Model):
help_text='If true, this field is required when creating new objects ' help_text='If true, this field is required when creating new objects '
'or editing an existing object.' 'or editing an existing object.'
) )
filter_logic = models.PositiveSmallIntegerField( filter_logic = models.CharField(
choices=CF_FILTER_CHOICES, max_length=50,
default=CF_FILTER_LOOSE, choices=CustomFieldFilterLogicChoices,
default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
help_text='Loose matches any instance of a given string; exact ' help_text='Loose matches any instance of a given string; exact '
'matches the entire field.' 'matches the entire field.'
) )
@ -233,15 +237,15 @@ class CustomField(models.Model):
""" """
if value is None: if value is None:
return '' return ''
if self.type == CF_TYPE_BOOLEAN: if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
return str(int(bool(value))) return str(int(bool(value)))
if self.type == CF_TYPE_DATE: if self.type == CustomFieldTypeChoices.TYPE_DATE:
# Could be date/datetime object or string # Could be date/datetime object or string
try: try:
return value.strftime('%Y-%m-%d') return value.strftime('%Y-%m-%d')
except AttributeError: except AttributeError:
return value return value
if self.type == CF_TYPE_SELECT: if self.type == CustomFieldTypeChoices.TYPE_SELECT:
# Could be ModelChoiceField or TypedChoiceField # Could be ModelChoiceField or TypedChoiceField
return str(value.id) if hasattr(value, 'id') else str(value) return str(value.id) if hasattr(value, 'id') else str(value)
return value return value
@ -252,14 +256,14 @@ class CustomField(models.Model):
""" """
if serialized_value == '': if serialized_value == '':
return None return None
if self.type == CF_TYPE_INTEGER: if self.type == CustomFieldTypeChoices.TYPE_INTEGER:
return int(serialized_value) return int(serialized_value)
if self.type == CF_TYPE_BOOLEAN: if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
return bool(int(serialized_value)) return bool(int(serialized_value))
if self.type == CF_TYPE_DATE: if self.type == CustomFieldTypeChoices.TYPE_DATE:
# Read date as YYYY-MM-DD # Read date as YYYY-MM-DD
return date(*[int(n) for n in serialized_value.split('-')]) return date(*[int(n) for n in serialized_value.split('-')])
if self.type == CF_TYPE_SELECT: if self.type == CustomFieldTypeChoices.TYPE_SELECT:
return self.choices.get(pk=int(serialized_value)) return self.choices.get(pk=int(serialized_value))
return serialized_value return serialized_value
@ -312,7 +316,7 @@ class CustomFieldChoice(models.Model):
to='extras.CustomField', to='extras.CustomField',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='choices', related_name='choices',
limit_choices_to={'type': CF_TYPE_SELECT} limit_choices_to={'type': CustomFieldTypeChoices.TYPE_SELECT}
) )
value = models.CharField( value = models.CharField(
max_length=100 max_length=100
@ -330,14 +334,17 @@ class CustomFieldChoice(models.Model):
return self.value return self.value
def clean(self): def clean(self):
if self.field.type != CF_TYPE_SELECT: if self.field.type != CustomFieldTypeChoices.TYPE_SELECT:
raise ValidationError("Custom field choices can only be assigned to selection fields.") raise ValidationError("Custom field choices can only be assigned to selection fields.")
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
# When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it # When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it
pk = self.pk pk = self.pk
super().delete(using, keep_parents) super().delete(using, keep_parents)
CustomFieldValue.objects.filter(field__type=CF_TYPE_SELECT, serialized_value=str(pk)).delete() CustomFieldValue.objects.filter(
field__type=CustomFieldTypeChoices.TYPE_SELECT,
serialized_value=str(pk)
).delete()
# #
@ -381,8 +388,8 @@ class CustomLink(models.Model):
) )
button_class = models.CharField( button_class = models.CharField(
max_length=30, max_length=30,
choices=BUTTON_CLASS_CHOICES, choices=CustomLinkButtonClassChoices,
default=BUTTON_CLASS_DEFAULT, default=CustomLinkButtonClassChoices.CLASS_DEFAULT,
help_text="The class of the first link in a group will be used for the dropdown button" help_text="The class of the first link in a group will be used for the dropdown button"
) )
new_window = models.BooleanField( new_window = models.BooleanField(
@ -458,9 +465,10 @@ class ExportTemplate(models.Model):
max_length=200, max_length=200,
blank=True blank=True
) )
template_language = models.PositiveSmallIntegerField( template_language = models.CharField(
choices=TEMPLATE_LANGUAGE_CHOICES, max_length=50,
default=TEMPLATE_LANGUAGE_JINJA2 choices=ExportTemplateLanguageChoices,
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
) )
template_code = models.TextField( template_code = models.TextField(
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.' help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.'
@ -796,8 +804,9 @@ class ObjectChange(models.Model):
request_id = models.UUIDField( request_id = models.UUIDField(
editable=False editable=False
) )
action = models.PositiveSmallIntegerField( action = models.CharField(
choices=OBJECTCHANGE_ACTION_CHOICES max_length=50,
choices=ObjectChangeActionChoices
) )
changed_object_type = models.ForeignKey( changed_object_type = models.ForeignKey(
to=ContentType, to=ContentType,

View File

@ -38,11 +38,11 @@ OBJECTCHANGE_TIME = """
""" """
OBJECTCHANGE_ACTION = """ OBJECTCHANGE_ACTION = """
{% if record.action == 1 %} {% if record.action == 'create' %}
<span class="label label-success">Created</span> <span class="label label-success">Created</span>
{% elif record.action == 2 %} {% elif record.action == 'update' %}
<span class="label label-primary">Updated</span> <span class="label label-primary">Updated</span>
{% elif record.action == 3 %} {% elif record.action == 'delete' %}
<span class="label label-danger">Deleted</span> <span class="label label-danger">Deleted</span>
{% endif %} {% endif %}
""" """

View File

@ -3,6 +3,7 @@ from django.urls import reverse
from rest_framework import status from rest_framework import status
from dcim.models import Site from dcim.models import Site
from extras.choices import *
from extras.constants import * from extras.constants import *
from extras.models import CustomField, CustomFieldValue, ObjectChange from extras.models import CustomField, CustomFieldValue, ObjectChange
from utilities.testing import APITestCase from utilities.testing import APITestCase
@ -17,7 +18,7 @@ class ChangeLogTest(APITestCase):
# Create a custom field on the Site model # Create a custom field on the Site model
ct = ContentType.objects.get_for_model(Site) ct = ContentType.objects.get_for_model(Site)
cf = CustomField( cf = CustomField(
type=CF_TYPE_TEXT, type=CustomFieldTypeChoices.TYPE_TEXT,
name='my_field', name='my_field',
required=False required=False
) )
@ -49,7 +50,7 @@ class ChangeLogTest(APITestCase):
changed_object_id=site.pk changed_object_id=site.pk
) )
self.assertEqual(oc.changed_object, site) self.assertEqual(oc.changed_object, site)
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_CREATE) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE)
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields']) self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
self.assertListEqual(sorted(oc.object_data['tags']), data['tags']) self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
@ -81,7 +82,7 @@ class ChangeLogTest(APITestCase):
changed_object_id=site.pk changed_object_id=site.pk
) )
self.assertEqual(oc.changed_object, site) self.assertEqual(oc.changed_object, site)
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_UPDATE) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE)
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields']) self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
self.assertListEqual(sorted(oc.object_data['tags']), data['tags']) self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
@ -110,6 +111,6 @@ class ChangeLogTest(APITestCase):
oc = ObjectChange.objects.first() oc = ObjectChange.objects.first()
self.assertEqual(oc.changed_object, None) self.assertEqual(oc.changed_object, None)
self.assertEqual(oc.object_repr, site.name) self.assertEqual(oc.object_repr, site.name)
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_DELETE) self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE)
self.assertEqual(oc.object_data['custom_fields'], {'my_field': 'ABC'}) self.assertEqual(oc.object_data['custom_fields'], {'my_field': 'ABC'})
self.assertListEqual(sorted(oc.object_data['tags']), ['bar', 'foo']) self.assertListEqual(sorted(oc.object_data['tags']), ['bar', 'foo'])

View File

@ -6,7 +6,7 @@ from django.urls import reverse
from rest_framework import status from rest_framework import status
from dcim.models import Site from dcim.models import Site
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CF_TYPE_URL, CF_TYPE_SELECT from extras.choices import *
from extras.models import CustomField, CustomFieldValue, CustomFieldChoice from extras.models import CustomField, CustomFieldValue, CustomFieldChoice
from utilities.testing import APITestCase from utilities.testing import APITestCase
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
@ -25,13 +25,13 @@ class CustomFieldTest(TestCase):
def test_simple_fields(self): def test_simple_fields(self):
DATA = ( DATA = (
{'field_type': CF_TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''}, {'field_type': CustomFieldTypeChoices.TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
{'field_type': CF_TYPE_INTEGER, 'field_value': 0, 'empty_value': None}, {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
{'field_type': CF_TYPE_INTEGER, 'field_value': 42, 'empty_value': None}, {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
{'field_type': CF_TYPE_BOOLEAN, 'field_value': True, 'empty_value': None}, {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
{'field_type': CF_TYPE_BOOLEAN, 'field_value': False, 'empty_value': None}, {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
{'field_type': CF_TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None}, {'field_type': CustomFieldTypeChoices.TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None},
{'field_type': CF_TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''}, {'field_type': CustomFieldTypeChoices.TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''},
) )
obj_type = ContentType.objects.get_for_model(Site) obj_type = ContentType.objects.get_for_model(Site)
@ -67,7 +67,7 @@ class CustomFieldTest(TestCase):
obj_type = ContentType.objects.get_for_model(Site) obj_type = ContentType.objects.get_for_model(Site)
# Create a custom field # Create a custom field
cf = CustomField(type=CF_TYPE_SELECT, name='my_field', required=False) cf = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='my_field', required=False)
cf.save() cf.save()
cf.obj_type.set([obj_type]) cf.obj_type.set([obj_type])
cf.save() cf.save()
@ -107,37 +107,37 @@ class CustomFieldAPITest(APITestCase):
content_type = ContentType.objects.get_for_model(Site) content_type = ContentType.objects.get_for_model(Site)
# Text custom field # Text custom field
self.cf_text = CustomField(type=CF_TYPE_TEXT, name='magic_word') self.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='magic_word')
self.cf_text.save() self.cf_text.save()
self.cf_text.obj_type.set([content_type]) self.cf_text.obj_type.set([content_type])
self.cf_text.save() self.cf_text.save()
# Integer custom field # Integer custom field
self.cf_integer = CustomField(type=CF_TYPE_INTEGER, name='magic_number') self.cf_integer = CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='magic_number')
self.cf_integer.save() self.cf_integer.save()
self.cf_integer.obj_type.set([content_type]) self.cf_integer.obj_type.set([content_type])
self.cf_integer.save() self.cf_integer.save()
# Boolean custom field # Boolean custom field
self.cf_boolean = CustomField(type=CF_TYPE_BOOLEAN, name='is_magic') self.cf_boolean = CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='is_magic')
self.cf_boolean.save() self.cf_boolean.save()
self.cf_boolean.obj_type.set([content_type]) self.cf_boolean.obj_type.set([content_type])
self.cf_boolean.save() self.cf_boolean.save()
# Date custom field # Date custom field
self.cf_date = CustomField(type=CF_TYPE_DATE, name='magic_date') self.cf_date = CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='magic_date')
self.cf_date.save() self.cf_date.save()
self.cf_date.obj_type.set([content_type]) self.cf_date.obj_type.set([content_type])
self.cf_date.save() self.cf_date.save()
# URL custom field # URL custom field
self.cf_url = CustomField(type=CF_TYPE_URL, name='magic_url') self.cf_url = CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='magic_url')
self.cf_url.save() self.cf_url.save()
self.cf_url.obj_type.set([content_type]) self.cf_url.obj_type.set([content_type])
self.cf_url.save() self.cf_url.save()
# Select custom field # Select custom field
self.cf_select = CustomField(type=CF_TYPE_SELECT, name='magic_choice') self.cf_select = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='magic_choice')
self.cf_select.save() self.cf_select.save()
self.cf_select.obj_type.set([content_type]) self.cf_select.obj_type.set([content_type])
self.cf_select.save() self.cf_select.save()
@ -308,8 +308,8 @@ class CustomFieldChoiceAPITest(APITestCase):
vm_content_type = ContentType.objects.get_for_model(VirtualMachine) vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
self.cf_1 = CustomField.objects.create(name="cf_1", type=CF_TYPE_SELECT) self.cf_1 = CustomField.objects.create(name="cf_1", type=CustomFieldTypeChoices.TYPE_SELECT)
self.cf_2 = CustomField.objects.create(name="cf_2", type=CF_TYPE_SELECT) self.cf_2 = CustomField.objects.create(name="cf_2", type=CustomFieldTypeChoices.TYPE_SELECT)
self.cf_choice_1 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_1", weight=100) self.cf_choice_1 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_1", weight=100)
self.cf_choice_2 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_2", weight=50) self.cf_choice_2 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_2", weight=50)

View File

@ -6,7 +6,7 @@ from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from dcim.models import Site from dcim.models import Site
from extras.constants import OBJECTCHANGE_ACTION_UPDATE from extras.choices import ObjectChangeActionChoices
from extras.models import ConfigContext, ObjectChange, Tag from extras.models import ConfigContext, ObjectChange, Tag
from utilities.testing import create_test_user from utilities.testing import create_test_user
@ -83,7 +83,7 @@ class ObjectChangeTestCase(TestCase):
# Create three ObjectChanges # Create three ObjectChanges
for i in range(1, 4): for i in range(1, 4):
oc = site.to_objectchange(action=OBJECTCHANGE_ACTION_UPDATE) oc = site.to_objectchange(action=ObjectChangeActionChoices.ACTION_UPDATE)
oc.user = user oc.user = user
oc.request_id = uuid.uuid4() oc.request_id = uuid.uuid4()
oc.save() oc.save()

View File

@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from extras.models import Webhook from extras.models import Webhook
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from .choices import *
from .constants import * from .constants import *
@ -18,9 +19,9 @@ def enqueue_webhooks(instance, user, request_id, action):
# Retrieve any applicable Webhooks # Retrieve any applicable Webhooks
action_flag = { action_flag = {
OBJECTCHANGE_ACTION_CREATE: 'type_create', ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
OBJECTCHANGE_ACTION_UPDATE: 'type_update', ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
OBJECTCHANGE_ACTION_DELETE: 'type_delete', ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
}[action] }[action]
obj_type = ContentType.objects.get_for_model(instance.__class__) obj_type = ContentType.objects.get_for_model(instance.__class__)
webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True}) webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True})

View File

@ -6,6 +6,7 @@ import requests
from django_rq import job from django_rq import job
from rest_framework.utils.encoders import JSONEncoder from rest_framework.utils.encoders import JSONEncoder
from .choices import ObjectChangeActionChoices
from .constants import * from .constants import *
@ -15,7 +16,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque
Make a POST request to the defined Webhook Make a POST request to the defined Webhook
""" """
payload = { payload = {
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(), 'event': dict(ObjectChangeActionChoices)[event].lower(),
'timestamp': timestamp, 'timestamp': timestamp,
'model': model_name, 'model': model_name,
'username': username, 'username': username,

View File

@ -8,8 +8,8 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer
from dcim.models import Interface from dcim.models import Interface
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from ipam.constants import * from ipam.choices import *
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam.models import AF_CHOICES, Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.nested_serializers import NestedTenantSerializer
from utilities.api import ( from utilities.api import (
ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer, ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer,
@ -102,7 +102,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
site = NestedSiteSerializer(required=False, allow_null=True) site = NestedSiteSerializer(required=False, allow_null=True)
group = NestedVLANGroupSerializer(required=False, allow_null=True) group = NestedVLANGroupSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=VLAN_STATUS_CHOICES, required=False) status = ChoiceField(choices=VLANStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = NestedRoleSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
prefix_count = serializers.IntegerField(read_only=True) prefix_count = serializers.IntegerField(read_only=True)
@ -140,7 +140,7 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
vrf = NestedVRFSerializer(required=False, allow_null=True) vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
vlan = NestedVLANSerializer(required=False, allow_null=True) vlan = NestedVLANSerializer(required=False, allow_null=True)
status = ChoiceField(choices=PREFIX_STATUS_CHOICES, required=False) status = ChoiceField(choices=PrefixStatusChoices, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = NestedRoleSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
@ -200,8 +200,8 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
family = ChoiceField(choices=AF_CHOICES, read_only=True) family = ChoiceField(choices=AF_CHOICES, read_only=True)
vrf = NestedVRFSerializer(required=False, allow_null=True) vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False) status = ChoiceField(choices=IPAddressStatusChoices, required=False)
role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False, allow_null=True) role = ChoiceField(choices=IPAddressRoleChoices, required=False, allow_null=True)
interface = IPAddressInterfaceSerializer(required=False, allow_null=True) interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
nat_inside = NestedIPAddressSerializer(required=False, allow_null=True) nat_inside = NestedIPAddressSerializer(required=False, allow_null=True)
nat_outside = NestedIPAddressSerializer(read_only=True) nat_outside = NestedIPAddressSerializer(read_only=True)
@ -239,7 +239,7 @@ class AvailableIPSerializer(serializers.Serializer):
class ServiceSerializer(CustomFieldModelSerializer): class ServiceSerializer(CustomFieldModelSerializer):
device = NestedDeviceSerializer(required=False, allow_null=True) device = NestedDeviceSerializer(required=False, allow_null=True)
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True) virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
protocol = ChoiceField(choices=IP_PROTOCOL_CHOICES) protocol = ChoiceField(choices=ServiceProtocolChoices)
ipaddresses = SerializedPKRelatedField( ipaddresses = SerializedPKRelatedField(
queryset=IPAddress.objects.all(), queryset=IPAddress.objects.all(),
serializer=NestedIPAddressSerializer, serializer=NestedIPAddressSerializer,

130
netbox/ipam/choices.py Normal file
View File

@ -0,0 +1,130 @@
from utilities.choices import ChoiceSet
#
# Prefixes
#
class PrefixStatusChoices(ChoiceSet):
STATUS_CONTAINER = 'container'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
(STATUS_CONTAINER, 'Container'),
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
)
LEGACY_MAP = {
STATUS_CONTAINER: 0,
STATUS_ACTIVE: 1,
STATUS_RESERVED: 2,
STATUS_DEPRECATED: 3,
}
#
# IPAddresses
#
class IPAddressStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
STATUS_DHCP = 'dhcp'
CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
(STATUS_DHCP, 'DHCP'),
)
LEGACY_MAP = {
STATUS_ACTIVE: 1,
STATUS_RESERVED: 2,
STATUS_DEPRECATED: 3,
STATUS_DHCP: 5,
}
class IPAddressRoleChoices(ChoiceSet):
ROLE_LOOPBACK = 'loopback'
ROLE_SECONDARY = 'secondary'
ROLE_ANYCAST = 'anycast'
ROLE_VIP = 'vip'
ROLE_VRRP = 'vrrp'
ROLE_HSRP = 'hsrp'
ROLE_GLBP = 'glbp'
ROLE_CARP = 'carp'
CHOICES = (
(ROLE_LOOPBACK, 'Loopback'),
(ROLE_SECONDARY, 'Secondary'),
(ROLE_ANYCAST, 'Anycast'),
(ROLE_VIP, 'VIP'),
(ROLE_VRRP, 'VRRP'),
(ROLE_HSRP, 'HSRP'),
(ROLE_GLBP, 'GLBP'),
(ROLE_CARP, 'CARP'),
)
LEGACY_MAP = {
ROLE_LOOPBACK: 10,
ROLE_SECONDARY: 20,
ROLE_ANYCAST: 30,
ROLE_VIP: 40,
ROLE_VRRP: 41,
ROLE_HSRP: 42,
ROLE_GLBP: 43,
ROLE_CARP: 44,
}
#
# VLANs
#
class VLANStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
)
LEGACY_MAP = {
STATUS_ACTIVE: 1,
STATUS_RESERVED: 2,
STATUS_DEPRECATED: 3,
}
#
# VLANs
#
class ServiceProtocolChoices(ChoiceSet):
PROTOCOL_TCP = 'tcp'
PROTOCOL_UDP = 'udp'
CHOICES = (
(PROTOCOL_TCP, 'TCP'),
(PROTOCOL_UDP, 'UDP'),
)
LEGACY_MAP = {
PROTOCOL_TCP: 6,
PROTOCOL_UDP: 17,
}

View File

@ -1,97 +0,0 @@
# IP address families
AF_CHOICES = (
(4, 'IPv4'),
(6, 'IPv6'),
)
# Prefix statuses
PREFIX_STATUS_CONTAINER = 0
PREFIX_STATUS_ACTIVE = 1
PREFIX_STATUS_RESERVED = 2
PREFIX_STATUS_DEPRECATED = 3
PREFIX_STATUS_CHOICES = (
(PREFIX_STATUS_CONTAINER, 'Container'),
(PREFIX_STATUS_ACTIVE, 'Active'),
(PREFIX_STATUS_RESERVED, 'Reserved'),
(PREFIX_STATUS_DEPRECATED, 'Deprecated')
)
# IP address statuses
IPADDRESS_STATUS_ACTIVE = 1
IPADDRESS_STATUS_RESERVED = 2
IPADDRESS_STATUS_DEPRECATED = 3
IPADDRESS_STATUS_DHCP = 5
IPADDRESS_STATUS_CHOICES = (
(IPADDRESS_STATUS_ACTIVE, 'Active'),
(IPADDRESS_STATUS_RESERVED, 'Reserved'),
(IPADDRESS_STATUS_DEPRECATED, 'Deprecated'),
(IPADDRESS_STATUS_DHCP, 'DHCP')
)
# IP address roles
IPADDRESS_ROLE_LOOPBACK = 10
IPADDRESS_ROLE_SECONDARY = 20
IPADDRESS_ROLE_ANYCAST = 30
IPADDRESS_ROLE_VIP = 40
IPADDRESS_ROLE_VRRP = 41
IPADDRESS_ROLE_HSRP = 42
IPADDRESS_ROLE_GLBP = 43
IPADDRESS_ROLE_CARP = 44
IPADDRESS_ROLE_CHOICES = (
(IPADDRESS_ROLE_LOOPBACK, 'Loopback'),
(IPADDRESS_ROLE_SECONDARY, 'Secondary'),
(IPADDRESS_ROLE_ANYCAST, 'Anycast'),
(IPADDRESS_ROLE_VIP, 'VIP'),
(IPADDRESS_ROLE_VRRP, 'VRRP'),
(IPADDRESS_ROLE_HSRP, 'HSRP'),
(IPADDRESS_ROLE_GLBP, 'GLBP'),
(IPADDRESS_ROLE_CARP, 'CARP'),
)
IPADDRESS_ROLES_NONUNIQUE = (
# IPAddress roles which are exempt from unique address enforcement
IPADDRESS_ROLE_ANYCAST,
IPADDRESS_ROLE_VIP,
IPADDRESS_ROLE_VRRP,
IPADDRESS_ROLE_HSRP,
IPADDRESS_ROLE_GLBP,
IPADDRESS_ROLE_CARP,
)
# VLAN statuses
VLAN_STATUS_ACTIVE = 1
VLAN_STATUS_RESERVED = 2
VLAN_STATUS_DEPRECATED = 3
VLAN_STATUS_CHOICES = (
(VLAN_STATUS_ACTIVE, 'Active'),
(VLAN_STATUS_RESERVED, 'Reserved'),
(VLAN_STATUS_DEPRECATED, 'Deprecated')
)
# Bootstrap CSS classes
STATUS_CHOICE_CLASSES = {
0: 'default',
1: 'primary',
2: 'info',
3: 'danger',
4: 'warning',
5: 'success',
}
ROLE_CHOICE_CLASSES = {
10: 'default',
20: 'primary',
30: 'warning',
40: 'success',
41: 'success',
42: 'success',
43: 'success',
44: 'success',
}
# IP protocols (for services)
IP_PROTOCOL_TCP = 6
IP_PROTOCOL_UDP = 17
IP_PROTOCOL_CHOICES = (
(IP_PROTOCOL_TCP, 'TCP'),
(IP_PROTOCOL_UDP, 'UDP'),
)

View File

@ -9,7 +9,7 @@ from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from tenancy.filtersets import TenancyFilterSet from tenancy.filtersets import TenancyFilterSet
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .constants import * from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
@ -178,7 +178,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
label='Role (slug)', label='Role (slug)',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=PREFIX_STATUS_CHOICES, choices=PrefixStatusChoices,
null_value=None null_value=None
) )
tag = TagFilter() tag = TagFilter()
@ -310,11 +310,11 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
label='Interface (ID)', label='Interface (ID)',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=IPADDRESS_STATUS_CHOICES, choices=IPAddressStatusChoices,
null_value=None null_value=None
) )
role = django_filters.MultipleChoiceFilter( role = django_filters.MultipleChoiceFilter(
choices=IPADDRESS_ROLE_CHOICES choices=IPAddressRoleChoices
) )
tag = TagFilter() tag = TagFilter()
@ -424,7 +424,7 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
label='Role (slug)', label='Role (slug)',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=VLAN_STATUS_CHOICES, choices=VLANStatusChoices,
null_value=None null_value=None
) )
tag = TagFilter() tag = TagFilter()

View File

@ -40,7 +40,7 @@
"site": 1, "site": 1,
"vrf": null, "vrf": null,
"vlan": null, "vlan": null,
"status": 1, "status": "active",
"role": 1, "role": 1,
"description": "" "description": ""
} }
@ -56,7 +56,7 @@
"site": 1, "site": 1,
"vrf": null, "vrf": null,
"vlan": null, "vlan": null,
"status": 1, "status": "active",
"role": 1, "role": 1,
"description": "" "description": ""
} }
@ -322,7 +322,7 @@
"site": 1, "site": 1,
"vid": 999, "vid": 999,
"name": "TEST", "name": "TEST",
"status": 1, "status": "active",
"role": 1 "role": 1
} }
} }

View File

@ -13,7 +13,7 @@ from utilities.forms import (
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
) )
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .constants import * from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
IP_FAMILY_CHOICES = [ IP_FAMILY_CHOICES = [
@ -374,7 +374,7 @@ class PrefixCSVForm(forms.ModelForm):
required=False required=False
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=PREFIX_STATUS_CHOICES, choices=PrefixStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
role = forms.ModelChoiceField( role = forms.ModelChoiceField(
@ -459,7 +459,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(PREFIX_STATUS_CHOICES), choices=add_blank_choice(PrefixStatusChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -527,7 +527,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=PREFIX_STATUS_CHOICES, choices=PrefixStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -764,11 +764,11 @@ class IPAddressCSVForm(forms.ModelForm):
} }
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=IPADDRESS_STATUS_CHOICES, choices=IPAddressStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
role = CSVChoiceField( role = CSVChoiceField(
choices=IPADDRESS_ROLE_CHOICES, choices=IPAddressRoleChoices,
required=False, required=False,
help_text='Functional role' help_text='Functional role'
) )
@ -893,12 +893,12 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES), choices=add_blank_choice(IPAddressStatusChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
role = forms.ChoiceField( role = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES), choices=add_blank_choice(IPAddressRoleChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -972,12 +972,12 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=IPADDRESS_STATUS_CHOICES, choices=IPAddressStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
role = forms.MultipleChoiceField( role = forms.MultipleChoiceField(
choices=IPADDRESS_ROLE_CHOICES, choices=IPAddressRoleChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -1111,7 +1111,7 @@ class VLANCSVForm(forms.ModelForm):
} }
) )
status = CSVChoiceField( status = CSVChoiceField(
choices=VLAN_STATUS_CHOICES, choices=VLANStatusChoices,
help_text='Operational status' help_text='Operational status'
) )
role = forms.ModelChoiceField( role = forms.ModelChoiceField(
@ -1180,7 +1180,7 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
) )
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(VLAN_STATUS_CHOICES), choices=add_blank_choice(VLANStatusChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -1229,7 +1229,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=VLAN_STATUS_CHOICES, choices=VLANStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -1292,7 +1292,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search' label='Search'
) )
protocol = forms.ChoiceField( protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES), choices=add_blank_choice(ServiceProtocolChoices),
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -1307,7 +1307,7 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
protocol = forms.ChoiceField( protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES), choices=add_blank_choice(ServiceProtocolChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )

View File

@ -0,0 +1,37 @@
from django.db import migrations, models
PREFIX_STATUS_CHOICES = (
(0, 'container'),
(1, 'active'),
(2, 'reserved'),
(3, 'deprecated'),
)
def prefix_status_to_slug(apps, schema_editor):
Prefix = apps.get_model('ipam', 'Prefix')
for id, slug in PREFIX_STATUS_CHOICES:
Prefix.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('ipam', '0027_ipaddress_add_dns_name'),
]
operations = [
# Prefix.status
migrations.AlterField(
model_name='prefix',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=prefix_status_to_slug
),
]

View File

@ -0,0 +1,69 @@
from django.db import migrations, models
IPADDRESS_STATUS_CHOICES = (
(0, 'container'),
(1, 'active'),
(2, 'reserved'),
(3, 'deprecated'),
)
IPADDRESS_ROLE_CHOICES = (
(10, 'loopback'),
(20, 'secondary'),
(30, 'anycast'),
(40, 'vip'),
(41, 'vrrp'),
(42, 'hsrp'),
(43, 'glbp'),
(44, 'carp'),
)
def ipaddress_status_to_slug(apps, schema_editor):
IPAddress = apps.get_model('ipam', 'IPAddress')
for id, slug in IPADDRESS_STATUS_CHOICES:
IPAddress.objects.filter(status=str(id)).update(status=slug)
def ipaddress_role_to_slug(apps, schema_editor):
IPAddress = apps.get_model('ipam', 'IPAddress')
for id, slug in IPADDRESS_STATUS_CHOICES:
IPAddress.objects.filter(role=str(id)).update(role=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('ipam', '0028_3569_prefix_fields'),
]
operations = [
# IPAddress.status
migrations.AlterField(
model_name='ipaddress',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=ipaddress_status_to_slug
),
# IPAddress.role
migrations.AlterField(
model_name='ipaddress',
name='role',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.RunPython(
code=ipaddress_role_to_slug
),
migrations.AlterField(
model_name='ipaddress',
name='role',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
VLAN_STATUS_CHOICES = (
(1, 'active'),
(2, 'reserved'),
(3, 'deprecated'),
)
def vlan_status_to_slug(apps, schema_editor):
VLAN = apps.get_model('ipam', 'VLAN')
for id, slug in VLAN_STATUS_CHOICES:
VLAN.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('ipam', '0029_3569_ipaddress_fields'),
]
operations = [
# VLAN.status
migrations.AlterField(
model_name='vlan',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=vlan_status_to_slug
),
]

View File

@ -0,0 +1,35 @@
from django.db import migrations, models
SERVICE_PROTOCOL_CHOICES = (
(6, 'tcp'),
(17, 'udp'),
)
def service_protocol_to_slug(apps, schema_editor):
Service = apps.get_model('ipam', 'Service')
for id, slug in SERVICE_PROTOCOL_CHOICES:
Service.objects.filter(protocol=str(id)).update(protocol=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('ipam', '0030_3569_vlan_fields'),
]
operations = [
# Service.protocol
migrations.AlterField(
model_name='service',
name='protocol',
field=models.CharField(max_length=50),
),
migrations.RunPython(
code=service_protocol_to_slug
),
]

View File

@ -14,12 +14,29 @@ from extras.models import CustomFieldModel, ObjectChange, TaggedItem
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object from utilities.utils import serialize_object
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .constants import * from .choices import *
from .fields import IPNetworkField, IPAddressField from .fields import IPNetworkField, IPAddressField
from .querysets import PrefixQuerySet from .querysets import PrefixQuerySet
from .validators import DNSValidator from .validators import DNSValidator
# IP address families
AF_CHOICES = (
(4, 'IPv4'),
(6, 'IPv6'),
)
IPADDRESS_ROLES_NONUNIQUE = (
# IPAddress roles which are exempt from unique address enforcement
IPAddressRoleChoices.ROLE_ANYCAST,
IPAddressRoleChoices.ROLE_VIP,
IPAddressRoleChoices.ROLE_VRRP,
IPAddressRoleChoices.ROLE_HSRP,
IPAddressRoleChoices.ROLE_GLBP,
IPAddressRoleChoices.ROLE_CARP,
)
class VRF(ChangeLoggedModel, CustomFieldModel): class VRF(ChangeLoggedModel, CustomFieldModel):
""" """
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
@ -297,9 +314,10 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
null=True, null=True,
verbose_name='VLAN' verbose_name='VLAN'
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=PREFIX_STATUS_CHOICES, max_length=50,
default=PREFIX_STATUS_ACTIVE, choices=PrefixStatusChoices,
default=PrefixStatusChoices.STATUS_ACTIVE,
verbose_name='Status', verbose_name='Status',
help_text='Operational status of this prefix' help_text='Operational status of this prefix'
) )
@ -333,6 +351,13 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
] ]
STATUS_CLASS_MAP = {
'container': 'default',
'active': 'primary',
'reserved': 'info',
'deprecated': 'danger',
}
class Meta: class Meta:
ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix'] ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix']
verbose_name_plural = 'prefixes' verbose_name_plural = 'prefixes'
@ -404,7 +429,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
prefix_length = property(fset=_set_prefix_length) prefix_length = property(fset=_set_prefix_length)
def get_status_class(self): def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
def get_duplicates(self): def get_duplicates(self):
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk) return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
@ -414,7 +439,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
Prefixes belonging to any VRF. Prefixes belonging to any VRF.
""" """
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
return Prefix.objects.filter(prefix__net_contained=str(self.prefix)) return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
else: else:
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf) return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
@ -424,7 +449,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
child IPAddresses belonging to any VRF. child IPAddresses belonging to any VRF.
""" """
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix)) return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
else: else:
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf) return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
@ -490,7 +515,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
"container", calculate utilization based on child prefixes. For all others, count child IP addresses. "container", calculate utilization based on child prefixes. For all others, count child IP addresses.
""" """
if self.status == PREFIX_STATUS_CONTAINER: if self.status == PrefixStatusChoices.STATUS_CONTAINER:
queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf) queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
return int(float(child_prefixes.size) / self.prefix.size * 100) return int(float(child_prefixes.size) / self.prefix.size * 100)
@ -550,17 +575,16 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
blank=True, blank=True,
null=True null=True
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=IPADDRESS_STATUS_CHOICES, max_length=50,
default=IPADDRESS_STATUS_ACTIVE, choices=IPAddressStatusChoices,
verbose_name='Status', default=IPAddressStatusChoices.STATUS_ACTIVE,
help_text='The operational status of this IP' help_text='The operational status of this IP'
) )
role = models.PositiveSmallIntegerField( role = models.CharField(
verbose_name='Role', max_length=50,
choices=IPADDRESS_ROLE_CHOICES, choices=IPAddressRoleChoices,
blank=True, blank=True,
null=True,
help_text='The functional role of this IP' help_text='The functional role of this IP'
) )
interface = models.ForeignKey( interface = models.ForeignKey(
@ -604,6 +628,24 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
'dns_name', 'description', 'dns_name', 'description',
] ]
STATUS_CLASS_MAP = {
'active': 'primary',
'reserved': 'info',
'deprecated': 'danger',
'dhcp': 'success',
}
ROLE_CLASS_MAP = {
'loopback': 'default',
'secondary': 'primary',
'anycast': 'warning',
'vip': 'success',
'vrrp': 'success',
'hsrp': 'success',
'glbp': 'success',
'carp': 'success',
}
class Meta: class Meta:
ordering = ['family', 'address'] ordering = ['family', 'address']
verbose_name = 'IP address' verbose_name = 'IP address'
@ -737,10 +779,10 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
return None return None
def get_status_class(self): def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
def get_role_class(self): def get_role_class(self):
return ROLE_CHOICE_CLASSES[self.role] return self.ROLE_CLASS_MAP[self.role]
class VLANGroup(ChangeLoggedModel): class VLANGroup(ChangeLoggedModel):
@ -831,10 +873,10 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
blank=True, blank=True,
null=True null=True
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=VLAN_STATUS_CHOICES, max_length=50,
default=1, choices=VLANStatusChoices,
verbose_name='Status' default=VLANStatusChoices.STATUS_ACTIVE
) )
role = models.ForeignKey( role = models.ForeignKey(
to='ipam.Role', to='ipam.Role',
@ -857,6 +899,12 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description'] csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
STATUS_CLASS_MAP = {
'active': 'primary',
'reserved': 'info',
'deprecated': 'danger',
}
class Meta: class Meta:
ordering = ['site', 'group', 'vid'] ordering = ['site', 'group', 'vid']
unique_together = [ unique_together = [
@ -899,7 +947,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
return None return None
def get_status_class(self): def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status] return self.STATUS_CLASS_MAP[self.status]
def get_members(self): def get_members(self):
# Return all interfaces assigned to this VLAN # Return all interfaces assigned to this VLAN
@ -932,8 +980,9 @@ class Service(ChangeLoggedModel, CustomFieldModel):
name = models.CharField( name = models.CharField(
max_length=30 max_length=30
) )
protocol = models.PositiveSmallIntegerField( protocol = models.CharField(
choices=IP_PROTOCOL_CHOICES max_length=50,
choices=ServiceProtocolChoices
) )
port = models.PositiveIntegerField( port = models.PositiveIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(65535)], validators=[MinValueValidator(1), MaxValueValidator(65535)],

View File

@ -5,7 +5,7 @@ from netaddr import IPNetwork
from rest_framework import status from rest_framework import status
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
from ipam.constants import IP_PROTOCOL_TCP, IP_PROTOCOL_UDP from ipam.choices import ServiceProtocolChoices
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from utilities.testing import APITestCase from utilities.testing import APITestCase
@ -996,13 +996,13 @@ class ServiceTest(APITestCase):
name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
) )
self.service1 = Service.objects.create( self.service1 = Service.objects.create(
device=self.device1, name='Test Service 1', protocol=IP_PROTOCOL_TCP, port=1 device=self.device1, name='Test Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1
) )
self.service1 = Service.objects.create( self.service1 = Service.objects.create(
device=self.device1, name='Test Service 2', protocol=IP_PROTOCOL_TCP, port=2 device=self.device1, name='Test Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2
) )
self.service1 = Service.objects.create( self.service1 = Service.objects.create(
device=self.device1, name='Test Service 3', protocol=IP_PROTOCOL_TCP, port=3 device=self.device1, name='Test Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3
) )
def test_get_service(self): def test_get_service(self):
@ -1024,7 +1024,7 @@ class ServiceTest(APITestCase):
data = { data = {
'device': self.device1.pk, 'device': self.device1.pk,
'name': 'Test Service 4', 'name': 'Test Service 4',
'protocol': IP_PROTOCOL_TCP, 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 4, 'port': 4,
} }
@ -1045,19 +1045,19 @@ class ServiceTest(APITestCase):
{ {
'device': self.device1.pk, 'device': self.device1.pk,
'name': 'Test Service 4', 'name': 'Test Service 4',
'protocol': IP_PROTOCOL_TCP, 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 4, 'port': 4,
}, },
{ {
'device': self.device1.pk, 'device': self.device1.pk,
'name': 'Test Service 5', 'name': 'Test Service 5',
'protocol': IP_PROTOCOL_TCP, 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 5, 'port': 5,
}, },
{ {
'device': self.device1.pk, 'device': self.device1.pk,
'name': 'Test Service 6', 'name': 'Test Service 6',
'protocol': IP_PROTOCOL_TCP, 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 6, 'port': 6,
}, },
] ]
@ -1076,7 +1076,7 @@ class ServiceTest(APITestCase):
data = { data = {
'device': self.device2.pk, 'device': self.device2.pk,
'name': 'Test Service X', 'name': 'Test Service X',
'protocol': IP_PROTOCOL_UDP, 'protocol': ServiceProtocolChoices.PROTOCOL_UDP,
'port': 99, 'port': 99,
} }

View File

@ -2,7 +2,7 @@ import netaddr
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from ipam.constants import IPADDRESS_ROLE_VIP from ipam.choices import IPAddressRoleChoices
from ipam.models import IPAddress, Prefix, VRF from ipam.models import IPAddress, Prefix, VRF
@ -61,5 +61,5 @@ class TestIPAddress(TestCase):
@override_settings(ENFORCE_GLOBAL_UNIQUE=True) @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
def test_duplicate_nonunique_role(self): def test_duplicate_nonunique_role(self):
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP) IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP) IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)

View File

@ -5,7 +5,7 @@ from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
from ipam.constants import IP_PROTOCOL_TCP from ipam.choices import ServiceProtocolChoices
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from utilities.testing import create_test_user from utilities.testing import create_test_user
@ -264,9 +264,9 @@ class ServiceTestCase(TestCase):
device.save() device.save()
Service.objects.bulk_create([ Service.objects.bulk_create([
Service(device=device, name='Service 1', protocol=IP_PROTOCOL_TCP, port=101), Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=101),
Service(device=device, name='Service 2', protocol=IP_PROTOCOL_TCP, port=102), Service(device=device, name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=102),
Service(device=device, name='Service 3', protocol=IP_PROTOCOL_TCP, port=103), Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=103),
]) ])
def test_service_list(self): def test_service_list(self):

View File

@ -14,7 +14,7 @@ from utilities.views import (
) )
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from . import filters, forms, tables from . import filters, forms, tables
from .constants import * from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
@ -217,13 +217,13 @@ class RIRListView(PermissionRequiredMixin, ObjectListView):
# Find all consumed space for each prefix status (we ignore containers for this purpose). # Find all consumed space for each prefix status (we ignore containers for this purpose).
active_prefixes = netaddr.cidr_merge( active_prefixes = netaddr.cidr_merge(
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)] [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_ACTIVE)]
) )
reserved_prefixes = netaddr.cidr_merge( reserved_prefixes = netaddr.cidr_merge(
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)] [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_RESERVED)]
) )
deprecated_prefixes = netaddr.cidr_merge( deprecated_prefixes = netaddr.cidr_merge(
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)] [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_DEPRECATED)]
) )
# Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix. # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
@ -665,8 +665,8 @@ class IPAddressView(PermissionRequiredMixin, View):
'nat_inside', 'interface__device' 'nat_inside', 'interface__device'
) )
# Exclude anycast IPs if this IP is anycast # Exclude anycast IPs if this IP is anycast
if ipaddress.role == IPADDRESS_ROLE_ANYCAST: if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST) duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False) duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
# Related IP table # Related IP table

View File

@ -13,6 +13,7 @@ from rest_framework.response import Response
from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.serializers import Field, ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet
from utilities.choices import ChoiceSet
from .utils import dict_to_filter_params, dynamic_import from .utils import dict_to_filter_params, dynamic_import
@ -64,14 +65,17 @@ class ChoiceField(Field):
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}. Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.
""" """
def __init__(self, choices, **kwargs): def __init__(self, choices, **kwargs):
self.choiceset = choices
self._choices = dict() self._choices = dict()
# Unpack grouped choices
for k, v in choices: for k, v in choices:
# Unpack grouped choices
if type(v) in [list, tuple]: if type(v) in [list, tuple]:
for k2, v2 in v: for k2, v2 in v:
self._choices[k2] = v2 self._choices[k2] = v2
else: else:
self._choices[k] = v self._choices[k] = v
super().__init__(**kwargs) super().__init__(**kwargs)
def to_representation(self, obj): def to_representation(self, obj):
@ -81,6 +85,11 @@ class ChoiceField(Field):
('value', obj), ('value', obj),
('label', self._choices[obj]) ('label', self._choices[obj])
]) ])
# Include legacy numeric ID (where applicable)
if type(self.choiceset) is ChoiceSet and obj in self.choiceset.LEGACY_MAP:
data['id'] = self.choiceset.LEGACY_MAP.get(obj)
return data return data
def to_internal_value(self, data): def to_internal_value(self, data):
@ -104,6 +113,10 @@ class ChoiceField(Field):
try: try:
if data in self._choices: if data in self._choices:
return data return data
# Check if data is a legacy numeric ID
slug = self.choiceset.id_to_slug(data)
if slug is not None:
return slug
except TypeError: # Input is an unhashable type except TypeError: # Input is an unhashable type
pass pass

View File

@ -0,0 +1,36 @@
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
return getattr(cls, 'CHOICES', ())
def __iter__(cls):
choices = getattr(cls, 'CHOICES', ())
return iter(choices)
class ChoiceSet(metaclass=ChoiceSetMeta):
CHOICES = list()
LEGACY_MAP = dict()
@classmethod
def slug_to_id(cls, slug):
"""
Return the legacy integer value corresponding to a slug.
"""
return cls.LEGACY_MAP.get(slug)
@classmethod
def id_to_slug(cls, legacy_id):
"""
Return the slug value corresponding to a legacy integer value.
"""
if legacy_id in cls.LEGACY_MAP.values():
# Invert the legacy map to allow lookup by integer
legacy_map = dict([
(id, slug) for slug, id in cls.LEGACY_MAP.items()
])
return legacy_map.get(legacy_id)

View File

@ -5,7 +5,7 @@ from collections import OrderedDict
from django.core.serializers import serialize from django.core.serializers import serialize
from django.db.models import Count, OuterRef, Subquery from django.db.models import Count, OuterRef, Subquery
from dcim.constants import LENGTH_UNIT_CENTIMETER, LENGTH_UNIT_FOOT, LENGTH_UNIT_INCH, LENGTH_UNIT_METER from dcim.choices import CableLengthUnitChoices
def csv_format(data): def csv_format(data):
@ -165,12 +165,18 @@ def to_meters(length, unit):
length = int(length) length = int(length)
if length < 0: if length < 0:
raise ValueError("Length must be a positive integer") raise ValueError("Length must be a positive integer")
if unit == LENGTH_UNIT_METER:
valid_units = [u[0] for u in CableLengthUnitChoices]
if unit not in valid_units:
raise ValueError(
"Unknown unit {}. Must be one of the following: {}".format(unit, ', '.join(valid_units))
)
if unit == CableLengthUnitChoices.UNIT_METER:
return length return length
if unit == LENGTH_UNIT_CENTIMETER: if unit == CableLengthUnitChoices.UNIT_CENTIMETER:
return length / 100 return length / 100
if unit == LENGTH_UNIT_FOOT: if unit == CableLengthUnitChoices.UNIT_FOOT:
return length * 0.3048 return length * 0.3048
if unit == LENGTH_UNIT_INCH: if unit == CableLengthUnitChoices.UNIT_INCH:
return length * 0.3048 * 12 return length * 0.3048 * 12
raise ValueError("Unknown unit {}. Must be 'm', 'cm', 'ft', or 'in'.".format(unit))

View File

@ -3,14 +3,14 @@ from rest_framework import serializers
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
from dcim.constants import IFACE_TYPE_CHOICES, IFACE_TYPE_VIRTUAL, IFACE_MODE_CHOICES from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
from dcim.models import Interface from dcim.models import Interface
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
from ipam.models import VLAN from ipam.models import VLAN
from tenancy.api.nested_serializers import NestedTenantSerializer from tenancy.api.nested_serializers import NestedTenantSerializer
from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer
from virtualization.constants import VM_STATUS_CHOICES from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
from .nested_serializers import * from .nested_serializers import *
@ -57,7 +57,7 @@ class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
# #
class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer):
status = ChoiceField(choices=VM_STATUS_CHOICES, required=False) status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
site = NestedSiteSerializer(read_only=True) site = NestedSiteSerializer(read_only=True)
cluster = NestedClusterSerializer() cluster = NestedClusterSerializer()
role = NestedDeviceRoleSerializer(required=False, allow_null=True) role = NestedDeviceRoleSerializer(required=False, allow_null=True)
@ -98,8 +98,8 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
virtual_machine = NestedVirtualMachineSerializer() virtual_machine = NestedVirtualMachineSerializer()
type = ChoiceField(choices=IFACE_TYPE_CHOICES, default=IFACE_TYPE_VIRTUAL, required=False) type = ChoiceField(choices=InterfaceTypeChoices, default=InterfaceTypeChoices.TYPE_VIRTUAL, required=False)
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True) mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_null=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField( tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),

View File

@ -0,0 +1,24 @@
from utilities.choices import ChoiceSet
#
# VirtualMachines
#
class VirtualMachineStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_OFFLINE = 'offline'
STATUS_STAGED = 'staged'
CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_OFFLINE, 'Offline'),
(STATUS_STAGED, 'Staged'),
)
LEGACY_MAP = {
STATUS_OFFLINE: 0,
STATUS_ACTIVE: 1,
STATUS_STAGED: 3,
}

View File

@ -1,15 +0,0 @@
from dcim.constants import DEVICE_STATUS_ACTIVE, DEVICE_STATUS_OFFLINE, DEVICE_STATUS_STAGED
# VirtualMachine statuses (replicated from Device statuses)
VM_STATUS_CHOICES = [
[DEVICE_STATUS_ACTIVE, 'Active'],
[DEVICE_STATUS_OFFLINE, 'Offline'],
[DEVICE_STATUS_STAGED, 'Staged'],
]
# Bootstrap CSS classes for VirtualMachine statuses
VM_STATUS_CLASSES = {
0: 'warning',
1: 'success',
3: 'primary',
}

View File

@ -10,7 +10,7 @@ from tenancy.models import Tenant
from utilities.filters import ( from utilities.filters import (
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter, MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
) )
from .constants import * from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -96,7 +96,7 @@ class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdate
label='Search', label='Search',
) )
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=VM_STATUS_CHOICES, choices=VirtualMachineStatusChoices,
null_value=None null_value=None
) )
cluster_group_id = django_filters.ModelMultipleChoiceFilter( cluster_group_id = django_filters.ModelMultipleChoiceFilter(

View File

@ -2,7 +2,7 @@ from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from taggit.forms import TagField from taggit.forms import TagField
from dcim.constants import IFACE_TYPE_VIRTUAL, IFACE_MODE_ACCESS, IFACE_MODE_TAGGED_ALL, IFACE_MODE_CHOICES from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
from dcim.forms import INTERFACE_MODE_HELP_TEXT from dcim.forms import INTERFACE_MODE_HELP_TEXT
from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
@ -15,11 +15,11 @@ from utilities.forms import (
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
SmallTextarea, StaticSelect2, StaticSelect2Multiple SmallTextarea, StaticSelect2, StaticSelect2Multiple
) )
from .constants import * from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
VIFACE_TYPE_CHOICES = ( VIFACE_TYPE_CHOICES = (
(IFACE_TYPE_VIRTUAL, 'Virtual'), (InterfaceTypeChoices.TYPE_VIRTUAL, 'Virtual'),
) )
@ -428,7 +428,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class VirtualMachineCSVForm(forms.ModelForm): class VirtualMachineCSVForm(forms.ModelForm):
status = CSVChoiceField( status = CSVChoiceField(
choices=VM_STATUS_CHOICES, choices=VirtualMachineStatusChoices,
required=False, required=False,
help_text='Operational status of device' help_text='Operational status of device'
) )
@ -481,7 +481,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
status = forms.ChoiceField( status = forms.ChoiceField(
choices=add_blank_choice(VM_STATUS_CHOICES), choices=add_blank_choice(VirtualMachineStatusChoices),
required=False, required=False,
initial='', initial='',
widget=StaticSelect2(), widget=StaticSelect2(),
@ -612,7 +612,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
) )
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(
choices=VM_STATUS_CHOICES, choices=VirtualMachineStatusChoices,
required=False, required=False,
widget=StaticSelect2Multiple() widget=StaticSelect2Multiple()
) )
@ -717,13 +717,13 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
tagged_vlans = self.cleaned_data['tagged_vlans'] tagged_vlans = self.cleaned_data['tagged_vlans']
# Untagged interfaces cannot be assigned tagged VLANs # Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans: if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
raise forms.ValidationError({ raise forms.ValidationError({
'mode': "An access interface cannot have tagged VLANs assigned." 'mode': "An access interface cannot have tagged VLANs assigned."
}) })
# Remove all tagged VLAN assignments from "tagged all" interfaces # Remove all tagged VLAN assignments from "tagged all" interfaces
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL: elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
self.cleaned_data['tagged_vlans'] = [] self.cleaned_data['tagged_vlans'] = []
@ -733,7 +733,7 @@ class InterfaceCreateForm(ComponentForm):
) )
type = forms.ChoiceField( type = forms.ChoiceField(
choices=VIFACE_TYPE_CHOICES, choices=VIFACE_TYPE_CHOICES,
initial=IFACE_TYPE_VIRTUAL, initial=InterfaceTypeChoices.TYPE_VIRTUAL,
widget=forms.HiddenInput() widget=forms.HiddenInput()
) )
enabled = forms.BooleanField( enabled = forms.BooleanField(
@ -754,7 +754,7 @@ class InterfaceCreateForm(ComponentForm):
required=False required=False
) )
mode = forms.ChoiceField( mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES), choices=add_blank_choice(InterfaceModeChoices),
required=False, required=False,
widget=StaticSelect2(), widget=StaticSelect2(),
) )
@ -839,7 +839,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
required=False required=False
) )
mode = forms.ChoiceField( mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES), choices=add_blank_choice(InterfaceModeChoices),
required=False, required=False,
widget=StaticSelect2() widget=StaticSelect2()
) )
@ -918,7 +918,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm): class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=VIFACE_TYPE_CHOICES, choices=VIFACE_TYPE_CHOICES,
initial=IFACE_TYPE_VIRTUAL, initial=InterfaceTypeChoices.TYPE_VIRTUAL,
widget=forms.HiddenInput() widget=forms.HiddenInput()
) )
enabled = forms.BooleanField( enabled = forms.BooleanField(

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
VIRTUALMACHINE_STATUS_CHOICES = (
(0, 'offline'),
(1, 'active'),
(3, 'staged'),
)
def virtualmachine_status_to_slug(apps, schema_editor):
VirtualMachine = apps.get_model('virtualization', 'VirtualMachine')
for id, slug in VIRTUALMACHINE_STATUS_CHOICES:
VirtualMachine.objects.filter(status=str(id)).update(status=slug)
class Migration(migrations.Migration):
atomic = False
dependencies = [
('virtualization', '0010_cluster_add_tenant'),
]
operations = [
# VirtualMachine.status
migrations.AlterField(
model_name='virtualmachine',
name='status',
field=models.CharField(default='active', max_length=50),
),
migrations.RunPython(
code=virtualmachine_status_to_slug
),
]

View File

@ -8,7 +8,7 @@ from taggit.managers import TaggableManager
from dcim.models import Device from dcim.models import Device
from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from .constants import * from .choices import *
# #
@ -193,9 +193,10 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
max_length=64, max_length=64,
unique=True unique=True
) )
status = models.PositiveSmallIntegerField( status = models.CharField(
choices=VM_STATUS_CHOICES, max_length=50,
default=DEVICE_STATUS_ACTIVE, choices=VirtualMachineStatusChoices,
default=VirtualMachineStatusChoices.STATUS_ACTIVE,
verbose_name='Status' verbose_name='Status'
) )
role = models.ForeignKey( role = models.ForeignKey(
@ -252,6 +253,12 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
] ]
STATUS_CLASS_MAP = {
'active': 'success',
'offline': 'warning',
'staged': 'primary',
}
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@ -294,7 +301,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
) )
def get_status_class(self): def get_status_class(self):
return VM_STATUS_CLASSES[self.status] return self.STATUS_CLASS_MAP.get(self.status)
@property @property
def primary_ip(self): def primary_ip(self):

View File

@ -2,7 +2,7 @@ from django.urls import reverse
from netaddr import IPNetwork from netaddr import IPNetwork
from rest_framework import status from rest_framework import status
from dcim.constants import IFACE_TYPE_VIRTUAL, IFACE_MODE_TAGGED from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
from dcim.models import Interface from dcim.models import Interface
from ipam.models import IPAddress, VLAN from ipam.models import IPAddress, VLAN
from utilities.testing import APITestCase from utilities.testing import APITestCase
@ -489,17 +489,17 @@ class InterfaceTest(APITestCase):
self.interface1 = Interface.objects.create( self.interface1 = Interface.objects.create(
virtual_machine=self.virtualmachine, virtual_machine=self.virtualmachine,
name='Test Interface 1', name='Test Interface 1',
type=IFACE_TYPE_VIRTUAL type=InterfaceTypeChoices.TYPE_VIRTUAL
) )
self.interface2 = Interface.objects.create( self.interface2 = Interface.objects.create(
virtual_machine=self.virtualmachine, virtual_machine=self.virtualmachine,
name='Test Interface 2', name='Test Interface 2',
type=IFACE_TYPE_VIRTUAL type=InterfaceTypeChoices.TYPE_VIRTUAL
) )
self.interface3 = Interface.objects.create( self.interface3 = Interface.objects.create(
virtual_machine=self.virtualmachine, virtual_machine=self.virtualmachine,
name='Test Interface 3', name='Test Interface 3',
type=IFACE_TYPE_VIRTUAL type=InterfaceTypeChoices.TYPE_VIRTUAL
) )
self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1) self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1)
@ -551,7 +551,7 @@ class InterfaceTest(APITestCase):
data = { data = {
'virtual_machine': self.virtualmachine.pk, 'virtual_machine': self.virtualmachine.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan3.id, 'untagged_vlan': self.vlan3.id,
'tagged_vlans': [self.vlan1.id, self.vlan2.id], 'tagged_vlans': [self.vlan1.id, self.vlan2.id],
} }
@ -598,21 +598,21 @@ class InterfaceTest(APITestCase):
{ {
'virtual_machine': self.virtualmachine.pk, 'virtual_machine': self.virtualmachine.pk,
'name': 'Test Interface 4', 'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },
{ {
'virtual_machine': self.virtualmachine.pk, 'virtual_machine': self.virtualmachine.pk,
'name': 'Test Interface 5', 'name': 'Test Interface 5',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },
{ {
'virtual_machine': self.virtualmachine.pk, 'virtual_machine': self.virtualmachine.pk,
'name': 'Test Interface 6', 'name': 'Test Interface 6',
'mode': IFACE_MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id, 'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id], 'tagged_vlans': [self.vlan1.id],
}, },