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

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 tenancy.filtersets import TenancyFilterSet
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
from .constants import *
from .choices import *
from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -84,7 +84,7 @@ class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilter
label='Circuit type (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=CIRCUIT_STATUS_CHOICES,
choices=CircuitStatusChoices,
null_value=None
)
site_id = django_filters.ModelMultipleChoiceFilter(

View File

@ -9,7 +9,7 @@ from utilities.forms import (
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
)
from .constants import *
from .choices import CircuitStatusChoices
from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -194,7 +194,7 @@ class CircuitCSVForm(forms.ModelForm):
}
)
status = CSVChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
choices=CircuitStatusChoices,
required=False,
help_text='Operational status'
)
@ -235,7 +235,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
)
)
status = forms.ChoiceField(
choices=add_blank_choice(CIRCUIT_STATUS_CHOICES),
choices=add_blank_choice(CircuitStatusChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -292,7 +292,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
)
)
status = forms.MultipleChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
choices=CircuitStatusChoices,
required=False,
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 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.models import CableTermination
from extras.models import CustomFieldModel, ObjectChange, TaggedItem
from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object
from .constants import *
from .choices import *
class Provider(ChangeLoggedModel, CustomFieldModel):
@ -132,9 +132,10 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
on_delete=models.PROTECT,
related_name='circuits'
)
status = models.PositiveSmallIntegerField(
choices=CIRCUIT_STATUS_CHOICES,
default=CIRCUIT_STATUS_ACTIVE
status = models.CharField(
max_length=50,
choices=CircuitStatusChoices,
default=CircuitStatusChoices.STATUS_ACTIVE
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
@ -171,6 +172,15 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]
STATUS_CLASS_MAP = {
CircuitStatusChoices.STATUS_DEPROVISIONING: 'warning',
CircuitStatusChoices.STATUS_ACTIVE: 'success',
CircuitStatusChoices.STATUS_PLANNED: 'info',
CircuitStatusChoices.STATUS_PROVISIONING: 'primary',
CircuitStatusChoices.STATUS_OFFLINE: 'danger',
CircuitStatusChoices.STATUS_DECOMMISSIONED: 'default',
}
class Meta:
ordering = ['provider', 'cid']
unique_together = ['provider', 'cid']
@ -195,7 +205,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
)
def get_status_class(self):
return STATUS_CLASSES[self.status]
return self.STATUS_CLASS_MAP.get(self.status)
def _get_termination(self, side):
for ct in self.terminations.all():
@ -220,7 +230,7 @@ class CircuitTermination(CableTermination):
)
term_side = models.CharField(
max_length=1,
choices=TERM_SIDE_CHOICES,
choices=CircuitTerminationSideChoices,
verbose_name='Termination'
)
site = models.ForeignKey(

View File

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

View File

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

View File

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

View File

@ -1,14 +1,187 @@
from .constants import *
from utilities.choices import ChoiceSet
#
# Console port type values
# Sites
#
class ConsolePortTypes:
"""
ConsolePort/ConsoleServerPort.type slugs
"""
class SiteStatusChoices(ChoiceSet):
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_DB25 = 'db-25'
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
# IEC 60320
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
# IEC 60320
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:
"""
Interface.type slugs
"""
class InterfaceTypeChoices(ChoiceSet):
# Virtual
TYPE_VIRTUAL = 'virtual'
TYPE_LAG = 'lag'
@ -315,7 +507,7 @@ class InterfaceTypes:
# Other
TYPE_OTHER = 'other'
TYPE_CHOICES = (
CHOICES = (
(
'Virtual interfaces',
(
@ -444,93 +636,105 @@ class InterfaceTypes:
),
)
@classmethod
def slug_to_integer(cls, slug):
"""
Provide backward-compatible mapping of the type slug to integer.
"""
return {
# Slug: integer
cls.TYPE_VIRTUAL: IFACE_TYPE_VIRTUAL,
cls.TYPE_LAG: IFACE_TYPE_LAG,
cls.TYPE_100ME_FIXED: IFACE_TYPE_100ME_FIXED,
cls.TYPE_1GE_FIXED: IFACE_TYPE_1GE_FIXED,
cls.TYPE_1GE_GBIC: IFACE_TYPE_1GE_GBIC,
cls.TYPE_1GE_SFP: IFACE_TYPE_1GE_SFP,
cls.TYPE_2GE_FIXED: IFACE_TYPE_2GE_FIXED,
cls.TYPE_5GE_FIXED: IFACE_TYPE_5GE_FIXED,
cls.TYPE_10GE_FIXED: IFACE_TYPE_10GE_FIXED,
cls.TYPE_10GE_CX4: IFACE_TYPE_10GE_CX4,
cls.TYPE_10GE_SFP_PLUS: IFACE_TYPE_10GE_SFP_PLUS,
cls.TYPE_10GE_XFP: IFACE_TYPE_10GE_XFP,
cls.TYPE_10GE_XENPAK: IFACE_TYPE_10GE_XENPAK,
cls.TYPE_10GE_X2: IFACE_TYPE_10GE_X2,
cls.TYPE_25GE_SFP28: IFACE_TYPE_25GE_SFP28,
cls.TYPE_40GE_QSFP_PLUS: IFACE_TYPE_40GE_QSFP_PLUS,
cls.TYPE_50GE_QSFP28: IFACE_TYPE_50GE_QSFP28,
cls.TYPE_100GE_CFP: IFACE_TYPE_100GE_CFP,
cls.TYPE_100GE_CFP2: IFACE_TYPE_100GE_CFP2,
cls.TYPE_100GE_CFP4: IFACE_TYPE_100GE_CFP4,
cls.TYPE_100GE_CPAK: IFACE_TYPE_100GE_CPAK,
cls.TYPE_100GE_QSFP28: IFACE_TYPE_100GE_QSFP28,
cls.TYPE_200GE_CFP2: IFACE_TYPE_200GE_CFP2,
cls.TYPE_200GE_QSFP56: IFACE_TYPE_200GE_QSFP56,
cls.TYPE_400GE_QSFP_DD: IFACE_TYPE_400GE_QSFP_DD,
cls.TYPE_80211A: IFACE_TYPE_80211A,
cls.TYPE_80211G: IFACE_TYPE_80211G,
cls.TYPE_80211N: IFACE_TYPE_80211N,
cls.TYPE_80211AC: IFACE_TYPE_80211AC,
cls.TYPE_80211AD: IFACE_TYPE_80211AD,
cls.TYPE_GSM: IFACE_TYPE_GSM,
cls.TYPE_CDMA: IFACE_TYPE_CDMA,
cls.TYPE_LTE: IFACE_TYPE_LTE,
cls.TYPE_SONET_OC3: IFACE_TYPE_SONET_OC3,
cls.TYPE_SONET_OC12: IFACE_TYPE_SONET_OC12,
cls.TYPE_SONET_OC48: IFACE_TYPE_SONET_OC48,
cls.TYPE_SONET_OC192: IFACE_TYPE_SONET_OC192,
cls.TYPE_SONET_OC768: IFACE_TYPE_SONET_OC768,
cls.TYPE_SONET_OC1920: IFACE_TYPE_SONET_OC1920,
cls.TYPE_SONET_OC3840: IFACE_TYPE_SONET_OC3840,
cls.TYPE_1GFC_SFP: IFACE_TYPE_1GFC_SFP,
cls.TYPE_2GFC_SFP: IFACE_TYPE_2GFC_SFP,
cls.TYPE_4GFC_SFP: IFACE_TYPE_4GFC_SFP,
cls.TYPE_8GFC_SFP_PLUS: IFACE_TYPE_8GFC_SFP_PLUS,
cls.TYPE_16GFC_SFP_PLUS: IFACE_TYPE_16GFC_SFP_PLUS,
cls.TYPE_32GFC_SFP28: IFACE_TYPE_32GFC_SFP28,
cls.TYPE_128GFC_QSFP28: IFACE_TYPE_128GFC_QSFP28,
cls.TYPE_INFINIBAND_SDR: IFACE_TYPE_INFINIBAND_SDR,
cls.TYPE_INFINIBAND_DDR: IFACE_TYPE_INFINIBAND_DDR,
cls.TYPE_INFINIBAND_QDR: IFACE_TYPE_INFINIBAND_QDR,
cls.TYPE_INFINIBAND_FDR10: IFACE_TYPE_INFINIBAND_FDR10,
cls.TYPE_INFINIBAND_FDR: IFACE_TYPE_INFINIBAND_FDR,
cls.TYPE_INFINIBAND_EDR: IFACE_TYPE_INFINIBAND_EDR,
cls.TYPE_INFINIBAND_HDR: IFACE_TYPE_INFINIBAND_HDR,
cls.TYPE_INFINIBAND_NDR: IFACE_TYPE_INFINIBAND_NDR,
cls.TYPE_INFINIBAND_XDR: IFACE_TYPE_INFINIBAND_XDR,
cls.TYPE_T1: IFACE_TYPE_T1,
cls.TYPE_E1: IFACE_TYPE_E1,
cls.TYPE_T3: IFACE_TYPE_T3,
cls.TYPE_E3: IFACE_TYPE_E3,
cls.TYPE_STACKWISE: IFACE_TYPE_STACKWISE,
cls.TYPE_STACKWISE_PLUS: IFACE_TYPE_STACKWISE_PLUS,
cls.TYPE_FLEXSTACK: IFACE_TYPE_FLEXSTACK,
cls.TYPE_FLEXSTACK_PLUS: IFACE_TYPE_FLEXSTACK_PLUS,
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,
cls.TYPE_SUMMITSTACK512: IFACE_TYPE_SUMMITSTACK512,
}.get(slug)
LEGACY_MAP = {
TYPE_VIRTUAL: 0,
TYPE_LAG: 200,
TYPE_100ME_FIXED: 800,
TYPE_1GE_FIXED: 1000,
TYPE_1GE_GBIC: 1050,
TYPE_1GE_SFP: 1100,
TYPE_2GE_FIXED: 1120,
TYPE_5GE_FIXED: 1130,
TYPE_10GE_FIXED: 1150,
TYPE_10GE_CX4: 1170,
TYPE_10GE_SFP_PLUS: 1200,
TYPE_10GE_XFP: 1300,
TYPE_10GE_XENPAK: 1310,
TYPE_10GE_X2: 1320,
TYPE_25GE_SFP28: 1350,
TYPE_40GE_QSFP_PLUS: 1400,
TYPE_50GE_QSFP28: 1420,
TYPE_100GE_CFP: 1500,
TYPE_100GE_CFP2: 1510,
TYPE_100GE_CFP4: 1520,
TYPE_100GE_CPAK: 1550,
TYPE_100GE_QSFP28: 1600,
TYPE_200GE_CFP2: 1650,
TYPE_200GE_QSFP56: 1700,
TYPE_400GE_QSFP_DD: 1750,
TYPE_400GE_OSFP: 1800,
TYPE_80211A: 2600,
TYPE_80211G: 2610,
TYPE_80211N: 2620,
TYPE_80211AC: 2630,
TYPE_80211AD: 2640,
TYPE_GSM: 2810,
TYPE_CDMA: 2820,
TYPE_LTE: 2830,
TYPE_SONET_OC3: 6100,
TYPE_SONET_OC12: 6200,
TYPE_SONET_OC48: 6300,
TYPE_SONET_OC192: 6400,
TYPE_SONET_OC768: 6500,
TYPE_SONET_OC1920: 6600,
TYPE_SONET_OC3840: 6700,
TYPE_1GFC_SFP: 3010,
TYPE_2GFC_SFP: 3020,
TYPE_4GFC_SFP: 3040,
TYPE_8GFC_SFP_PLUS: 3080,
TYPE_16GFC_SFP_PLUS: 3160,
TYPE_32GFC_SFP28: 3320,
TYPE_128GFC_QSFP28: 3400,
TYPE_INFINIBAND_SDR: 7010,
TYPE_INFINIBAND_DDR: 7020,
TYPE_INFINIBAND_QDR: 7030,
TYPE_INFINIBAND_FDR10: 7040,
TYPE_INFINIBAND_FDR: 7050,
TYPE_INFINIBAND_EDR: 7060,
TYPE_INFINIBAND_HDR: 7070,
TYPE_INFINIBAND_NDR: 7080,
TYPE_INFINIBAND_XDR: 7090,
TYPE_T1: 4000,
TYPE_E1: 4010,
TYPE_T3: 4040,
TYPE_E3: 4050,
TYPE_STACKWISE: 5000,
TYPE_STACKWISE_PLUS: 5050,
TYPE_FLEXSTACK: 5100,
TYPE_FLEXSTACK_PLUS: 5150,
TYPE_JUNIPER_VCP: 5200,
TYPE_SUMMITSTACK: 5300,
TYPE_SUMMITSTACK128: 5310,
TYPE_SUMMITSTACK256: 5320,
TYPE_SUMMITSTACK512: 5330,
}
class InterfaceModeChoices(ChoiceSet):
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:
"""
FrontPort/RearPort.type slugs
"""
class PortTypeChoices(ChoiceSet):
TYPE_8P8C = '8p8c'
TYPE_110_PUNCH = '110-punch'
TYPE_BNC = 'bnc'
@ -545,7 +749,7 @@ class PortTypes:
TYPE_LSH = 'lsh'
TYPE_LSH_APC = 'lsh-apc'
TYPE_CHOICES = (
CHOICES = (
(
'Copper',
(
@ -571,24 +775,193 @@ class PortTypes:
)
)
@classmethod
def slug_to_integer(cls, slug):
"""
Provide backward-compatible mapping of the type slug to integer.
"""
return {
# Slug: integer
cls.TYPE_8P8C: PORT_TYPE_8P8C,
cls.TYPE_110_PUNCH: PORT_TYPE_8P8C,
cls.TYPE_BNC: PORT_TYPE_BNC,
cls.TYPE_ST: PORT_TYPE_ST,
cls.TYPE_SC: PORT_TYPE_SC,
cls.TYPE_SC_APC: PORT_TYPE_SC_APC,
cls.TYPE_FC: PORT_TYPE_FC,
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,
cls.TYPE_LSH_APC: PORT_TYPE_LSH_APC,
}.get(slug)
LEGACY_MAP = {
TYPE_8P8C: 1000,
TYPE_110_PUNCH: 1100,
TYPE_BNC: 1200,
TYPE_ST: 2000,
TYPE_SC: 2100,
TYPE_SC_APC: 2110,
TYPE_FC: 2200,
TYPE_LC: 2300,
TYPE_LC_APC: 2310,
TYPE_MTRJ: 2400,
TYPE_MPO: 2500,
TYPE_LSH: 2600,
TYPE_LSH_APC: 2610,
}
#
# Cables
#
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
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'),
)
from .choices import InterfaceTypeChoices
# 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 = [
IFACE_TYPE_VIRTUAL,
IFACE_TYPE_LAG,
InterfaceTypeChoices.TYPE_VIRTUAL,
InterfaceTypeChoices.TYPE_LAG,
]
WIRELESS_IFACE_TYPES = [
IFACE_TYPE_80211A,
IFACE_TYPE_80211G,
IFACE_TYPE_80211N,
IFACE_TYPE_80211AC,
IFACE_TYPE_80211AD,
InterfaceTypeChoices.TYPE_80211A,
InterfaceTypeChoices.TYPE_80211G,
InterfaceTypeChoices.TYPE_80211N,
InterfaceTypeChoices.TYPE_80211AC,
InterfaceTypeChoices.TYPE_80211AD,
]
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
CONNECTION_STATUS_PLANNED = False
CONNECTION_STATUS_CONNECTED = True
@ -390,56 +34,6 @@ CABLE_TERMINATION_TYPES = [
'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 = {
# (API endpoint, human-friendly name)
'consoleport': ('console-ports', 'Console port'),
@ -461,57 +55,3 @@ COMPATIBLE_TERMINATION_TYPES = {
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
'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',
)
status = django_filters.MultipleChoiceFilter(
choices=SITE_STATUS_CHOICES,
choices=SiteStatusChoices,
null_value=None
)
region_id = TreeNodeMultipleChoiceFilter(
@ -147,7 +147,7 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
label='Group',
)
status = django_filters.MultipleChoiceFilter(
choices=RACK_STATUS_CHOICES,
choices=RackStatusChoices,
null_value=None
)
role_id = django_filters.ModelMultipleChoiceFilter(
@ -511,7 +511,7 @@ class DeviceFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilter
label='Device model (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=DEVICE_STATUS_CHOICES,
choices=DeviceStatusChoices,
null_value=None
)
is_full_depth = django_filters.BooleanFilter(
@ -663,7 +663,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
class ConsolePortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
null_value=None
)
cabled = django_filters.BooleanFilter(
@ -679,7 +679,7 @@ class ConsolePortFilter(DeviceComponentFilterSet):
class ConsoleServerPortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
null_value=None
)
cabled = django_filters.BooleanFilter(
@ -695,7 +695,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
class PowerPortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerPortTypes.CHOICES,
choices=PowerPortTypeChoices,
null_value=None
)
cabled = django_filters.BooleanFilter(
@ -711,7 +711,7 @@ class PowerPortFilter(DeviceComponentFilterSet):
class PowerOutletFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerOutletTypes.CHOICES,
choices=PowerOutletTypeChoices,
null_value=None
)
cabled = django_filters.BooleanFilter(
@ -789,7 +789,7 @@ class InterfaceFilter(django_filters.FilterSet):
label='Assigned VID'
)
type = django_filters.MultipleChoiceFilter(
choices=IFACE_TYPE_CHOICES,
choices=InterfaceTypeChoices,
null_value=None
)
@ -980,7 +980,7 @@ class CableFilter(django_filters.FilterSet):
label='Search',
)
type = django_filters.MultipleChoiceFilter(
choices=CABLE_TYPE_CHOICES
choices=CableTypeChoices
)
status = django_filters.MultipleChoiceFilter(
choices=CONNECTION_STATUS_CHOICES

View File

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

View File

@ -93,13 +93,13 @@ class InterfaceCommonForm:
tagged_vlans = self.cleaned_data['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({
'mode': "An access interface cannot have tagged VLANs assigned."
})
# 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'] = []
@ -250,7 +250,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class SiteCSVForm(forms.ModelForm):
status = CSVChoiceField(
choices=SITE_STATUS_CHOICES,
choices=SiteStatusChoices,
required=False,
help_text='Operational status'
)
@ -289,7 +289,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
widget=forms.MultipleHiddenInput
)
status = forms.ChoiceField(
choices=add_blank_choice(SITE_STATUS_CHOICES),
choices=add_blank_choice(SiteStatusChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -338,7 +338,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
label='Search'
)
status = forms.MultipleChoiceField(
choices=SITE_STATUS_CHOICES,
choices=SiteStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -500,7 +500,7 @@ class RackCSVForm(forms.ModelForm):
}
)
status = CSVChoiceField(
choices=RACK_STATUS_CHOICES,
choices=RackStatusChoices,
required=False,
help_text='Operational status'
)
@ -514,19 +514,16 @@ class RackCSVForm(forms.ModelForm):
}
)
type = CSVChoiceField(
choices=RACK_TYPE_CHOICES,
choices=RackTypeChoices,
required=False,
help_text='Rack type'
)
width = forms.ChoiceField(
choices=(
(RACK_WIDTH_19IN, '19'),
(RACK_WIDTH_23IN, '23'),
),
choices=RackWidthChoices,
help_text='Rail-to-rail width (in inches)'
)
outer_unit = CSVChoiceField(
choices=RACK_DIMENSION_UNIT_CHOICES,
choices=RackDimensionUnitChoices,
required=False,
help_text='Unit for outer dimensions'
)
@ -598,7 +595,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
)
)
status = forms.ChoiceField(
choices=add_blank_choice(RACK_STATUS_CHOICES),
choices=add_blank_choice(RackStatusChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -620,12 +617,12 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
required=False
)
type = forms.ChoiceField(
choices=add_blank_choice(RACK_TYPE_CHOICES),
choices=add_blank_choice(RackTypeChoices),
required=False,
widget=StaticSelect2()
)
width = forms.ChoiceField(
choices=add_blank_choice(RACK_WIDTH_CHOICES),
choices=add_blank_choice(RackWidthChoices),
required=False,
widget=StaticSelect2()
)
@ -647,7 +644,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
min_value=1
)
outer_unit = forms.ChoiceField(
choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES),
choices=add_blank_choice(RackDimensionUnitChoices),
required=False,
widget=StaticSelect2()
)
@ -692,7 +689,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
)
)
status = forms.MultipleChoiceField(
choices=RACK_STATUS_CHOICES,
choices=RackStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -909,12 +906,10 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
value_field="slug",
)
)
subdevice_role = forms.NullBooleanField(
subdevice_role = forms.MultipleChoiceField(
choices=add_blank_choice(SubdeviceRoleChoices),
required=False,
label='Subdevice role',
widget=StaticSelect2(
choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES)
)
widget=StaticSelect2Multiple()
)
console_ports = forms.NullBooleanField(
required=False,
@ -981,7 +976,7 @@ class ConsolePortTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
widget=StaticSelect2()
)
@ -1003,7 +998,7 @@ class ConsoleServerPortTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES),
choices=add_blank_choice(ConsolePortTypeChoices),
widget=StaticSelect2()
)
@ -1025,7 +1020,7 @@ class PowerPortTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(PowerPortTypes.CHOICES),
choices=add_blank_choice(PowerPortTypeChoices),
required=False
)
maximum_draw = forms.IntegerField(
@ -1067,7 +1062,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(PowerOutletTypes.CHOICES),
choices=add_blank_choice(PowerOutletTypeChoices),
required=False
)
power_port = forms.ModelChoiceField(
@ -1075,7 +1070,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
required=False
)
feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False,
widget=StaticSelect2()
)
@ -1108,7 +1103,7 @@ class InterfaceTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES,
choices=InterfaceTypeChoices,
widget=StaticSelect2()
)
mgmt_only = forms.BooleanField(
@ -1123,7 +1118,7 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(IFACE_TYPE_CHOICES),
choices=add_blank_choice(InterfaceTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -1165,7 +1160,7 @@ class FrontPortTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
widget=StaticSelect2()
)
rear_port_set = forms.MultipleChoiceField(
@ -1235,7 +1230,7 @@ class RearPortTemplateCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
widget=StaticSelect2(),
)
positions = forms.IntegerField(
@ -1334,7 +1329,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField(
choices=InterfaceTypes.TYPE_CHOICES
choices=InterfaceTypeChoices.CHOICES
)
class Meta:
@ -1343,15 +1338,10 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
'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):
type = forms.ChoiceField(
choices=PortTypes.TYPE_CHOICES
choices=PortTypeChoices.CHOICES
)
rear_port = forms.ModelChoiceField(
queryset=RearPortTemplate.objects.all(),
@ -1365,15 +1355,10 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
'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):
type = forms.ChoiceField(
choices=PortTypes.TYPE_CHOICES
choices=PortTypeChoices.CHOICES
)
class Meta:
@ -1382,11 +1367,6 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
'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):
@ -1702,7 +1682,7 @@ class BaseDeviceCSVForm(forms.ModelForm):
}
)
status = CSVChoiceField(
choices=DEVICE_STATUS_CHOICES,
choices=DeviceStatusChoices,
help_text='Operational status'
)
@ -1746,7 +1726,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
help_text='Name of parent rack'
)
face = CSVChoiceField(
choices=RACK_FACE_CHOICES,
choices=DeviceFaceChoices,
required=False,
help_text='Mounted rack face'
)
@ -1870,7 +1850,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
)
)
status = forms.ChoiceField(
choices=add_blank_choice(DEVICE_STATUS_CHOICES),
choices=add_blank_choice(DeviceStatusChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -1981,7 +1961,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
)
)
status = forms.MultipleChoiceField(
choices=DEVICE_STATUS_CHOICES,
choices=DeviceStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -2063,7 +2043,7 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES,
choices=InterfaceTypeChoices,
widget=StaticSelect2()
)
enabled = forms.BooleanField(
@ -2115,7 +2095,7 @@ class ConsolePortCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES),
choices=add_blank_choice(ConsolePortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2172,7 +2152,7 @@ class ConsoleServerPortCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES),
choices=add_blank_choice(ConsolePortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2191,7 +2171,7 @@ class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditF
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(ConsolePortTypes.CHOICES),
choices=add_blank_choice(ConsolePortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2264,7 +2244,7 @@ class PowerPortCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(PowerPortTypes.CHOICES),
choices=add_blank_choice(PowerPortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2344,7 +2324,7 @@ class PowerOutletCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=add_blank_choice(PowerOutletTypes.CHOICES),
choices=add_blank_choice(PowerOutletTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2353,7 +2333,7 @@ class PowerOutletCreateForm(ComponentForm):
required=False
)
feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False
)
description = forms.CharField(
@ -2391,7 +2371,7 @@ class PowerOutletCSVForm(forms.ModelForm):
}
)
feed_leg = CSVChoiceField(
choices=POWERFEED_LEG_CHOICES,
choices=PowerOutletFeedLegChoices,
required=False,
)
@ -2428,11 +2408,11 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=PowerOutletTypes.CHOICES,
choices=PowerOutletTypeChoices,
required=False
)
feed_leg = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
choices=add_blank_choice(PowerOutletFeedLegChoices),
required=False,
)
power_port = forms.ModelChoiceField(
@ -2529,12 +2509,14 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
if self.is_bound:
device = Device.objects.get(pk=self.data['device'])
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:
device = self.instance.device
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
@ -2573,7 +2555,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
label='Name'
)
type = forms.ChoiceField(
choices=IFACE_TYPE_CHOICES,
choices=InterfaceTypeChoices,
widget=StaticSelect2(),
)
enabled = forms.BooleanField(
@ -2605,7 +2587,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES),
choices=add_blank_choice(InterfaceModeChoices),
required=False,
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)
if self.parent is not None:
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:
self.fields['lag'].queryset = Interface.objects.none()
@ -2707,10 +2690,10 @@ class InterfaceCSVForm(forms.ModelForm):
}
)
type = CSVChoiceField(
choices=IFACE_TYPE_CHOICES,
choices=InterfaceTypeChoices,
)
mode = CSVChoiceField(
choices=IFACE_MODE_CHOICES,
choices=InterfaceModeChoices,
required=False,
)
@ -2732,7 +2715,7 @@ class InterfaceCSVForm(forms.ModelForm):
if device:
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:
self.fields['lag'].queryset = Interface.objects.none()
@ -2751,7 +2734,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(IFACE_TYPE_CHOICES),
choices=add_blank_choice(InterfaceTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -2785,7 +2768,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES),
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect2()
)
@ -2821,7 +2804,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
if device is not None:
self.fields['lag'].queryset = Interface.objects.filter(
device__in=[device, device.get_vc_master()],
type=IFACE_TYPE_LAG
type=InterfaceTypeChoices.TYPE_LAG
)
else:
self.fields['lag'].choices = []
@ -2911,7 +2894,7 @@ class FrontPortCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
widget=StaticSelect2(),
)
rear_port_set = forms.MultipleChoiceField(
@ -2983,7 +2966,7 @@ class FrontPortCSVForm(forms.ModelForm):
}
)
type = CSVChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
)
class Meta:
@ -3019,7 +3002,7 @@ class FrontPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(PORT_TYPE_CHOICES),
choices=add_blank_choice(PortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -3077,7 +3060,7 @@ class RearPortCreateForm(ComponentForm):
label='Name'
)
type = forms.ChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
widget=StaticSelect2(),
)
positions = forms.IntegerField(
@ -3101,7 +3084,7 @@ class RearPortCSVForm(forms.ModelForm):
}
)
type = CSVChoiceField(
choices=PORT_TYPE_CHOICES,
choices=PortTypeChoices,
)
class Meta:
@ -3115,7 +3098,7 @@ class RearPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(PORT_TYPE_CHOICES),
choices=add_blank_choice(PortTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -3449,12 +3432,12 @@ class CableCSVForm(forms.ModelForm):
help_text='Connection status'
)
type = CSVChoiceField(
choices=CABLE_TYPE_CHOICES,
choices=CableTypeChoices,
required=False,
help_text='Cable type'
)
length_unit = CSVChoiceField(
choices=CABLE_LENGTH_UNIT_CHOICES,
choices=CableLengthUnitChoices,
required=False,
help_text='Length unit'
)
@ -3534,7 +3517,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
widget=forms.MultipleHiddenInput
)
type = forms.ChoiceField(
choices=add_blank_choice(CABLE_TYPE_CHOICES),
choices=add_blank_choice(CableTypeChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -3559,7 +3542,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
required=False
)
length_unit = forms.ChoiceField(
choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES),
choices=add_blank_choice(CableLengthUnitChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -3608,7 +3591,7 @@ class CableFilterForm(BootstrapMixin, forms.Form):
)
)
type = forms.MultipleChoiceField(
choices=add_blank_choice(CABLE_TYPE_CHOICES),
choices=add_blank_choice(CableTypeChoices),
required=False,
widget=StaticSelect2()
)
@ -3677,7 +3660,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
rack=device_bay.device.rack,
parent_bay__isnull=True,
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)
@ -3725,7 +3708,7 @@ class DeviceBayCSVForm(forms.ModelForm):
rack=device.rack,
parent_bay__isnull=True,
device_type__u_height=0,
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD
device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
).exclude(pk=device.pk)
else:
self.fields['installed_device'].queryset = Interface.objects.none()
@ -4214,22 +4197,22 @@ class PowerFeedCSVForm(forms.ModelForm):
help_text="Rack name (optional)"
)
status = CSVChoiceField(
choices=POWERFEED_STATUS_CHOICES,
choices=PowerFeedStatusChoices,
required=False,
help_text='Operational status'
)
type = CSVChoiceField(
choices=POWERFEED_TYPE_CHOICES,
choices=PowerFeedTypeChoices,
required=False,
help_text='Primary or redundant'
)
supply = CSVChoiceField(
choices=POWERFEED_SUPPLY_CHOICES,
choices=PowerFeedSupplyChoices,
required=False,
help_text='AC/DC'
)
phase = CSVChoiceField(
choices=POWERFEED_PHASE_CHOICES,
choices=PowerFeedPhaseChoices,
required=False,
help_text='Single or three-phase'
)
@ -4289,25 +4272,25 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
)
)
status = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
choices=add_blank_choice(PowerFeedStatusChoices),
required=False,
initial='',
widget=StaticSelect2()
)
type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
choices=add_blank_choice(PowerFeedTypeChoices),
required=False,
initial='',
widget=StaticSelect2()
)
supply = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
choices=add_blank_choice(PowerFeedSupplyChoices),
required=False,
initial='',
widget=StaticSelect2()
)
phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
choices=add_blank_choice(PowerFeedPhaseChoices),
required=False,
initial='',
widget=StaticSelect2()
@ -4368,22 +4351,22 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
)
)
status = forms.MultipleChoiceField(
choices=POWERFEED_STATUS_CHOICES,
choices=PowerFeedStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
type = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
choices=add_blank_choice(PowerFeedTypeChoices),
required=False,
widget=StaticSelect2()
)
supply = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
choices=add_blank_choice(PowerFeedSupplyChoices),
required=False,
widget=StaticSelect2()
)
phase = forms.ChoiceField(
choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
choices=add_blank_choice(PowerFeedPhaseChoices),
required=False,
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(
unique=True
)
status = models.PositiveSmallIntegerField(
choices=SITE_STATUS_CHOICES,
default=SITE_STATUS_ACTIVE
status = models.CharField(
max_length=50,
choices=SiteStatusChoices,
default=SiteStatusChoices.STATUS_ACTIVE
)
region = models.ForeignKey(
to='dcim.Region',
@ -331,6 +332,12 @@ class Site(ChangeLoggedModel, CustomFieldModel):
'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:
ordering = ['name']
@ -362,7 +369,7 @@ class Site(ChangeLoggedModel, CustomFieldModel):
)
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,
null=True
)
status = models.PositiveSmallIntegerField(
choices=RACK_STATUS_CHOICES,
default=RACK_STATUS_ACTIVE
status = models.CharField(
max_length=50,
choices=RackStatusChoices,
default=RackStatusChoices.STATUS_ACTIVE
)
role = models.ForeignKey(
to='dcim.RackRole',
@ -497,15 +505,15 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
verbose_name='Asset tag',
help_text='A unique tag used to identify this rack'
)
type = models.PositiveSmallIntegerField(
choices=RACK_TYPE_CHOICES,
type = models.CharField(
choices=RackTypeChoices,
max_length=50,
blank=True,
null=True,
verbose_name='Type'
)
width = models.PositiveSmallIntegerField(
choices=RACK_WIDTH_CHOICES,
default=RACK_WIDTH_19IN,
choices=RackWidthChoices,
default=RackWidthChoices.WIDTH_19IN,
verbose_name='Width',
help_text='Rail-to-rail width'
)
@ -527,10 +535,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
blank=True,
null=True
)
outer_unit = models.PositiveSmallIntegerField(
choices=RACK_DIMENSION_UNIT_CHOICES,
outer_unit = models.CharField(
max_length=50,
choices=RackDimensionUnitChoices,
blank=True,
null=True
)
comments = models.TextField(
blank=True
@ -552,6 +560,14 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
'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:
ordering = ['site', 'group', 'name']
unique_together = [
@ -568,10 +584,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
def clean(self):
# 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")
elif self.outer_width is None and self.outer_depth is None:
self.outer_unit = None
self.outer_unit = ''
if self.pk:
# Validate that Rack is tall enough to house the installed Devices
@ -644,9 +660,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
return ""
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'}
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()]
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):
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()):
"""
@ -910,12 +926,13 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
verbose_name='Is full depth',
help_text='Device consumes both front and rear rack faces'
)
subdevice_role = models.NullBooleanField(
default=None,
subdevice_role = models.CharField(
max_length=50,
choices=SubdeviceRoleChoices,
blank=True,
verbose_name='Parent/child status',
choices=SUBDEVICE_ROLE_CHOICES,
help_text='Parent devices house child devices in device bays. Select '
'"None" if this device type is neither a parent nor a child.'
help_text='Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.'
)
comments = models.TextField(
blank=True
@ -959,7 +976,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
self.part_number,
self.u_height,
self.is_full_depth,
self.get_subdevice_role_display() if self.subdevice_role else None,
self.get_subdevice_role_display(),
self.comments,
)
@ -979,13 +996,15 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
"{}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({
'subdevice_role': "Must delete all device bay templates associated with this device before "
"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({
'u_height': "Child device types must be 0U."
})
@ -996,11 +1015,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
@property
def is_parent_device(self):
return bool(self.subdevice_role)
return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT
@property
def is_child_device(self):
return bool(self.subdevice_role is False)
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
class ConsolePortTemplate(ComponentTemplateModel):
@ -1017,7 +1036,7 @@ class ConsolePortTemplate(ComponentTemplateModel):
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
blank=True
)
@ -1052,7 +1071,7 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
blank=True
)
@ -1087,7 +1106,7 @@ class PowerPortTemplate(ComponentTemplateModel):
)
type = models.CharField(
max_length=50,
choices=PowerPortTypes.CHOICES,
choices=PowerPortTypeChoices,
blank=True
)
maximum_draw = models.PositiveSmallIntegerField(
@ -1135,7 +1154,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
)
type = models.CharField(
max_length=50,
choices=PowerOutletTypes.CHOICES,
choices=PowerOutletTypeChoices,
blank=True
)
power_port = models.ForeignKey(
@ -1145,10 +1164,10 @@ class PowerOutletTemplate(ComponentTemplateModel):
null=True,
related_name='poweroutlet_templates'
)
feed_leg = models.PositiveSmallIntegerField(
choices=POWERFEED_LEG_CHOICES,
feed_leg = models.CharField(
max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True,
null=True,
help_text="Phase (for three-phase feeds)"
)
@ -1194,9 +1213,9 @@ class InterfaceTemplate(ComponentTemplateModel):
name = models.CharField(
max_length=64
)
type = models.PositiveSmallIntegerField(
choices=IFACE_TYPE_CHOICES,
default=IFACE_TYPE_10GE_SFP_PLUS
type = models.CharField(
max_length=50,
choices=InterfaceTypeChoices
)
mgmt_only = models.BooleanField(
default=False,
@ -1233,8 +1252,9 @@ class FrontPortTemplate(ComponentTemplateModel):
name = models.CharField(
max_length=64
)
type = models.PositiveSmallIntegerField(
choices=PORT_TYPE_CHOICES
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
rear_port = models.ForeignKey(
to='dcim.RearPortTemplate',
@ -1300,8 +1320,9 @@ class RearPortTemplate(ComponentTemplateModel):
name = models.CharField(
max_length=64
)
type = models.PositiveSmallIntegerField(
choices=PORT_TYPE_CHOICES
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
positions = models.PositiveSmallIntegerField(
default=1,
@ -1526,16 +1547,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
verbose_name='Position (U)',
help_text='The lowest-numbered unit occupied by the device'
)
face = models.PositiveSmallIntegerField(
face = models.CharField(
max_length=50,
blank=True,
null=True,
choices=RACK_FACE_CHOICES,
choices=DeviceFaceChoices,
verbose_name='Rack face'
)
status = models.PositiveSmallIntegerField(
choices=DEVICE_STATUS_CHOICES,
default=DEVICE_STATUS_ACTIVE,
verbose_name='Status'
status = models.CharField(
max_length=50,
choices=DeviceStatusChoices,
default=DeviceStatusChoices.STATUS_ACTIVE
)
primary_ip4 = models.OneToOneField(
to='ipam.IPAddress',
@ -1597,6 +1618,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
'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:
ordering = ['name']
unique_together = [
@ -1625,7 +1656,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
})
if self.rack is None:
if self.face is not None:
if self.face:
raise ValidationError({
'face': "Cannot select a rack face without assigning a rack.",
})
@ -1635,7 +1666,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
})
# Validate position/face combination
if self.position and self.face is None:
if self.position and not self.face:
raise ValidationError({
'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)
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(
max_length=50,
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
blank=True
)
connected_endpoint = models.OneToOneField(
@ -1928,7 +1959,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypes.CHOICES,
choices=ConsolePortTypeChoices,
blank=True
)
connection_status = models.NullBooleanField(
@ -1977,7 +2008,7 @@ class PowerPort(CableTermination, ComponentModel):
)
type = models.CharField(
max_length=50,
choices=PowerPortTypes.CHOICES,
choices=PowerPortTypeChoices,
blank=True
)
maximum_draw = models.PositiveSmallIntegerField(
@ -2077,8 +2108,8 @@ class PowerPort(CableTermination, ComponentModel):
}
# Calculate per-leg aggregates for three-phase feeds
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE:
for leg, leg_name in POWERFEED_LEG_CHOICES:
if self._connected_powerfeed and self._connected_powerfeed.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
for leg, leg_name in PowerOutletFeedLegChoices:
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(
maximum_draw_total=Sum('maximum_draw'),
@ -2120,7 +2151,7 @@ class PowerOutlet(CableTermination, ComponentModel):
)
type = models.CharField(
max_length=50,
choices=PowerOutletTypes.CHOICES,
choices=PowerOutletTypeChoices,
blank=True
)
power_port = models.ForeignKey(
@ -2130,10 +2161,10 @@ class PowerOutlet(CableTermination, ComponentModel):
null=True,
related_name='poweroutlets'
)
feed_leg = models.PositiveSmallIntegerField(
choices=POWERFEED_LEG_CHOICES,
feed_leg = models.CharField(
max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True,
null=True,
help_text="Phase (for three-phase feeds)"
)
connection_status = models.NullBooleanField(
@ -2226,9 +2257,9 @@ class Interface(CableTermination, ComponentModel):
blank=True,
verbose_name='Parent LAG'
)
type = models.PositiveSmallIntegerField(
choices=IFACE_TYPE_CHOICES,
default=IFACE_TYPE_10GE_SFP_PLUS
type = models.CharField(
max_length=50,
choices=InterfaceTypeChoices
)
enabled = models.BooleanField(
default=True
@ -2249,10 +2280,10 @@ class Interface(CableTermination, ComponentModel):
verbose_name='OOB Management',
help_text='This interface is used only for out-of-band management'
)
mode = models.PositiveSmallIntegerField(
choices=IFACE_MODE_CHOICES,
mode = models.CharField(
max_length=50,
choices=InterfaceModeChoices,
blank=True,
null=True
)
untagged_vlan = models.ForeignKey(
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.")
# 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({
'type': "Virtual machines can only have virtual interfaces."
})
@ -2340,7 +2371,7 @@ class Interface(CableTermination, ComponentModel):
})
# 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({
'type': "Cannot change interface type; it has LAG members ({}).".format(
", ".join([iface.name for iface in self.member_interfaces.all()])
@ -2361,7 +2392,7 @@ class Interface(CableTermination, ComponentModel):
self.untagged_vlan = None
# 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()
return super().save(*args, **kwargs)
@ -2423,7 +2454,7 @@ class Interface(CableTermination, ComponentModel):
@property
def is_lag(self):
return self.type == IFACE_TYPE_LAG
return self.type == InterfaceTypeChoices.TYPE_LAG
@property
def count_ipaddresses(self):
@ -2446,8 +2477,9 @@ class FrontPort(CableTermination, ComponentModel):
name = models.CharField(
max_length=64
)
type = models.PositiveSmallIntegerField(
choices=PORT_TYPE_CHOICES
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
rear_port = models.ForeignKey(
to='dcim.RearPort',
@ -2513,8 +2545,9 @@ class RearPort(CableTermination, ComponentModel):
name = models.CharField(
max_length=64
)
type = models.PositiveSmallIntegerField(
choices=PORT_TYPE_CHOICES
type = models.CharField(
max_length=50,
choices=PortTypeChoices
)
positions = models.PositiveSmallIntegerField(
default=1,
@ -2776,10 +2809,10 @@ class Cable(ChangeLoggedModel):
ct_field='termination_b_type',
fk_field='termination_b_id'
)
type = models.PositiveSmallIntegerField(
choices=CABLE_TYPE_CHOICES,
blank=True,
null=True
type = models.CharField(
max_length=50,
choices=CableTypeChoices,
blank=True
)
status = models.BooleanField(
choices=CONNECTION_STATUS_CHOICES,
@ -2796,10 +2829,10 @@ class Cable(ChangeLoggedModel):
blank=True,
null=True
)
length_unit = models.PositiveSmallIntegerField(
choices=CABLE_LENGTH_UNIT_CHOICES,
length_unit = models.CharField(
max_length=50,
choices=CableLengthUnitChoices,
blank=True,
null=True
)
# Stores the normalized length (in meters) for database ordering
_abs_length = models.DecimalField(
@ -2927,10 +2960,10 @@ class Cable(ChangeLoggedModel):
))
# 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")
elif self.length is None:
self.length_unit = None
self.length_unit = ''
def save(self, *args, **kwargs):
@ -3074,21 +3107,25 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
name = models.CharField(
max_length=50
)
status = models.PositiveSmallIntegerField(
choices=POWERFEED_STATUS_CHOICES,
default=POWERFEED_STATUS_ACTIVE
status = models.CharField(
max_length=50,
choices=PowerFeedStatusChoices,
default=PowerFeedStatusChoices.STATUS_ACTIVE
)
type = models.PositiveSmallIntegerField(
choices=POWERFEED_TYPE_CHOICES,
default=POWERFEED_TYPE_PRIMARY
type = models.CharField(
max_length=50,
choices=PowerFeedTypeChoices,
default=PowerFeedTypeChoices.TYPE_PRIMARY
)
supply = models.PositiveSmallIntegerField(
choices=POWERFEED_SUPPLY_CHOICES,
default=POWERFEED_SUPPLY_AC
supply = models.CharField(
max_length=50,
choices=PowerFeedSupplyChoices,
default=PowerFeedSupplyChoices.SUPPLY_AC
)
phase = models.PositiveSmallIntegerField(
choices=POWERFEED_PHASE_CHOICES,
default=POWERFEED_PHASE_SINGLE
phase = models.CharField(
max_length=50,
choices=PowerFeedPhaseChoices,
default=PowerFeedPhaseChoices.PHASE_SINGLE
)
voltage = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)],
@ -3123,6 +3160,18 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
'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:
ordering = ['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
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)
else:
self.available_power = round(kva)
@ -3170,7 +3219,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
super().save(*args, **kwargs)
def get_type_class(self):
return STATUS_CLASSES[self.type]
return self.TYPE_CLASS_MAP.get(self.type)
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:"" }}
"""
SUBDEVICE_ROLE_TEMPLATE = """
{% if record.subdevice_role == True %}Parent{% elif record.subdevice_role == False %}Child{% else %}—{% endif %}
"""
DEVICETYPE_INSTANCES_TEMPLATE = """
<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'
)
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(
template_code=DEVICETYPE_INSTANCES_TEMPLATE,
verbose_name='Instances'

View File

@ -3,6 +3,7 @@ from netaddr import IPNetwork
from rest_framework import status
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.choices import *
from dcim.constants import *
from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@ -180,7 +181,7 @@ class SiteTest(APITestCase):
'name': 'Test Site 4',
'slug': 'test-site-4',
'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE,
'status': SiteStatusChoices.STATUS_ACTIVE,
}
url = reverse('dcim-api:site-list')
@ -200,19 +201,19 @@ class SiteTest(APITestCase):
'name': 'Test Site 4',
'slug': 'test-site-4',
'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE,
'status': SiteStatusChoices.STATUS_ACTIVE,
},
{
'name': 'Test Site 5',
'slug': 'test-site-5',
'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE,
'status': SiteStatusChoices.STATUS_ACTIVE,
},
{
'name': 'Test Site 6',
'slug': 'test-site-6',
'region': self.region1.pk,
'status': SITE_STATUS_ACTIVE,
'status': SiteStatusChoices.STATUS_ACTIVE,
},
]
@ -2473,7 +2474,7 @@ class InterfaceTest(APITestCase):
data = {
'device': self.device.pk,
'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED,
'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan3.id,
'tagged_vlans': [self.vlan1.id, self.vlan2.id],
}
@ -2520,21 +2521,21 @@ class InterfaceTest(APITestCase):
{
'device': self.device.pk,
'name': 'Test Interface 4',
'mode': IFACE_MODE_TAGGED,
'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
},
{
'device': self.device.pk,
'name': 'Test Interface 5',
'mode': IFACE_MODE_TAGGED,
'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
},
{
'device': self.device.pk,
'name': 'Test Interface 6',
'mode': IFACE_MODE_TAGGED,
'mode': InterfaceModeChoices.MODE_TAGGED,
'untagged_vlan': self.vlan2.id,
'tagged_vlans': [self.vlan1.id],
},
@ -2553,7 +2554,7 @@ class InterfaceTest(APITestCase):
def test_update_interface(self):
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 = {
@ -2590,11 +2591,11 @@ class DeviceBayTest(APITestCase):
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
self.devicetype1 = DeviceType.objects.create(
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(
manufacturer=manufacturer, model='Child Device Type', slug='child-device-type',
subdevice_role=SUBDEVICE_ROLE_CHILD
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
)
devicerole = DeviceRole.objects.create(
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 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(
termination_a=self.device1.interfaces.get(name='eth0'),
@ -3033,16 +3034,16 @@ class ConnectionTest(APITestCase):
device=self.device2, name='Test Console Server Port 1'
)
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(
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(
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(
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')
@ -3161,16 +3162,16 @@ class ConnectionTest(APITestCase):
device=self.device2, name='Test Interface 2'
)
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(
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(
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(
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')
@ -3272,16 +3273,16 @@ class ConnectionTest(APITestCase):
circuit=circuit, term_side='A', site=self.site, port_speed=10000
)
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(
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(
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(
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')
@ -3410,23 +3411,23 @@ class VirtualChassisTest(APITestCase):
device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
)
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):
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):
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):
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):
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):
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):
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):
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):
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
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'
)
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(
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(
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(
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(
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(
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):
@ -3726,7 +3727,7 @@ class PowerFeedTest(APITestCase):
'name': 'Test Power Feed 4A',
'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk,
'type': POWERFEED_TYPE_PRIMARY,
'type': PowerFeedTypeChoices.TYPE_PRIMARY,
}
url = reverse('dcim-api:powerfeed-list')
@ -3746,13 +3747,13 @@ class PowerFeedTest(APITestCase):
'name': 'Test Power Feed 4A',
'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk,
'type': POWERFEED_TYPE_PRIMARY,
'type': PowerFeedTypeChoices.TYPE_PRIMARY,
},
{
'name': 'Test Power Feed 4B',
'power_panel': self.powerpanel1.pk,
'rack': self.rack4.pk,
'type': POWERFEED_TYPE_REDUNDANT,
'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
},
]
@ -3769,7 +3770,7 @@ class PowerFeedTest(APITestCase):
data = {
'name': 'Test Power Feed X',
'rack': self.rack4.pk,
'type': POWERFEED_TYPE_REDUNDANT,
'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
}
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'),
'site': get_id(Site, 'test1'),
'rack': '1',
'face': RACK_FACE_FRONT,
'face': DeviceFaceChoices.FACE_FRONT,
'position': 41,
'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.save())
@ -38,10 +38,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'qfx5100-48s'),
'site': get_id(Site, 'test1'),
'rack': '1',
'face': RACK_FACE_FRONT,
'face': DeviceFaceChoices.FACE_FRONT,
'position': 1,
'platform': get_id(Platform, 'juniper-junos'),
'status': DEVICE_STATUS_ACTIVE,
'status': DeviceStatusChoices.STATUS_ACTIVE,
})
self.assertFalse(test.is_valid())
@ -54,10 +54,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
'site': get_id(Site, 'test1'),
'rack': '1',
'face': None,
'face': '',
'position': None,
'platform': None,
'status': DEVICE_STATUS_ACTIVE,
'status': DeviceStatusChoices.STATUS_ACTIVE,
})
self.assertTrue(test.is_valid())
self.assertTrue(test.save())
@ -71,10 +71,10 @@ class DeviceTestCase(TestCase):
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
'site': get_id(Site, 'test1'),
'rack': '1',
'face': RACK_FACE_REAR,
'face': DeviceFaceChoices.FACE_REAR,
'position': None,
'platform': None,
'status': DEVICE_STATUS_ACTIVE,
'status': DeviceStatusChoices.STATUS_ACTIVE,
})
self.assertTrue(test.is_valid())
self.assertTrue(test.save())

View File

@ -87,7 +87,7 @@ class RackTestCase(TestCase):
site=self.site1,
rack=rack1,
position=43,
face=RACK_FACE_FRONT,
face=DeviceFaceChoices.FACE_FRONT,
)
device1.save()
@ -117,7 +117,7 @@ class RackTestCase(TestCase):
site=self.site1,
rack=self.rack,
position=10,
face=RACK_FACE_REAR,
face=DeviceFaceChoices.FACE_REAR,
)
device1.save()
@ -146,7 +146,7 @@ class RackTestCase(TestCase):
site=self.site1,
rack=self.rack,
position=None,
face=None,
face='',
)
self.assertTrue(pdu)
@ -187,20 +187,20 @@ class DeviceTestCase(TestCase):
device_type=self.device_type,
name='Power Outlet 1',
power_port=ppt,
feed_leg=POWERFEED_LEG_A
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
).save()
InterfaceTemplate(
device_type=self.device_type,
name='Interface 1',
type=IFACE_TYPE_1GE_FIXED,
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mgmt_only=True
).save()
rpt = RearPortTemplate(
device_type=self.device_type,
name='Rear Port 1',
type=PORT_TYPE_8P8C,
type=PortTypeChoices.TYPE_8P8C,
positions=8
)
rpt.save()
@ -208,7 +208,7 @@ class DeviceTestCase(TestCase):
FrontPortTemplate(
device_type=self.device_type,
name='Front Port 1',
type=PORT_TYPE_8P8C,
type=PortTypeChoices.TYPE_8P8C,
rear_port=rpt,
rear_port_position=2
).save()
@ -251,27 +251,27 @@ class DeviceTestCase(TestCase):
device=d,
name='Power Outlet 1',
power_port=pp,
feed_leg=POWERFEED_LEG_A
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
)
Interface.objects.get(
device=d,
name='Interface 1',
type=IFACE_TYPE_1GE_FIXED,
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mgmt_only=True
)
rp = RearPort.objects.get(
device=d,
name='Rear Port 1',
type=PORT_TYPE_8P8C,
type=PortTypeChoices.TYPE_8P8C,
positions=8
)
FrontPort.objects.get(
device=d,
name='Front Port 1',
type=PORT_TYPE_8P8C,
type=PortTypeChoices.TYPE_8P8C,
rear_port=rp,
rear_port_position=2
)
@ -379,7 +379,7 @@ class CableTestCase(TestCase):
"""
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)
with self.assertRaises(ValidationError):
cable.clean()
@ -388,7 +388,7 @@ class CableTestCase(TestCase):
"""
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)
with self.assertRaises(ValidationError):
cable.clean()
@ -421,16 +421,16 @@ class CablePathTestCase(TestCase):
device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site
)
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(
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(
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(
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):

View File

@ -255,15 +255,15 @@ power-outlets:
- name: Power Outlet 1
type: iec-60320-c13
power_port: Power Port 1
feed_leg: 1
feed_leg: A
- name: Power Outlet 2
type: iec-60320-c13
power_port: Power Port 1
feed_leg: 1
feed_leg: A
- name: Power Outlet 3
type: iec-60320-c13
power_port: Power Port 1
feed_leg: 1
feed_leg: A
interfaces:
- name: Interface 1
type: 1000base-t
@ -326,29 +326,29 @@ device-bays:
self.assertEqual(dt.consoleport_templates.count(), 3)
cp1 = ConsolePortTemplate.objects.first()
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)
csp1 = ConsoleServerPortTemplate.objects.first()
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)
pp1 = PowerPortTemplate.objects.first()
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)
po1 = PowerOutletTemplate.objects.first()
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.feed_leg, POWERFEED_LEG_A)
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
self.assertEqual(dt.interface_templates.count(), 3)
iface1 = InterfaceTemplate.objects.first()
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.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.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()
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()
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()
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()
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()
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()
Cable(termination_a=iface1, termination_b=iface4, type=CABLE_TYPE_CAT6).save()
Cable(termination_a=iface2, termination_b=iface5, type=CABLE_TYPE_CAT6).save()
Cable(termination_a=iface3, termination_b=iface6, 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=CableTypeChoices.TYPE_CAT6).save()
Cable(termination_a=iface3, termination_b=iface6, type=CableTypeChoices.TYPE_CAT6).save()
def test_cable_list(self):
url = reverse('dcim:cable_list')
params = {
"type": CABLE_TYPE_CAT6,
"type": CableTypeChoices.TYPE_CAT6,
}
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.exceptions import ValidationError
from extras.constants import *
from extras.choices import *
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
from utilities.api import ValidatedModelSerializer
@ -37,7 +37,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
if value not in [None, '']:
# Validate integer
if cf.type == CF_TYPE_INTEGER:
if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
try:
int(value)
except ValueError:
@ -46,13 +46,13 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
)
# 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(
"Invalid value for boolean field {}: {}".format(field_name, value)
)
# Validate date
if cf.type == CF_TYPE_DATE:
if cf.type == CustomFieldTypeChoices.TYPE_DATE:
try:
datetime.strptime(value, '%Y-%m-%d')
except ValueError:
@ -61,7 +61,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
)
# Validate selected choice
if cf.type == CF_TYPE_SELECT:
if cf.type == CustomFieldTypeChoices.TYPE_SELECT:
try:
value = int(value)
except ValueError:
@ -100,7 +100,7 @@ class CustomFieldModelSerializer(ValidatedModelSerializer):
instance.custom_fields = {}
for field in fields:
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
else:
instance.custom_fields[field.name] = value

View File

@ -8,6 +8,7 @@ from dcim.api.nested_serializers import (
NestedRegionSerializer, NestedSiteSerializer,
)
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
from extras.choices import *
from extras.constants import *
from extras.models import (
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
@ -56,8 +57,8 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
class ExportTemplateSerializer(ValidatedModelSerializer):
template_language = ChoiceField(
choices=TEMPLATE_LANGUAGE_CHOICES,
default=TEMPLATE_LANGUAGE_JINJA2
choices=ExportTemplateLanguageChoices,
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
)
class Meta:
@ -255,7 +256,7 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
read_only=True
)
action = ChoiceField(
choices=OBJECTCHANGE_ACTION_CHOICES,
choices=ObjectChangeActionChoices,
read_only=True
)
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',
]
# 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
CUSTOMLINK_MODELS = [
'circuits.circuit',
@ -68,23 +42,6 @@ CUSTOMLINK_MODELS = [
'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_TYPE_INTERFACE = 100
GRAPH_TYPE_DEVICE = 150
@ -128,42 +85,6 @@ EXPORTTEMPLATE_MODELS = [
'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
LOG_DEFAULT = 0
LOG_SUCCESS = 10
@ -178,14 +99,6 @@ LOG_LEVEL_CODES = {
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
WEBHOOK_MODELS = [
'circuits.circuit',

View File

@ -4,6 +4,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup
from .choices import *
from .constants import *
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
@ -25,7 +26,7 @@ class CustomFieldFilter(django_filters.Filter):
return queryset
# Selection fields get special treatment (values must be integers)
if self.cf_type == CF_TYPE_SELECT:
if self.cf_type == CustomFieldTypeChoices.TYPE_SELECT:
try:
# Treat 0 as None
if int(value) == 0:
@ -42,7 +43,8 @@ class CustomFieldFilter(django_filters.Filter):
return queryset.none()
# 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(
custom_field_values__field__name=self.field_name,
custom_field_values__serialized_value=value
@ -65,7 +67,11 @@ class CustomFieldFilterSet(django_filters.FilterSet):
super().__init__(*args, **kwargs)
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:
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,
BOOLEAN_WITH_BLANK_CHOICES,
)
from .choices import *
from .constants import *
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()
custom_fields = CustomField.objects.filter(obj_type=content_type)
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:
field_name = 'cf_{}'.format(str(cf.name))
initial = cf.default if not bulk_edit else None
# Integer
if cf.type == CF_TYPE_INTEGER:
if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
field = forms.IntegerField(required=cf.required, initial=initial)
# Boolean
elif cf.type == CF_TYPE_BOOLEAN:
elif cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
choices = (
(None, '---------'),
(1, 'True'),
@ -56,11 +57,11 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
)
# 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")
# Select
elif cf.type == CF_TYPE_SELECT:
elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
if not cf.required or bulk_edit or filterable_only:
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)
# URL
elif cf.type == CF_TYPE_URL:
elif cf.type == CustomFieldTypeChoices.TYPE_URL:
field = LaxURLField(required=cf.required, initial=initial)
# Text
@ -400,7 +401,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
)
)
action = forms.ChoiceField(
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
choices=add_blank_choice(ObjectChangeActionChoices),
required=False
)
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 utilities.querysets import DummyQuerySet
from .constants import *
from .choices import ObjectChangeActionChoices
from .models import ObjectChange
from .signals import purge_changelog
from .webhooks import enqueue_webhooks
@ -23,7 +23,7 @@ def handle_changed_object(sender, instance, **kwargs):
Fires when an object is created or updated.
"""
# 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(
(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
_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:
# 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'):
instance.cache_custom_fields()
@ -116,11 +116,11 @@ class ObjectChangeMiddleware(object):
enqueue_webhooks(instance, request.user, request.id, action)
# Increment metric counters
if action == OBJECTCHANGE_ACTION_CREATE:
if action == ObjectChangeActionChoices.ACTION_CREATE:
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()
elif action == OBJECTCHANGE_ACTION_DELETE:
elif action == ObjectChangeActionChoices.ACTION_DELETE:
model_deletes.labels(instance._meta.model_name).inc()
# 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
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):
"""

View File

@ -2,21 +2,19 @@
# Generated by Django 1.11.9 on 2018-02-21 19:48
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):
CustomField = apps.get_model('extras', 'CustomField')
CustomField.objects.filter(is_filterable=False).update(filter_logic=CF_FILTER_DISABLED)
CustomField.objects.filter(is_filterable=True).update(filter_logic=CF_FILTER_LOOSE)
CustomField.objects.filter(is_filterable=False).update(filter_logic=0)
CustomField.objects.filter(is_filterable=True).update(filter_logic=1)
# 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):
CustomField = apps.get_model('extras', 'CustomField')
CustomField.objects.filter(filter_logic=CF_FILTER_DISABLED).update(is_filterable=False)
CustomField.objects.exclude(filter_logic=CF_FILTER_DISABLED).update(is_filterable=True)
CustomField.objects.filter(filter_logic=0).update(is_filterable=False)
CustomField.objects.exclude(filter_logic=0).update(is_filterable=True)
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.utils import deepmerge, model_names_to_filter_dict
from .choices import *
from .constants import *
from .querysets import ConfigContextQuerySet
@ -62,9 +63,10 @@ class Webhook(models.Model):
verbose_name='URL',
help_text="A POST will be sent to this URL when the webhook is called."
)
http_content_type = models.PositiveSmallIntegerField(
choices=WEBHOOK_CT_CHOICES,
default=WEBHOOK_CT_JSON,
http_content_type = models.CharField(
max_length=50,
choices=WebhookContentTypeChoices,
default=WebhookContentTypeChoices.CONTENTTYPE_JSON,
verbose_name='HTTP content type'
)
additional_headers = JSONField(
@ -182,9 +184,10 @@ class CustomField(models.Model):
limit_choices_to=get_custom_field_models,
help_text='The object(s) to which this field applies.'
)
type = models.PositiveSmallIntegerField(
choices=CUSTOMFIELD_TYPE_CHOICES,
default=CF_TYPE_TEXT
type = models.CharField(
max_length=50,
choices=CustomFieldTypeChoices,
default=CustomFieldTypeChoices.TYPE_TEXT
)
name = models.CharField(
max_length=50,
@ -205,9 +208,10 @@ class CustomField(models.Model):
help_text='If true, this field is required when creating new objects '
'or editing an existing object.'
)
filter_logic = models.PositiveSmallIntegerField(
choices=CF_FILTER_CHOICES,
default=CF_FILTER_LOOSE,
filter_logic = models.CharField(
max_length=50,
choices=CustomFieldFilterLogicChoices,
default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
help_text='Loose matches any instance of a given string; exact '
'matches the entire field.'
)
@ -233,15 +237,15 @@ class CustomField(models.Model):
"""
if value is None:
return ''
if self.type == CF_TYPE_BOOLEAN:
if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
return str(int(bool(value)))
if self.type == CF_TYPE_DATE:
if self.type == CustomFieldTypeChoices.TYPE_DATE:
# Could be date/datetime object or string
try:
return value.strftime('%Y-%m-%d')
except AttributeError:
return value
if self.type == CF_TYPE_SELECT:
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
# Could be ModelChoiceField or TypedChoiceField
return str(value.id) if hasattr(value, 'id') else str(value)
return value
@ -252,14 +256,14 @@ class CustomField(models.Model):
"""
if serialized_value == '':
return None
if self.type == CF_TYPE_INTEGER:
if self.type == CustomFieldTypeChoices.TYPE_INTEGER:
return int(serialized_value)
if self.type == CF_TYPE_BOOLEAN:
if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
return bool(int(serialized_value))
if self.type == CF_TYPE_DATE:
if self.type == CustomFieldTypeChoices.TYPE_DATE:
# Read date as YYYY-MM-DD
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 serialized_value
@ -312,7 +316,7 @@ class CustomFieldChoice(models.Model):
to='extras.CustomField',
on_delete=models.CASCADE,
related_name='choices',
limit_choices_to={'type': CF_TYPE_SELECT}
limit_choices_to={'type': CustomFieldTypeChoices.TYPE_SELECT}
)
value = models.CharField(
max_length=100
@ -330,14 +334,17 @@ class CustomFieldChoice(models.Model):
return self.value
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.")
def delete(self, using=None, keep_parents=False):
# When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it
pk = self.pk
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(
max_length=30,
choices=BUTTON_CLASS_CHOICES,
default=BUTTON_CLASS_DEFAULT,
choices=CustomLinkButtonClassChoices,
default=CustomLinkButtonClassChoices.CLASS_DEFAULT,
help_text="The class of the first link in a group will be used for the dropdown button"
)
new_window = models.BooleanField(
@ -458,9 +465,10 @@ class ExportTemplate(models.Model):
max_length=200,
blank=True
)
template_language = models.PositiveSmallIntegerField(
choices=TEMPLATE_LANGUAGE_CHOICES,
default=TEMPLATE_LANGUAGE_JINJA2
template_language = models.CharField(
max_length=50,
choices=ExportTemplateLanguageChoices,
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
)
template_code = models.TextField(
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(
editable=False
)
action = models.PositiveSmallIntegerField(
choices=OBJECTCHANGE_ACTION_CHOICES
action = models.CharField(
max_length=50,
choices=ObjectChangeActionChoices
)
changed_object_type = models.ForeignKey(
to=ContentType,

View File

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

View File

@ -3,6 +3,7 @@ from django.urls import reverse
from rest_framework import status
from dcim.models import Site
from extras.choices import *
from extras.constants import *
from extras.models import CustomField, CustomFieldValue, ObjectChange
from utilities.testing import APITestCase
@ -17,7 +18,7 @@ class ChangeLogTest(APITestCase):
# Create a custom field on the Site model
ct = ContentType.objects.get_for_model(Site)
cf = CustomField(
type=CF_TYPE_TEXT,
type=CustomFieldTypeChoices.TYPE_TEXT,
name='my_field',
required=False
)
@ -49,7 +50,7 @@ class ChangeLogTest(APITestCase):
changed_object_id=site.pk
)
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.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
@ -81,7 +82,7 @@ class ChangeLogTest(APITestCase):
changed_object_id=site.pk
)
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.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
@ -110,6 +111,6 @@ class ChangeLogTest(APITestCase):
oc = ObjectChange.objects.first()
self.assertEqual(oc.changed_object, None)
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.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 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 utilities.testing import APITestCase
from virtualization.models import VirtualMachine
@ -25,13 +25,13 @@ class CustomFieldTest(TestCase):
def test_simple_fields(self):
DATA = (
{'field_type': CF_TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
{'field_type': CF_TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
{'field_type': CF_TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
{'field_type': CF_TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
{'field_type': CF_TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
{'field_type': CF_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_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
{'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
{'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
{'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
{'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
{'field_type': CustomFieldTypeChoices.TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None},
{'field_type': CustomFieldTypeChoices.TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''},
)
obj_type = ContentType.objects.get_for_model(Site)
@ -67,7 +67,7 @@ class CustomFieldTest(TestCase):
obj_type = ContentType.objects.get_for_model(Site)
# 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.obj_type.set([obj_type])
cf.save()
@ -107,37 +107,37 @@ class CustomFieldAPITest(APITestCase):
content_type = ContentType.objects.get_for_model(Site)
# 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.obj_type.set([content_type])
self.cf_text.save()
# 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.obj_type.set([content_type])
self.cf_integer.save()
# 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.obj_type.set([content_type])
self.cf_boolean.save()
# 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.obj_type.set([content_type])
self.cf_date.save()
# 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.obj_type.set([content_type])
self.cf_url.save()
# 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.obj_type.set([content_type])
self.cf_select.save()
@ -308,8 +308,8 @@ class CustomFieldChoiceAPITest(APITestCase):
vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
self.cf_1 = CustomField.objects.create(name="cf_1", type=CF_TYPE_SELECT)
self.cf_2 = CustomField.objects.create(name="cf_2", 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=CustomFieldTypeChoices.TYPE_SELECT)
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)

View File

@ -6,7 +6,7 @@ from django.test import Client, TestCase
from django.urls import reverse
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 utilities.testing import create_test_user
@ -83,7 +83,7 @@ class ObjectChangeTestCase(TestCase):
# Create three ObjectChanges
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.request_id = uuid.uuid4()
oc.save()

View File

@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from extras.models import Webhook
from utilities.api import get_serializer_for_model
from .choices import *
from .constants import *
@ -18,9 +19,9 @@ def enqueue_webhooks(instance, user, request_id, action):
# Retrieve any applicable Webhooks
action_flag = {
OBJECTCHANGE_ACTION_CREATE: 'type_create',
OBJECTCHANGE_ACTION_UPDATE: 'type_update',
OBJECTCHANGE_ACTION_DELETE: 'type_delete',
ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
}[action]
obj_type = ContentType.objects.get_for_model(instance.__class__)
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 rest_framework.utils.encoders import JSONEncoder
from .choices import ObjectChangeActionChoices
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
"""
payload = {
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
'event': dict(ObjectChangeActionChoices)[event].lower(),
'timestamp': timestamp,
'model': model_name,
'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.models import Interface
from extras.api.customfields import CustomFieldModelSerializer
from ipam.constants import *
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from ipam.choices import *
from ipam.models import AF_CHOICES, Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from tenancy.api.nested_serializers import NestedTenantSerializer
from utilities.api import (
ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer,
@ -102,7 +102,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
site = NestedSiteSerializer(required=False, allow_null=True)
group = NestedVLANGroupSerializer(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)
tags = TagListSerializerField(required=False)
prefix_count = serializers.IntegerField(read_only=True)
@ -140,7 +140,7 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(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)
tags = TagListSerializerField(required=False)
@ -200,8 +200,8 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
family = ChoiceField(choices=AF_CHOICES, read_only=True)
vrf = NestedVRFSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False, allow_null=True)
status = ChoiceField(choices=IPAddressStatusChoices, required=False)
role = ChoiceField(choices=IPAddressRoleChoices, required=False, allow_null=True)
interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
nat_inside = NestedIPAddressSerializer(required=False, allow_null=True)
nat_outside = NestedIPAddressSerializer(read_only=True)
@ -239,7 +239,7 @@ class AvailableIPSerializer(serializers.Serializer):
class ServiceSerializer(CustomFieldModelSerializer):
device = NestedDeviceSerializer(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(
queryset=IPAddress.objects.all(),
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 utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
from virtualization.models import VirtualMachine
from .constants import *
from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
@ -178,7 +178,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
label='Role (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=PREFIX_STATUS_CHOICES,
choices=PrefixStatusChoices,
null_value=None
)
tag = TagFilter()
@ -310,11 +310,11 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
label='Interface (ID)',
)
status = django_filters.MultipleChoiceFilter(
choices=IPADDRESS_STATUS_CHOICES,
choices=IPAddressStatusChoices,
null_value=None
)
role = django_filters.MultipleChoiceFilter(
choices=IPADDRESS_ROLE_CHOICES
choices=IPAddressRoleChoices
)
tag = TagFilter()
@ -424,7 +424,7 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
label='Role (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=VLAN_STATUS_CHOICES,
choices=VLANStatusChoices,
null_value=None
)
tag = TagFilter()

View File

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

View File

@ -13,7 +13,7 @@ from utilities.forms import (
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
)
from virtualization.models import VirtualMachine
from .constants import *
from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
IP_FAMILY_CHOICES = [
@ -374,7 +374,7 @@ class PrefixCSVForm(forms.ModelForm):
required=False
)
status = CSVChoiceField(
choices=PREFIX_STATUS_CHOICES,
choices=PrefixStatusChoices,
help_text='Operational status'
)
role = forms.ModelChoiceField(
@ -459,7 +459,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
)
)
status = forms.ChoiceField(
choices=add_blank_choice(PREFIX_STATUS_CHOICES),
choices=add_blank_choice(PrefixStatusChoices),
required=False,
widget=StaticSelect2()
)
@ -527,7 +527,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
)
)
status = forms.MultipleChoiceField(
choices=PREFIX_STATUS_CHOICES,
choices=PrefixStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -764,11 +764,11 @@ class IPAddressCSVForm(forms.ModelForm):
}
)
status = CSVChoiceField(
choices=IPADDRESS_STATUS_CHOICES,
choices=IPAddressStatusChoices,
help_text='Operational status'
)
role = CSVChoiceField(
choices=IPADDRESS_ROLE_CHOICES,
choices=IPAddressRoleChoices,
required=False,
help_text='Functional role'
)
@ -893,12 +893,12 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
)
)
status = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
choices=add_blank_choice(IPAddressStatusChoices),
required=False,
widget=StaticSelect2()
)
role = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
choices=add_blank_choice(IPAddressRoleChoices),
required=False,
widget=StaticSelect2()
)
@ -972,12 +972,12 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
)
)
status = forms.MultipleChoiceField(
choices=IPADDRESS_STATUS_CHOICES,
choices=IPAddressStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
role = forms.MultipleChoiceField(
choices=IPADDRESS_ROLE_CHOICES,
choices=IPAddressRoleChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -1111,7 +1111,7 @@ class VLANCSVForm(forms.ModelForm):
}
)
status = CSVChoiceField(
choices=VLAN_STATUS_CHOICES,
choices=VLANStatusChoices,
help_text='Operational status'
)
role = forms.ModelChoiceField(
@ -1180,7 +1180,7 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
)
)
status = forms.ChoiceField(
choices=add_blank_choice(VLAN_STATUS_CHOICES),
choices=add_blank_choice(VLANStatusChoices),
required=False,
widget=StaticSelect2()
)
@ -1229,7 +1229,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
)
)
status = forms.MultipleChoiceField(
choices=VLAN_STATUS_CHOICES,
choices=VLANStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -1292,7 +1292,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search'
)
protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
choices=add_blank_choice(ServiceProtocolChoices),
required=False,
widget=StaticSelect2Multiple()
)
@ -1307,7 +1307,7 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
widget=forms.MultipleHiddenInput()
)
protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
choices=add_blank_choice(ServiceProtocolChoices),
required=False,
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.utils import serialize_object
from virtualization.models import VirtualMachine
from .constants import *
from .choices import *
from .fields import IPNetworkField, IPAddressField
from .querysets import PrefixQuerySet
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):
"""
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,
verbose_name='VLAN'
)
status = models.PositiveSmallIntegerField(
choices=PREFIX_STATUS_CHOICES,
default=PREFIX_STATUS_ACTIVE,
status = models.CharField(
max_length=50,
choices=PrefixStatusChoices,
default=PrefixStatusChoices.STATUS_ACTIVE,
verbose_name='Status',
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',
]
STATUS_CLASS_MAP = {
'container': 'default',
'active': 'primary',
'reserved': 'info',
'deprecated': 'danger',
}
class Meta:
ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix']
verbose_name_plural = 'prefixes'
@ -404,7 +429,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
prefix_length = property(fset=_set_prefix_length)
def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status]
return self.STATUS_CLASS_MAP.get(self.status)
def get_duplicates(self):
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
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))
else:
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
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))
else:
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
"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)
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
return int(float(child_prefixes.size) / self.prefix.size * 100)
@ -550,17 +575,16 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
blank=True,
null=True
)
status = models.PositiveSmallIntegerField(
choices=IPADDRESS_STATUS_CHOICES,
default=IPADDRESS_STATUS_ACTIVE,
verbose_name='Status',
status = models.CharField(
max_length=50,
choices=IPAddressStatusChoices,
default=IPAddressStatusChoices.STATUS_ACTIVE,
help_text='The operational status of this IP'
)
role = models.PositiveSmallIntegerField(
verbose_name='Role',
choices=IPADDRESS_ROLE_CHOICES,
role = models.CharField(
max_length=50,
choices=IPAddressRoleChoices,
blank=True,
null=True,
help_text='The functional role of this IP'
)
interface = models.ForeignKey(
@ -604,6 +628,24 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
'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:
ordering = ['family', 'address']
verbose_name = 'IP address'
@ -737,10 +779,10 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
return None
def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status]
return self.STATUS_CLASS_MAP.get(self.status)
def get_role_class(self):
return ROLE_CHOICE_CLASSES[self.role]
return self.ROLE_CLASS_MAP[self.role]
class VLANGroup(ChangeLoggedModel):
@ -831,10 +873,10 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
blank=True,
null=True
)
status = models.PositiveSmallIntegerField(
choices=VLAN_STATUS_CHOICES,
default=1,
verbose_name='Status'
status = models.CharField(
max_length=50,
choices=VLANStatusChoices,
default=VLANStatusChoices.STATUS_ACTIVE
)
role = models.ForeignKey(
to='ipam.Role',
@ -857,6 +899,12 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
STATUS_CLASS_MAP = {
'active': 'primary',
'reserved': 'info',
'deprecated': 'danger',
}
class Meta:
ordering = ['site', 'group', 'vid']
unique_together = [
@ -899,7 +947,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
return None
def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status]
return self.STATUS_CLASS_MAP[self.status]
def get_members(self):
# Return all interfaces assigned to this VLAN
@ -932,8 +980,9 @@ class Service(ChangeLoggedModel, CustomFieldModel):
name = models.CharField(
max_length=30
)
protocol = models.PositiveSmallIntegerField(
choices=IP_PROTOCOL_CHOICES
protocol = models.CharField(
max_length=50,
choices=ServiceProtocolChoices
)
port = models.PositiveIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(65535)],

View File

@ -5,7 +5,7 @@ from netaddr import IPNetwork
from rest_framework import status
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 utilities.testing import APITestCase
@ -996,13 +996,13 @@ class ServiceTest(APITestCase):
name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
)
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(
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(
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):
@ -1024,7 +1024,7 @@ class ServiceTest(APITestCase):
data = {
'device': self.device1.pk,
'name': 'Test Service 4',
'protocol': IP_PROTOCOL_TCP,
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 4,
}
@ -1045,19 +1045,19 @@ class ServiceTest(APITestCase):
{
'device': self.device1.pk,
'name': 'Test Service 4',
'protocol': IP_PROTOCOL_TCP,
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 4,
},
{
'device': self.device1.pk,
'name': 'Test Service 5',
'protocol': IP_PROTOCOL_TCP,
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 5,
},
{
'device': self.device1.pk,
'name': 'Test Service 6',
'protocol': IP_PROTOCOL_TCP,
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
'port': 6,
},
]
@ -1076,7 +1076,7 @@ class ServiceTest(APITestCase):
data = {
'device': self.device2.pk,
'name': 'Test Service X',
'protocol': IP_PROTOCOL_UDP,
'protocol': ServiceProtocolChoices.PROTOCOL_UDP,
'port': 99,
}

View File

@ -2,7 +2,7 @@ import netaddr
from django.core.exceptions import ValidationError
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
@ -61,5 +61,5 @@ class TestIPAddress(TestCase):
@override_settings(ENFORCE_GLOBAL_UNIQUE=True)
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=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=IPAddressRoleChoices.ROLE_VIP)

View File

@ -5,7 +5,7 @@ from django.test import Client, TestCase
from django.urls import reverse
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 utilities.testing import create_test_user
@ -264,9 +264,9 @@ class ServiceTestCase(TestCase):
device.save()
Service.objects.bulk_create([
Service(device=device, name='Service 1', protocol=IP_PROTOCOL_TCP, port=101),
Service(device=device, name='Service 2', protocol=IP_PROTOCOL_TCP, port=102),
Service(device=device, name='Service 3', protocol=IP_PROTOCOL_TCP, port=103),
Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=101),
Service(device=device, name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=102),
Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=103),
])
def test_service_list(self):

View File

@ -14,7 +14,7 @@ from utilities.views import (
)
from virtualization.models import VirtualMachine
from . import filters, forms, tables
from .constants import *
from .choices import *
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).
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(
[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(
[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.
@ -665,8 +665,8 @@ class IPAddressView(PermissionRequiredMixin, View):
'nat_inside', 'interface__device'
)
# Exclude anycast IPs if this IP is anycast
if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST)
if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
# 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.viewsets import ModelViewSet as _ModelViewSet, ViewSet
from utilities.choices import ChoiceSet
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>}.
"""
def __init__(self, choices, **kwargs):
self.choiceset = choices
self._choices = dict()
for k, v in choices:
# Unpack grouped choices
for k, v in choices:
if type(v) in [list, tuple]:
for k2, v2 in v:
self._choices[k2] = v2
else:
self._choices[k] = v
super().__init__(**kwargs)
def to_representation(self, obj):
@ -81,6 +85,11 @@ class ChoiceField(Field):
('value', 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
def to_internal_value(self, data):
@ -104,6 +113,10 @@ class ChoiceField(Field):
try:
if data in self._choices:
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
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.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):
@ -165,12 +165,18 @@ def to_meters(length, unit):
length = int(length)
if length < 0:
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
if unit == LENGTH_UNIT_CENTIMETER:
if unit == CableLengthUnitChoices.UNIT_CENTIMETER:
return length / 100
if unit == LENGTH_UNIT_FOOT:
if unit == CableLengthUnitChoices.UNIT_FOOT:
return length * 0.3048
if unit == LENGTH_UNIT_INCH:
if unit == CableLengthUnitChoices.UNIT_INCH:
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 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 extras.api.customfields import CustomFieldModelSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
from ipam.models import VLAN
from tenancy.api.nested_serializers import NestedTenantSerializer
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 .nested_serializers import *
@ -57,7 +57,7 @@ class ClusterSerializer(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)
cluster = NestedClusterSerializer()
role = NestedDeviceRoleSerializer(required=False, allow_null=True)
@ -98,8 +98,8 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
virtual_machine = NestedVirtualMachineSerializer()
type = ChoiceField(choices=IFACE_TYPE_CHOICES, default=IFACE_TYPE_VIRTUAL, required=False)
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
type = ChoiceField(choices=InterfaceTypeChoices, default=InterfaceTypeChoices.TYPE_VIRTUAL, required=False)
mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_null=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
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 (
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
)
from .constants import *
from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -96,7 +96,7 @@ class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdate
label='Search',
)
status = django_filters.MultipleChoiceFilter(
choices=VM_STATUS_CHOICES,
choices=VirtualMachineStatusChoices,
null_value=None
)
cluster_group_id = django_filters.ModelMultipleChoiceFilter(

View File

@ -2,7 +2,7 @@ from django import forms
from django.core.exceptions import ValidationError
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.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
@ -15,11 +15,11 @@ from utilities.forms import (
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
SmallTextarea, StaticSelect2, StaticSelect2Multiple
)
from .constants import *
from .choices import *
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
VIFACE_TYPE_CHOICES = (
(IFACE_TYPE_VIRTUAL, 'Virtual'),
(InterfaceTypeChoices.TYPE_VIRTUAL, 'Virtual'),
)
@ -428,7 +428,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class VirtualMachineCSVForm(forms.ModelForm):
status = CSVChoiceField(
choices=VM_STATUS_CHOICES,
choices=VirtualMachineStatusChoices,
required=False,
help_text='Operational status of device'
)
@ -481,7 +481,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
widget=forms.MultipleHiddenInput()
)
status = forms.ChoiceField(
choices=add_blank_choice(VM_STATUS_CHOICES),
choices=add_blank_choice(VirtualMachineStatusChoices),
required=False,
initial='',
widget=StaticSelect2(),
@ -612,7 +612,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
)
)
status = forms.MultipleChoiceField(
choices=VM_STATUS_CHOICES,
choices=VirtualMachineStatusChoices,
required=False,
widget=StaticSelect2Multiple()
)
@ -717,13 +717,13 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
tagged_vlans = self.cleaned_data['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({
'mode': "An access interface cannot have tagged VLANs assigned."
})
# 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'] = []
@ -733,7 +733,7 @@ class InterfaceCreateForm(ComponentForm):
)
type = forms.ChoiceField(
choices=VIFACE_TYPE_CHOICES,
initial=IFACE_TYPE_VIRTUAL,
initial=InterfaceTypeChoices.TYPE_VIRTUAL,
widget=forms.HiddenInput()
)
enabled = forms.BooleanField(
@ -754,7 +754,7 @@ class InterfaceCreateForm(ComponentForm):
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES),
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect2(),
)
@ -839,7 +839,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
required=False
)
mode = forms.ChoiceField(
choices=add_blank_choice(IFACE_MODE_CHOICES),
choices=add_blank_choice(InterfaceModeChoices),
required=False,
widget=StaticSelect2()
)
@ -918,7 +918,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
type = forms.ChoiceField(
choices=VIFACE_TYPE_CHOICES,
initial=IFACE_TYPE_VIRTUAL,
initial=InterfaceTypeChoices.TYPE_VIRTUAL,
widget=forms.HiddenInput()
)
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 extras.models import ConfigContextModel, CustomFieldModel, TaggedItem
from utilities.models import ChangeLoggedModel
from .constants import *
from .choices import *
#
@ -193,9 +193,10 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
max_length=64,
unique=True
)
status = models.PositiveSmallIntegerField(
choices=VM_STATUS_CHOICES,
default=DEVICE_STATUS_ACTIVE,
status = models.CharField(
max_length=50,
choices=VirtualMachineStatusChoices,
default=VirtualMachineStatusChoices.STATUS_ACTIVE,
verbose_name='Status'
)
role = models.ForeignKey(
@ -252,6 +253,12 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
]
STATUS_CLASS_MAP = {
'active': 'success',
'offline': 'warning',
'staged': 'primary',
}
class Meta:
ordering = ['name']
@ -294,7 +301,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
)
def get_status_class(self):
return VM_STATUS_CLASSES[self.status]
return self.STATUS_CLASS_MAP.get(self.status)
@property
def primary_ip(self):

View File

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