mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Merge pull request #3732 from netbox-community/3569-api-choice-slugs
Replace API integer API choice values with slugs
This commit is contained in:
commit
dcb2c4722c
@ -1,7 +1,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||||
|
|
||||||
from circuits.constants import CIRCUIT_STATUS_CHOICES
|
from circuits.choices import CircuitStatusChoices
|
||||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||||
from dcim.api.serializers import ConnectedEndpointSerializer
|
from dcim.api.serializers import ConnectedEndpointSerializer
|
||||||
@ -41,7 +41,7 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
provider = NestedProviderSerializer()
|
provider = NestedProviderSerializer()
|
||||||
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||||
type = NestedCircuitTypeSerializer()
|
type = NestedCircuitTypeSerializer()
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
48
netbox/circuits/choices.py
Normal file
48
netbox/circuits/choices.py
Normal 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')
|
||||||
|
)
|
@ -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'),
|
|
||||||
)
|
|
@ -5,7 +5,7 @@ from dcim.models import Region, Site
|
|||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilter
|
|||||||
label='Circuit type (slug)',
|
label='Circuit type (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=CIRCUIT_STATUS_CHOICES,
|
choices=CircuitStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
@ -9,7 +9,7 @@ from utilities.forms import (
|
|||||||
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
|
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
|
||||||
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
|
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
|
||||||
)
|
)
|
||||||
from .constants import *
|
from .choices import CircuitStatusChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ class CircuitCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=CIRCUIT_STATUS_CHOICES,
|
choices=CircuitStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
@ -235,7 +235,7 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(CIRCUIT_STATUS_CHOICES),
|
choices=add_blank_choice(CircuitStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -292,7 +292,7 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=CIRCUIT_STATUS_CHOICES,
|
choices=CircuitStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
|
39
netbox/circuits/migrations/0016_3569_circuit_fields.py
Normal file
39
netbox/circuits/migrations/0016_3569_circuit_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -3,13 +3,13 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.constants import CONNECTION_STATUS_CHOICES, STATUS_CLASSES
|
from dcim.constants import CONNECTION_STATUS_CHOICES
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from dcim.models import CableTermination
|
from dcim.models import CableTermination
|
||||||
from extras.models import CustomFieldModel, ObjectChange, TaggedItem
|
from extras.models import CustomFieldModel, ObjectChange, TaggedItem
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
from .constants import *
|
from .choices import *
|
||||||
|
|
||||||
|
|
||||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||||
@ -132,9 +132,10 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='circuits'
|
related_name='circuits'
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=CIRCUIT_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=CIRCUIT_STATUS_ACTIVE
|
choices=CircuitStatusChoices,
|
||||||
|
default=CircuitStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
@ -171,6 +172,15 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
|||||||
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
|
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
CircuitStatusChoices.STATUS_DEPROVISIONING: 'warning',
|
||||||
|
CircuitStatusChoices.STATUS_ACTIVE: 'success',
|
||||||
|
CircuitStatusChoices.STATUS_PLANNED: 'info',
|
||||||
|
CircuitStatusChoices.STATUS_PROVISIONING: 'primary',
|
||||||
|
CircuitStatusChoices.STATUS_OFFLINE: 'danger',
|
||||||
|
CircuitStatusChoices.STATUS_DECOMMISSIONED: 'default',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['provider', 'cid']
|
ordering = ['provider', 'cid']
|
||||||
unique_together = ['provider', 'cid']
|
unique_together = ['provider', 'cid']
|
||||||
@ -195,7 +205,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
def _get_termination(self, side):
|
def _get_termination(self, side):
|
||||||
for ct in self.terminations.all():
|
for ct in self.terminations.all():
|
||||||
@ -220,7 +230,7 @@ class CircuitTermination(CableTermination):
|
|||||||
)
|
)
|
||||||
term_side = models.CharField(
|
term_side = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
choices=TERM_SIDE_CHOICES,
|
choices=CircuitTerminationSideChoices,
|
||||||
verbose_name='Termination'
|
verbose_name='Termination'
|
||||||
)
|
)
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from circuits.constants import CIRCUIT_STATUS_ACTIVE, TERM_SIDE_A, TERM_SIDE_Z
|
from circuits.choices import *
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
|
from dcim.models import Site
|
||||||
from extras.constants import GRAPH_TYPE_PROVIDER
|
from extras.constants import GRAPH_TYPE_PROVIDER
|
||||||
from extras.models import Graph
|
from extras.models import Graph
|
||||||
from utilities.testing import APITestCase
|
from utilities.testing import APITestCase
|
||||||
@ -250,7 +250,7 @@ class CircuitTest(APITestCase):
|
|||||||
'cid': 'TEST0004',
|
'cid': 'TEST0004',
|
||||||
'provider': self.provider1.pk,
|
'provider': self.provider1.pk,
|
||||||
'type': self.circuittype1.pk,
|
'type': self.circuittype1.pk,
|
||||||
'status': CIRCUIT_STATUS_ACTIVE,
|
'status': CircuitStatusChoices.STATUS_ACTIVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('circuits-api:circuit-list')
|
url = reverse('circuits-api:circuit-list')
|
||||||
@ -270,19 +270,19 @@ class CircuitTest(APITestCase):
|
|||||||
'cid': 'TEST0004',
|
'cid': 'TEST0004',
|
||||||
'provider': self.provider1.pk,
|
'provider': self.provider1.pk,
|
||||||
'type': self.circuittype1.pk,
|
'type': self.circuittype1.pk,
|
||||||
'status': CIRCUIT_STATUS_ACTIVE,
|
'status': CircuitStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'cid': 'TEST0005',
|
'cid': 'TEST0005',
|
||||||
'provider': self.provider1.pk,
|
'provider': self.provider1.pk,
|
||||||
'type': self.circuittype1.pk,
|
'type': self.circuittype1.pk,
|
||||||
'status': CIRCUIT_STATUS_ACTIVE,
|
'status': CircuitStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'cid': 'TEST0006',
|
'cid': 'TEST0006',
|
||||||
'provider': self.provider1.pk,
|
'provider': self.provider1.pk,
|
||||||
'type': self.circuittype1.pk,
|
'type': self.circuittype1.pk,
|
||||||
'status': CIRCUIT_STATUS_ACTIVE,
|
'status': CircuitStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -336,16 +336,28 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
|
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
|
||||||
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
|
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
|
||||||
self.circuittermination1 = CircuitTermination.objects.create(
|
self.circuittermination1 = CircuitTermination.objects.create(
|
||||||
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
circuit=self.circuit1,
|
||||||
|
term_side=CircuitTerminationSideChoices.SIDE_A,
|
||||||
|
site=self.site1,
|
||||||
|
port_speed=1000000
|
||||||
)
|
)
|
||||||
self.circuittermination2 = CircuitTermination.objects.create(
|
self.circuittermination2 = CircuitTermination.objects.create(
|
||||||
circuit=self.circuit1, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
|
circuit=self.circuit1,
|
||||||
|
term_side=CircuitTerminationSideChoices.SIDE_Z,
|
||||||
|
site=self.site2,
|
||||||
|
port_speed=1000000
|
||||||
)
|
)
|
||||||
self.circuittermination3 = CircuitTermination.objects.create(
|
self.circuittermination3 = CircuitTermination.objects.create(
|
||||||
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
circuit=self.circuit2,
|
||||||
|
term_side=CircuitTerminationSideChoices.SIDE_A,
|
||||||
|
site=self.site1,
|
||||||
|
port_speed=1000000
|
||||||
)
|
)
|
||||||
self.circuittermination4 = CircuitTermination.objects.create(
|
self.circuittermination4 = CircuitTermination.objects.create(
|
||||||
circuit=self.circuit2, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
|
circuit=self.circuit2,
|
||||||
|
term_side=CircuitTerminationSideChoices.SIDE_Z,
|
||||||
|
site=self.site2,
|
||||||
|
port_speed=1000000
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_circuittermination(self):
|
def test_get_circuittermination(self):
|
||||||
@ -366,7 +378,7 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
|
|
||||||
data = {
|
data = {
|
||||||
'circuit': self.circuit3.pk,
|
'circuit': self.circuit3.pk,
|
||||||
'term_side': TERM_SIDE_A,
|
'term_side': CircuitTerminationSideChoices.SIDE_A,
|
||||||
'site': self.site1.pk,
|
'site': self.site1.pk,
|
||||||
'port_speed': 1000000,
|
'port_speed': 1000000,
|
||||||
}
|
}
|
||||||
@ -385,12 +397,15 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
def test_update_circuittermination(self):
|
def test_update_circuittermination(self):
|
||||||
|
|
||||||
circuittermination5 = CircuitTermination.objects.create(
|
circuittermination5 = CircuitTermination.objects.create(
|
||||||
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
circuit=self.circuit3,
|
||||||
|
term_side=CircuitTerminationSideChoices.SIDE_A,
|
||||||
|
site=self.site1,
|
||||||
|
port_speed=1000000
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'circuit': self.circuit3.pk,
|
'circuit': self.circuit3.pk,
|
||||||
'term_side': TERM_SIDE_Z,
|
'term_side': CircuitTerminationSideChoices.SIDE_Z,
|
||||||
'site': self.site2.pk,
|
'site': self.site2.pk,
|
||||||
'port_speed': 1000000,
|
'port_speed': 1000000,
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ from utilities.views import (
|
|||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .constants import TERM_SIDE_A, TERM_SIDE_Z
|
from .choices import CircuitTerminationSideChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
|
|
||||||
@ -151,12 +151,12 @@ class CircuitView(PermissionRequiredMixin, View):
|
|||||||
termination_a = CircuitTermination.objects.prefetch_related(
|
termination_a = CircuitTermination.objects.prefetch_related(
|
||||||
'site__region', 'connected_endpoint__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_A
|
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
|
||||||
).first()
|
).first()
|
||||||
termination_z = CircuitTermination.objects.prefetch_related(
|
termination_z = CircuitTermination.objects.prefetch_related(
|
||||||
'site__region', 'connected_endpoint__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_Z
|
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
return render(request, 'circuits/circuit.html', {
|
return render(request, 'circuits/circuit.html', {
|
||||||
@ -212,8 +212,12 @@ class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
def circuit_terminations_swap(request, pk):
|
def circuit_terminations_swap(request, pk):
|
||||||
|
|
||||||
circuit = get_object_or_404(Circuit, pk=pk)
|
circuit = get_object_or_404(Circuit, pk=pk)
|
||||||
termination_a = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_A).first()
|
termination_a = CircuitTermination.objects.filter(
|
||||||
termination_z = CircuitTermination.objects.filter(circuit=circuit, term_side=TERM_SIDE_Z).first()
|
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
|
||||||
|
).first()
|
||||||
|
termination_z = CircuitTermination.objects.filter(
|
||||||
|
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
|
||||||
|
).first()
|
||||||
if not termination_a and not termination_z:
|
if not termination_a and not termination_z:
|
||||||
messages.error(request, "No terminations have been defined for circuit {}.".format(circuit))
|
messages.error(request, "No terminations have been defined for circuit {}.".format(circuit))
|
||||||
return redirect('circuits:circuit', pk=circuit.pk)
|
return redirect('circuits:circuit', pk=circuit.pk)
|
||||||
|
@ -68,7 +68,7 @@ class RegionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=SiteStatusChoices, required=False)
|
||||||
region = NestedRegionSerializer(required=False, allow_null=True)
|
region = NestedRegionSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
time_zone = TimeZoneField(required=False)
|
time_zone = TimeZoneField(required=False)
|
||||||
@ -115,11 +115,11 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=RackStatusChoices, required=False)
|
||||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||||
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
type = ChoiceField(choices=RackTypeChoices, required=False, allow_null=True)
|
||||||
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
width = ChoiceField(choices=RackWidthChoices, required=False)
|
||||||
outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False)
|
outer_unit = ChoiceField(choices=RackDimensionUnitChoices, required=False)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
powerfeed_count = serializers.IntegerField(read_only=True)
|
powerfeed_count = serializers.IntegerField(read_only=True)
|
||||||
@ -187,7 +187,7 @@ class ManufacturerSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
manufacturer = NestedManufacturerSerializer()
|
manufacturer = NestedManufacturerSerializer()
|
||||||
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
|
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, required=False, allow_null=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=PowerPortTypes.CHOICES,
|
choices=PowerPortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -238,14 +238,14 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
power_port = PowerPortTemplateSerializer(
|
power_port = PowerPortTemplateSerializer(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
feed_leg = ChoiceField(
|
feed_leg = ChoiceField(
|
||||||
choices=POWERFEED_LEG_CHOICES,
|
choices=PowerOutletFeedLegChoices,
|
||||||
required=False,
|
required=False,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
@ -257,7 +257,7 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(choices=IFACE_TYPE_CHOICES, required=False)
|
type = ChoiceField(choices=InterfaceTypeChoices, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
@ -266,7 +266,7 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
type = ChoiceField(choices=PortTypeChoices)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
@ -275,7 +275,7 @@ class RearPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
type = ChoiceField(choices=PortTypeChoices)
|
||||||
rear_port = NestedRearPortTemplateSerializer()
|
rear_port = NestedRearPortTemplateSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -324,8 +324,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
platform = NestedPlatformSerializer(required=False, allow_null=True)
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
rack = NestedRackSerializer(required=False, allow_null=True)
|
rack = NestedRackSerializer(required=False, allow_null=True)
|
||||||
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
|
face = ChoiceField(choices=DeviceFaceChoices, required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=DeviceStatusChoices, required=False)
|
||||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
@ -388,7 +388,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
@ -405,7 +405,7 @@ class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer)
|
|||||||
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
@ -422,14 +422,14 @@ class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
power_port = NestedPowerPortSerializer(
|
power_port = NestedPowerPortSerializer(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
feed_leg = ChoiceField(
|
feed_leg = ChoiceField(
|
||||||
choices=POWERFEED_LEG_CHOICES,
|
choices=PowerOutletFeedLegChoices,
|
||||||
required=False,
|
required=False,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
@ -451,7 +451,7 @@ class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=PowerPortTypes.CHOICES,
|
choices=PowerPortTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
@ -467,9 +467,9 @@ class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
|
|
||||||
class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(choices=IFACE_TYPE_CHOICES, required=False)
|
type = ChoiceField(choices=InterfaceTypeChoices, required=False)
|
||||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||||
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_null=True)
|
||||||
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||||
tagged_vlans = SerializedPKRelatedField(
|
tagged_vlans = SerializedPKRelatedField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
@ -511,7 +511,7 @@ class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
|
|
||||||
class RearPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
class RearPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
type = ChoiceField(choices=PortTypeChoices)
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
@ -533,7 +533,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class FrontPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
class FrontPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
type = ChoiceField(choices=PortTypeChoices)
|
||||||
rear_port = FrontPortRearPortSerializer()
|
rear_port = FrontPortRearPortSerializer()
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
@ -586,7 +586,7 @@ class CableSerializer(ValidatedModelSerializer):
|
|||||||
termination_a = serializers.SerializerMethodField(read_only=True)
|
termination_a = serializers.SerializerMethodField(read_only=True)
|
||||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||||
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||||
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False, allow_null=True)
|
length_unit = ChoiceField(choices=CableLengthUnitChoices, required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
@ -691,20 +691,20 @@ class PowerFeedSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
default=None
|
default=None
|
||||||
)
|
)
|
||||||
type = ChoiceField(
|
type = ChoiceField(
|
||||||
choices=POWERFEED_TYPE_CHOICES,
|
choices=PowerFeedTypeChoices,
|
||||||
default=POWERFEED_TYPE_PRIMARY
|
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
status = ChoiceField(
|
status = ChoiceField(
|
||||||
choices=POWERFEED_STATUS_CHOICES,
|
choices=PowerFeedStatusChoices,
|
||||||
default=POWERFEED_STATUS_ACTIVE
|
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
supply = ChoiceField(
|
supply = ChoiceField(
|
||||||
choices=POWERFEED_SUPPLY_CHOICES,
|
choices=PowerFeedSupplyChoices,
|
||||||
default=POWERFEED_SUPPLY_AC
|
default=PowerFeedSupplyChoices.SUPPLY_AC
|
||||||
)
|
)
|
||||||
phase = ChoiceField(
|
phase = ChoiceField(
|
||||||
choices=POWERFEED_PHASE_CHOICES,
|
choices=PowerFeedPhaseChoices,
|
||||||
default=POWERFEED_PHASE_SINGLE
|
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
||||||
)
|
)
|
||||||
tags = TagListSerializerField(
|
tags = TagListSerializerField(
|
||||||
required=False
|
required=False
|
||||||
|
@ -1,14 +1,187 @@
|
|||||||
from .constants import *
|
from utilities.choices import ChoiceSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Console port type values
|
# Sites
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortTypes:
|
class SiteStatusChoices(ChoiceSet):
|
||||||
"""
|
|
||||||
ConsolePort/ConsoleServerPort.type slugs
|
STATUS_ACTIVE = 'active'
|
||||||
"""
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_RETIRED = 'retired'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(STATUS_ACTIVE, 'Active'),
|
||||||
|
(STATUS_PLANNED, 'Planned'),
|
||||||
|
(STATUS_RETIRED, 'Retired'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
STATUS_ACTIVE: 1,
|
||||||
|
STATUS_PLANNED: 2,
|
||||||
|
STATUS_RETIRED: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Racks
|
||||||
|
#
|
||||||
|
|
||||||
|
class RackTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
|
TYPE_2POST = '2-post-frame'
|
||||||
|
TYPE_4POST = '4-post-frame'
|
||||||
|
TYPE_CABINET = '4-post-cabinet'
|
||||||
|
TYPE_WALLFRAME = 'wall-frame'
|
||||||
|
TYPE_WALLCABINET = 'wall-cabinet'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(TYPE_2POST, '2-post frame'),
|
||||||
|
(TYPE_4POST, '4-post frame'),
|
||||||
|
(TYPE_CABINET, '4-post cabinet'),
|
||||||
|
(TYPE_WALLFRAME, 'Wall-mounted frame'),
|
||||||
|
(TYPE_WALLCABINET, 'Wall-mounted cabinet'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
TYPE_2POST: 100,
|
||||||
|
TYPE_4POST: 200,
|
||||||
|
TYPE_CABINET: 300,
|
||||||
|
TYPE_WALLFRAME: 1000,
|
||||||
|
TYPE_WALLCABINET: 1100,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RackWidthChoices(ChoiceSet):
|
||||||
|
|
||||||
|
WIDTH_19IN = 19
|
||||||
|
WIDTH_23IN = 23
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(WIDTH_19IN, '19 inches'),
|
||||||
|
(WIDTH_23IN, '23 inches'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RackStatusChoices(ChoiceSet):
|
||||||
|
|
||||||
|
STATUS_RESERVED = 'reserved'
|
||||||
|
STATUS_AVAILABLE = 'available'
|
||||||
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_ACTIVE = 'active'
|
||||||
|
STATUS_DEPRECATED = 'deprecated'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(STATUS_RESERVED, 'Reserved'),
|
||||||
|
(STATUS_AVAILABLE, 'Available'),
|
||||||
|
(STATUS_PLANNED, 'Planned'),
|
||||||
|
(STATUS_ACTIVE, 'Active'),
|
||||||
|
(STATUS_DEPRECATED, 'Deprecated'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
STATUS_RESERVED: 0,
|
||||||
|
STATUS_AVAILABLE: 1,
|
||||||
|
STATUS_PLANNED: 2,
|
||||||
|
STATUS_ACTIVE: 3,
|
||||||
|
STATUS_DEPRECATED: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RackDimensionUnitChoices(ChoiceSet):
|
||||||
|
|
||||||
|
UNIT_MILLIMETER = 'mm'
|
||||||
|
UNIT_INCH = 'in'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(UNIT_MILLIMETER, 'Millimeters'),
|
||||||
|
(UNIT_INCH, 'Inches'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
UNIT_MILLIMETER: 1000,
|
||||||
|
UNIT_INCH: 2000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# DeviceTypes
|
||||||
|
#
|
||||||
|
|
||||||
|
class SubdeviceRoleChoices(ChoiceSet):
|
||||||
|
|
||||||
|
ROLE_PARENT = 'parent'
|
||||||
|
ROLE_CHILD = 'child'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(ROLE_PARENT, 'Parent'),
|
||||||
|
(ROLE_CHILD, 'Child'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
ROLE_PARENT: True,
|
||||||
|
ROLE_CHILD: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Devices
|
||||||
|
#
|
||||||
|
|
||||||
|
class DeviceFaceChoices(ChoiceSet):
|
||||||
|
|
||||||
|
FACE_FRONT = 'front'
|
||||||
|
FACE_REAR = 'rear'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(FACE_FRONT, 'Front'),
|
||||||
|
(FACE_REAR, 'Rear'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
FACE_FRONT: 0,
|
||||||
|
FACE_REAR: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceStatusChoices(ChoiceSet):
|
||||||
|
|
||||||
|
STATUS_OFFLINE = 'offline'
|
||||||
|
STATUS_ACTIVE = 'active'
|
||||||
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_STAGED = 'staged'
|
||||||
|
STATUS_FAILED = 'failed'
|
||||||
|
STATUS_INVENTORY = 'inventory'
|
||||||
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(STATUS_OFFLINE, 'Offline'),
|
||||||
|
(STATUS_ACTIVE, 'Active'),
|
||||||
|
(STATUS_PLANNED, 'Planned'),
|
||||||
|
(STATUS_STAGED, 'Staged'),
|
||||||
|
(STATUS_FAILED, 'Failed'),
|
||||||
|
(STATUS_INVENTORY, 'Inventory'),
|
||||||
|
(STATUS_DECOMMISSIONING, 'Decommissioning'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
STATUS_OFFLINE: 0,
|
||||||
|
STATUS_ACTIVE: 1,
|
||||||
|
STATUS_PLANNED: 2,
|
||||||
|
STATUS_STAGED: 3,
|
||||||
|
STATUS_FAILED: 4,
|
||||||
|
STATUS_INVENTORY: 5,
|
||||||
|
STATUS_DECOMMISSIONING: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ConsolePorts
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConsolePortTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
TYPE_DE9 = 'de-9'
|
TYPE_DE9 = 'de-9'
|
||||||
TYPE_DB25 = 'db-25'
|
TYPE_DB25 = 'db-25'
|
||||||
TYPE_RJ45 = 'rj-45'
|
TYPE_RJ45 = 'rj-45'
|
||||||
@ -43,10 +216,11 @@ class ConsolePortTypes:
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Power port types
|
# PowerPorts
|
||||||
#
|
#
|
||||||
|
|
||||||
class PowerPortTypes:
|
class PowerPortTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
# TODO: Add more power port types
|
# TODO: Add more power port types
|
||||||
# IEC 60320
|
# IEC 60320
|
||||||
TYPE_IEC_C6 = 'iec-60320-c6'
|
TYPE_IEC_C6 = 'iec-60320-c6'
|
||||||
@ -130,10 +304,11 @@ class PowerPortTypes:
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Power outlet types
|
# PowerOutlets
|
||||||
#
|
#
|
||||||
|
|
||||||
class PowerOutletTypes:
|
class PowerOutletTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
# TODO: Add more power outlet types
|
# TODO: Add more power outlet types
|
||||||
# IEC 60320
|
# IEC 60320
|
||||||
TYPE_IEC_C5 = 'iec-60320-c5'
|
TYPE_IEC_C5 = 'iec-60320-c5'
|
||||||
@ -216,14 +391,31 @@ class PowerOutletTypes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletFeedLegChoices(ChoiceSet):
|
||||||
|
|
||||||
|
FEED_LEG_A = 'A'
|
||||||
|
FEED_LEG_B = 'B'
|
||||||
|
FEED_LEG_C = 'C'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(FEED_LEG_A, 'A'),
|
||||||
|
(FEED_LEG_B, 'B'),
|
||||||
|
(FEED_LEG_C, 'C'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
FEED_LEG_A: 1,
|
||||||
|
FEED_LEG_B: 2,
|
||||||
|
FEED_LEG_C: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interface type values
|
# Interfaces
|
||||||
#
|
#
|
||||||
|
|
||||||
class InterfaceTypes:
|
class InterfaceTypeChoices(ChoiceSet):
|
||||||
"""
|
|
||||||
Interface.type slugs
|
|
||||||
"""
|
|
||||||
# Virtual
|
# Virtual
|
||||||
TYPE_VIRTUAL = 'virtual'
|
TYPE_VIRTUAL = 'virtual'
|
||||||
TYPE_LAG = 'lag'
|
TYPE_LAG = 'lag'
|
||||||
@ -315,7 +507,7 @@ class InterfaceTypes:
|
|||||||
# Other
|
# Other
|
||||||
TYPE_OTHER = 'other'
|
TYPE_OTHER = 'other'
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
'Virtual interfaces',
|
'Virtual interfaces',
|
||||||
(
|
(
|
||||||
@ -444,93 +636,105 @@ class InterfaceTypes:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
LEGACY_MAP = {
|
||||||
def slug_to_integer(cls, slug):
|
TYPE_VIRTUAL: 0,
|
||||||
"""
|
TYPE_LAG: 200,
|
||||||
Provide backward-compatible mapping of the type slug to integer.
|
TYPE_100ME_FIXED: 800,
|
||||||
"""
|
TYPE_1GE_FIXED: 1000,
|
||||||
return {
|
TYPE_1GE_GBIC: 1050,
|
||||||
# Slug: integer
|
TYPE_1GE_SFP: 1100,
|
||||||
cls.TYPE_VIRTUAL: IFACE_TYPE_VIRTUAL,
|
TYPE_2GE_FIXED: 1120,
|
||||||
cls.TYPE_LAG: IFACE_TYPE_LAG,
|
TYPE_5GE_FIXED: 1130,
|
||||||
cls.TYPE_100ME_FIXED: IFACE_TYPE_100ME_FIXED,
|
TYPE_10GE_FIXED: 1150,
|
||||||
cls.TYPE_1GE_FIXED: IFACE_TYPE_1GE_FIXED,
|
TYPE_10GE_CX4: 1170,
|
||||||
cls.TYPE_1GE_GBIC: IFACE_TYPE_1GE_GBIC,
|
TYPE_10GE_SFP_PLUS: 1200,
|
||||||
cls.TYPE_1GE_SFP: IFACE_TYPE_1GE_SFP,
|
TYPE_10GE_XFP: 1300,
|
||||||
cls.TYPE_2GE_FIXED: IFACE_TYPE_2GE_FIXED,
|
TYPE_10GE_XENPAK: 1310,
|
||||||
cls.TYPE_5GE_FIXED: IFACE_TYPE_5GE_FIXED,
|
TYPE_10GE_X2: 1320,
|
||||||
cls.TYPE_10GE_FIXED: IFACE_TYPE_10GE_FIXED,
|
TYPE_25GE_SFP28: 1350,
|
||||||
cls.TYPE_10GE_CX4: IFACE_TYPE_10GE_CX4,
|
TYPE_40GE_QSFP_PLUS: 1400,
|
||||||
cls.TYPE_10GE_SFP_PLUS: IFACE_TYPE_10GE_SFP_PLUS,
|
TYPE_50GE_QSFP28: 1420,
|
||||||
cls.TYPE_10GE_XFP: IFACE_TYPE_10GE_XFP,
|
TYPE_100GE_CFP: 1500,
|
||||||
cls.TYPE_10GE_XENPAK: IFACE_TYPE_10GE_XENPAK,
|
TYPE_100GE_CFP2: 1510,
|
||||||
cls.TYPE_10GE_X2: IFACE_TYPE_10GE_X2,
|
TYPE_100GE_CFP4: 1520,
|
||||||
cls.TYPE_25GE_SFP28: IFACE_TYPE_25GE_SFP28,
|
TYPE_100GE_CPAK: 1550,
|
||||||
cls.TYPE_40GE_QSFP_PLUS: IFACE_TYPE_40GE_QSFP_PLUS,
|
TYPE_100GE_QSFP28: 1600,
|
||||||
cls.TYPE_50GE_QSFP28: IFACE_TYPE_50GE_QSFP28,
|
TYPE_200GE_CFP2: 1650,
|
||||||
cls.TYPE_100GE_CFP: IFACE_TYPE_100GE_CFP,
|
TYPE_200GE_QSFP56: 1700,
|
||||||
cls.TYPE_100GE_CFP2: IFACE_TYPE_100GE_CFP2,
|
TYPE_400GE_QSFP_DD: 1750,
|
||||||
cls.TYPE_100GE_CFP4: IFACE_TYPE_100GE_CFP4,
|
TYPE_400GE_OSFP: 1800,
|
||||||
cls.TYPE_100GE_CPAK: IFACE_TYPE_100GE_CPAK,
|
TYPE_80211A: 2600,
|
||||||
cls.TYPE_100GE_QSFP28: IFACE_TYPE_100GE_QSFP28,
|
TYPE_80211G: 2610,
|
||||||
cls.TYPE_200GE_CFP2: IFACE_TYPE_200GE_CFP2,
|
TYPE_80211N: 2620,
|
||||||
cls.TYPE_200GE_QSFP56: IFACE_TYPE_200GE_QSFP56,
|
TYPE_80211AC: 2630,
|
||||||
cls.TYPE_400GE_QSFP_DD: IFACE_TYPE_400GE_QSFP_DD,
|
TYPE_80211AD: 2640,
|
||||||
cls.TYPE_80211A: IFACE_TYPE_80211A,
|
TYPE_GSM: 2810,
|
||||||
cls.TYPE_80211G: IFACE_TYPE_80211G,
|
TYPE_CDMA: 2820,
|
||||||
cls.TYPE_80211N: IFACE_TYPE_80211N,
|
TYPE_LTE: 2830,
|
||||||
cls.TYPE_80211AC: IFACE_TYPE_80211AC,
|
TYPE_SONET_OC3: 6100,
|
||||||
cls.TYPE_80211AD: IFACE_TYPE_80211AD,
|
TYPE_SONET_OC12: 6200,
|
||||||
cls.TYPE_GSM: IFACE_TYPE_GSM,
|
TYPE_SONET_OC48: 6300,
|
||||||
cls.TYPE_CDMA: IFACE_TYPE_CDMA,
|
TYPE_SONET_OC192: 6400,
|
||||||
cls.TYPE_LTE: IFACE_TYPE_LTE,
|
TYPE_SONET_OC768: 6500,
|
||||||
cls.TYPE_SONET_OC3: IFACE_TYPE_SONET_OC3,
|
TYPE_SONET_OC1920: 6600,
|
||||||
cls.TYPE_SONET_OC12: IFACE_TYPE_SONET_OC12,
|
TYPE_SONET_OC3840: 6700,
|
||||||
cls.TYPE_SONET_OC48: IFACE_TYPE_SONET_OC48,
|
TYPE_1GFC_SFP: 3010,
|
||||||
cls.TYPE_SONET_OC192: IFACE_TYPE_SONET_OC192,
|
TYPE_2GFC_SFP: 3020,
|
||||||
cls.TYPE_SONET_OC768: IFACE_TYPE_SONET_OC768,
|
TYPE_4GFC_SFP: 3040,
|
||||||
cls.TYPE_SONET_OC1920: IFACE_TYPE_SONET_OC1920,
|
TYPE_8GFC_SFP_PLUS: 3080,
|
||||||
cls.TYPE_SONET_OC3840: IFACE_TYPE_SONET_OC3840,
|
TYPE_16GFC_SFP_PLUS: 3160,
|
||||||
cls.TYPE_1GFC_SFP: IFACE_TYPE_1GFC_SFP,
|
TYPE_32GFC_SFP28: 3320,
|
||||||
cls.TYPE_2GFC_SFP: IFACE_TYPE_2GFC_SFP,
|
TYPE_128GFC_QSFP28: 3400,
|
||||||
cls.TYPE_4GFC_SFP: IFACE_TYPE_4GFC_SFP,
|
TYPE_INFINIBAND_SDR: 7010,
|
||||||
cls.TYPE_8GFC_SFP_PLUS: IFACE_TYPE_8GFC_SFP_PLUS,
|
TYPE_INFINIBAND_DDR: 7020,
|
||||||
cls.TYPE_16GFC_SFP_PLUS: IFACE_TYPE_16GFC_SFP_PLUS,
|
TYPE_INFINIBAND_QDR: 7030,
|
||||||
cls.TYPE_32GFC_SFP28: IFACE_TYPE_32GFC_SFP28,
|
TYPE_INFINIBAND_FDR10: 7040,
|
||||||
cls.TYPE_128GFC_QSFP28: IFACE_TYPE_128GFC_QSFP28,
|
TYPE_INFINIBAND_FDR: 7050,
|
||||||
cls.TYPE_INFINIBAND_SDR: IFACE_TYPE_INFINIBAND_SDR,
|
TYPE_INFINIBAND_EDR: 7060,
|
||||||
cls.TYPE_INFINIBAND_DDR: IFACE_TYPE_INFINIBAND_DDR,
|
TYPE_INFINIBAND_HDR: 7070,
|
||||||
cls.TYPE_INFINIBAND_QDR: IFACE_TYPE_INFINIBAND_QDR,
|
TYPE_INFINIBAND_NDR: 7080,
|
||||||
cls.TYPE_INFINIBAND_FDR10: IFACE_TYPE_INFINIBAND_FDR10,
|
TYPE_INFINIBAND_XDR: 7090,
|
||||||
cls.TYPE_INFINIBAND_FDR: IFACE_TYPE_INFINIBAND_FDR,
|
TYPE_T1: 4000,
|
||||||
cls.TYPE_INFINIBAND_EDR: IFACE_TYPE_INFINIBAND_EDR,
|
TYPE_E1: 4010,
|
||||||
cls.TYPE_INFINIBAND_HDR: IFACE_TYPE_INFINIBAND_HDR,
|
TYPE_T3: 4040,
|
||||||
cls.TYPE_INFINIBAND_NDR: IFACE_TYPE_INFINIBAND_NDR,
|
TYPE_E3: 4050,
|
||||||
cls.TYPE_INFINIBAND_XDR: IFACE_TYPE_INFINIBAND_XDR,
|
TYPE_STACKWISE: 5000,
|
||||||
cls.TYPE_T1: IFACE_TYPE_T1,
|
TYPE_STACKWISE_PLUS: 5050,
|
||||||
cls.TYPE_E1: IFACE_TYPE_E1,
|
TYPE_FLEXSTACK: 5100,
|
||||||
cls.TYPE_T3: IFACE_TYPE_T3,
|
TYPE_FLEXSTACK_PLUS: 5150,
|
||||||
cls.TYPE_E3: IFACE_TYPE_E3,
|
TYPE_JUNIPER_VCP: 5200,
|
||||||
cls.TYPE_STACKWISE: IFACE_TYPE_STACKWISE,
|
TYPE_SUMMITSTACK: 5300,
|
||||||
cls.TYPE_STACKWISE_PLUS: IFACE_TYPE_STACKWISE_PLUS,
|
TYPE_SUMMITSTACK128: 5310,
|
||||||
cls.TYPE_FLEXSTACK: IFACE_TYPE_FLEXSTACK,
|
TYPE_SUMMITSTACK256: 5320,
|
||||||
cls.TYPE_FLEXSTACK_PLUS: IFACE_TYPE_FLEXSTACK_PLUS,
|
TYPE_SUMMITSTACK512: 5330,
|
||||||
cls.TYPE_JUNIPER_VCP: IFACE_TYPE_JUNIPER_VCP,
|
}
|
||||||
cls.TYPE_SUMMITSTACK: IFACE_TYPE_SUMMITSTACK,
|
|
||||||
cls.TYPE_SUMMITSTACK128: IFACE_TYPE_SUMMITSTACK128,
|
|
||||||
cls.TYPE_SUMMITSTACK256: IFACE_TYPE_SUMMITSTACK256,
|
class InterfaceModeChoices(ChoiceSet):
|
||||||
cls.TYPE_SUMMITSTACK512: IFACE_TYPE_SUMMITSTACK512,
|
|
||||||
}.get(slug)
|
MODE_ACCESS = 'access'
|
||||||
|
MODE_TAGGED = 'tagged'
|
||||||
|
MODE_TAGGED_ALL = 'tagged-all'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(MODE_ACCESS, 'Access'),
|
||||||
|
(MODE_TAGGED, 'Tagged'),
|
||||||
|
(MODE_TAGGED_ALL, 'Tagged (All)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
MODE_ACCESS: 100,
|
||||||
|
MODE_TAGGED: 200,
|
||||||
|
MODE_TAGGED_ALL: 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Port type values
|
# FrontPorts/RearPorts
|
||||||
#
|
#
|
||||||
|
|
||||||
class PortTypes:
|
class PortTypeChoices(ChoiceSet):
|
||||||
"""
|
|
||||||
FrontPort/RearPort.type slugs
|
|
||||||
"""
|
|
||||||
TYPE_8P8C = '8p8c'
|
TYPE_8P8C = '8p8c'
|
||||||
TYPE_110_PUNCH = '110-punch'
|
TYPE_110_PUNCH = '110-punch'
|
||||||
TYPE_BNC = 'bnc'
|
TYPE_BNC = 'bnc'
|
||||||
@ -545,7 +749,7 @@ class PortTypes:
|
|||||||
TYPE_LSH = 'lsh'
|
TYPE_LSH = 'lsh'
|
||||||
TYPE_LSH_APC = 'lsh-apc'
|
TYPE_LSH_APC = 'lsh-apc'
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
CHOICES = (
|
||||||
(
|
(
|
||||||
'Copper',
|
'Copper',
|
||||||
(
|
(
|
||||||
@ -571,24 +775,193 @@ class PortTypes:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
LEGACY_MAP = {
|
||||||
def slug_to_integer(cls, slug):
|
TYPE_8P8C: 1000,
|
||||||
"""
|
TYPE_110_PUNCH: 1100,
|
||||||
Provide backward-compatible mapping of the type slug to integer.
|
TYPE_BNC: 1200,
|
||||||
"""
|
TYPE_ST: 2000,
|
||||||
return {
|
TYPE_SC: 2100,
|
||||||
# Slug: integer
|
TYPE_SC_APC: 2110,
|
||||||
cls.TYPE_8P8C: PORT_TYPE_8P8C,
|
TYPE_FC: 2200,
|
||||||
cls.TYPE_110_PUNCH: PORT_TYPE_8P8C,
|
TYPE_LC: 2300,
|
||||||
cls.TYPE_BNC: PORT_TYPE_BNC,
|
TYPE_LC_APC: 2310,
|
||||||
cls.TYPE_ST: PORT_TYPE_ST,
|
TYPE_MTRJ: 2400,
|
||||||
cls.TYPE_SC: PORT_TYPE_SC,
|
TYPE_MPO: 2500,
|
||||||
cls.TYPE_SC_APC: PORT_TYPE_SC_APC,
|
TYPE_LSH: 2600,
|
||||||
cls.TYPE_FC: PORT_TYPE_FC,
|
TYPE_LSH_APC: 2610,
|
||||||
cls.TYPE_LC: PORT_TYPE_LC,
|
}
|
||||||
cls.TYPE_LC_APC: PORT_TYPE_LC_APC,
|
|
||||||
cls.TYPE_MTRJ: PORT_TYPE_MTRJ,
|
|
||||||
cls.TYPE_MPO: PORT_TYPE_MPO,
|
#
|
||||||
cls.TYPE_LSH: PORT_TYPE_LSH,
|
# Cables
|
||||||
cls.TYPE_LSH_APC: PORT_TYPE_LSH_APC,
|
#
|
||||||
}.get(slug)
|
|
||||||
|
class CableTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
|
TYPE_CAT3 = 'cat3'
|
||||||
|
TYPE_CAT5 = 'cat5'
|
||||||
|
TYPE_CAT5E = 'cat5e'
|
||||||
|
TYPE_CAT6 = 'cat6'
|
||||||
|
TYPE_CAT6A = 'cat6a'
|
||||||
|
TYPE_CAT7 = 'cat7'
|
||||||
|
TYPE_DAC_ACTIVE = 'dac-active'
|
||||||
|
TYPE_DAC_PASSIVE = 'dac-passive'
|
||||||
|
TYPE_COAXIAL = 'coaxial'
|
||||||
|
TYPE_MMF = 'mmf'
|
||||||
|
TYPE_MMF_OM1 = 'mmf-om1'
|
||||||
|
TYPE_MMF_OM2 = 'mmf-om2'
|
||||||
|
TYPE_MMF_OM3 = 'mmf-om3'
|
||||||
|
TYPE_MMF_OM4 = 'mmf-om4'
|
||||||
|
TYPE_SMF = 'smf'
|
||||||
|
TYPE_SMF_OS1 = 'smf-os1'
|
||||||
|
TYPE_SMF_OS2 = 'smf-os2'
|
||||||
|
TYPE_AOC = 'aoc'
|
||||||
|
TYPE_POWER = 'power'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(
|
||||||
|
'Copper', (
|
||||||
|
(TYPE_CAT3, 'CAT3'),
|
||||||
|
(TYPE_CAT5, 'CAT5'),
|
||||||
|
(TYPE_CAT5E, 'CAT5e'),
|
||||||
|
(TYPE_CAT6, 'CAT6'),
|
||||||
|
(TYPE_CAT6A, 'CAT6a'),
|
||||||
|
(TYPE_CAT7, 'CAT7'),
|
||||||
|
(TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
|
||||||
|
(TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
|
||||||
|
(TYPE_COAXIAL, 'Coaxial'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Fiber', (
|
||||||
|
(TYPE_MMF, 'Multimode Fiber'),
|
||||||
|
(TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
||||||
|
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
||||||
|
(TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
||||||
|
(TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
||||||
|
(TYPE_SMF, 'Singlemode Fiber'),
|
||||||
|
(TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
|
||||||
|
(TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
|
||||||
|
(TYPE_AOC, 'Active Optical Cabling (AOC)'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(TYPE_POWER, 'Power'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
TYPE_CAT3: 1300,
|
||||||
|
TYPE_CAT5: 1500,
|
||||||
|
TYPE_CAT5E: 1510,
|
||||||
|
TYPE_CAT6: 1600,
|
||||||
|
TYPE_CAT6A: 1610,
|
||||||
|
TYPE_CAT7: 1700,
|
||||||
|
TYPE_DAC_ACTIVE: 1800,
|
||||||
|
TYPE_DAC_PASSIVE: 1810,
|
||||||
|
TYPE_COAXIAL: 1900,
|
||||||
|
TYPE_MMF: 3000,
|
||||||
|
TYPE_MMF_OM1: 3010,
|
||||||
|
TYPE_MMF_OM2: 3020,
|
||||||
|
TYPE_MMF_OM3: 3030,
|
||||||
|
TYPE_MMF_OM4: 3040,
|
||||||
|
TYPE_SMF: 3500,
|
||||||
|
TYPE_SMF_OS1: 3510,
|
||||||
|
TYPE_SMF_OS2: 3520,
|
||||||
|
TYPE_AOC: 3800,
|
||||||
|
TYPE_POWER: 5000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CableLengthUnitChoices(ChoiceSet):
|
||||||
|
|
||||||
|
UNIT_METER = 'm'
|
||||||
|
UNIT_CENTIMETER = 'cm'
|
||||||
|
UNIT_FOOT = 'ft'
|
||||||
|
UNIT_INCH = 'in'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(UNIT_METER, 'Meters'),
|
||||||
|
(UNIT_CENTIMETER, 'Centimeters'),
|
||||||
|
(UNIT_FOOT, 'Feet'),
|
||||||
|
(UNIT_INCH, 'Inches'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
UNIT_METER: 1200,
|
||||||
|
UNIT_CENTIMETER: 1100,
|
||||||
|
UNIT_FOOT: 2100,
|
||||||
|
UNIT_INCH: 2000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# PowerFeeds
|
||||||
|
#
|
||||||
|
|
||||||
|
class PowerFeedStatusChoices(ChoiceSet):
|
||||||
|
|
||||||
|
STATUS_OFFLINE = 'offline'
|
||||||
|
STATUS_ACTIVE = 'active'
|
||||||
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_FAILED = 'failed'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(STATUS_OFFLINE, 'Offline'),
|
||||||
|
(STATUS_ACTIVE, 'Active'),
|
||||||
|
(STATUS_PLANNED, 'Planned'),
|
||||||
|
(STATUS_FAILED, 'Failed'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
STATUS_OFFLINE: 0,
|
||||||
|
STATUS_ACTIVE: 1,
|
||||||
|
STATUS_PLANNED: 2,
|
||||||
|
STATUS_FAILED: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PowerFeedTypeChoices(ChoiceSet):
|
||||||
|
|
||||||
|
TYPE_PRIMARY = 'primary'
|
||||||
|
TYPE_REDUNDANT = 'redundant'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(TYPE_PRIMARY, 'Primary'),
|
||||||
|
(TYPE_REDUNDANT, 'Redundant'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
TYPE_PRIMARY: 1,
|
||||||
|
TYPE_REDUNDANT: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PowerFeedSupplyChoices(ChoiceSet):
|
||||||
|
|
||||||
|
SUPPLY_AC = 'ac'
|
||||||
|
SUPPLY_DC = 'dc'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(SUPPLY_AC, 'AC'),
|
||||||
|
(SUPPLY_DC, 'DC'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
SUPPLY_AC: 1,
|
||||||
|
SUPPLY_DC: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PowerFeedPhaseChoices(ChoiceSet):
|
||||||
|
|
||||||
|
PHASE_SINGLE = 'single-phase'
|
||||||
|
PHASE_3PHASE = 'three-phase'
|
||||||
|
|
||||||
|
CHOICES = (
|
||||||
|
(PHASE_SINGLE, 'Single phase'),
|
||||||
|
(PHASE_3PHASE, 'Three-phase'),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEGACY_MAP = {
|
||||||
|
PHASE_SINGLE: 1,
|
||||||
|
PHASE_3PHASE: 3,
|
||||||
|
}
|
||||||
|
@ -1,381 +1,25 @@
|
|||||||
# Rack types
|
from .choices import InterfaceTypeChoices
|
||||||
RACK_TYPE_2POST = 100
|
|
||||||
RACK_TYPE_4POST = 200
|
|
||||||
RACK_TYPE_CABINET = 300
|
|
||||||
RACK_TYPE_WALLFRAME = 1000
|
|
||||||
RACK_TYPE_WALLCABINET = 1100
|
|
||||||
RACK_TYPE_CHOICES = (
|
|
||||||
(RACK_TYPE_2POST, '2-post frame'),
|
|
||||||
(RACK_TYPE_4POST, '4-post frame'),
|
|
||||||
(RACK_TYPE_CABINET, '4-post cabinet'),
|
|
||||||
(RACK_TYPE_WALLFRAME, 'Wall-mounted frame'),
|
|
||||||
(RACK_TYPE_WALLCABINET, 'Wall-mounted cabinet'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rack widths
|
|
||||||
RACK_WIDTH_19IN = 19
|
|
||||||
RACK_WIDTH_23IN = 23
|
|
||||||
RACK_WIDTH_CHOICES = (
|
|
||||||
(RACK_WIDTH_19IN, '19 inches'),
|
|
||||||
(RACK_WIDTH_23IN, '23 inches'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rack faces
|
|
||||||
RACK_FACE_FRONT = 0
|
|
||||||
RACK_FACE_REAR = 1
|
|
||||||
RACK_FACE_CHOICES = [
|
|
||||||
[RACK_FACE_FRONT, 'Front'],
|
|
||||||
[RACK_FACE_REAR, 'Rear'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Rack statuses
|
|
||||||
RACK_STATUS_RESERVED = 0
|
|
||||||
RACK_STATUS_AVAILABLE = 1
|
|
||||||
RACK_STATUS_PLANNED = 2
|
|
||||||
RACK_STATUS_ACTIVE = 3
|
|
||||||
RACK_STATUS_DEPRECATED = 4
|
|
||||||
RACK_STATUS_CHOICES = [
|
|
||||||
[RACK_STATUS_ACTIVE, 'Active'],
|
|
||||||
[RACK_STATUS_PLANNED, 'Planned'],
|
|
||||||
[RACK_STATUS_RESERVED, 'Reserved'],
|
|
||||||
[RACK_STATUS_AVAILABLE, 'Available'],
|
|
||||||
[RACK_STATUS_DEPRECATED, 'Deprecated'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Device rack position
|
|
||||||
DEVICE_POSITION_CHOICES = [
|
|
||||||
# Rack.u_height is limited to 100
|
|
||||||
(i, 'Unit {}'.format(i)) for i in range(1, 101)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Parent/child device roles
|
|
||||||
SUBDEVICE_ROLE_PARENT = True
|
|
||||||
SUBDEVICE_ROLE_CHILD = False
|
|
||||||
SUBDEVICE_ROLE_CHOICES = (
|
|
||||||
(None, 'None'),
|
|
||||||
(SUBDEVICE_ROLE_PARENT, 'Parent'),
|
|
||||||
(SUBDEVICE_ROLE_CHILD, 'Child'),
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Numeric interface types
|
# Interface type groups
|
||||||
#
|
#
|
||||||
|
|
||||||
# Virtual
|
|
||||||
IFACE_TYPE_VIRTUAL = 0
|
|
||||||
IFACE_TYPE_LAG = 200
|
|
||||||
# Ethernet
|
|
||||||
IFACE_TYPE_100ME_FIXED = 800
|
|
||||||
IFACE_TYPE_1GE_FIXED = 1000
|
|
||||||
IFACE_TYPE_1GE_GBIC = 1050
|
|
||||||
IFACE_TYPE_1GE_SFP = 1100
|
|
||||||
IFACE_TYPE_2GE_FIXED = 1120
|
|
||||||
IFACE_TYPE_5GE_FIXED = 1130
|
|
||||||
IFACE_TYPE_10GE_FIXED = 1150
|
|
||||||
IFACE_TYPE_10GE_CX4 = 1170
|
|
||||||
IFACE_TYPE_10GE_SFP_PLUS = 1200
|
|
||||||
IFACE_TYPE_10GE_XFP = 1300
|
|
||||||
IFACE_TYPE_10GE_XENPAK = 1310
|
|
||||||
IFACE_TYPE_10GE_X2 = 1320
|
|
||||||
IFACE_TYPE_25GE_SFP28 = 1350
|
|
||||||
IFACE_TYPE_40GE_QSFP_PLUS = 1400
|
|
||||||
IFACE_TYPE_50GE_QSFP28 = 1420
|
|
||||||
IFACE_TYPE_100GE_CFP = 1500
|
|
||||||
IFACE_TYPE_100GE_CFP2 = 1510
|
|
||||||
IFACE_TYPE_100GE_CFP4 = 1520
|
|
||||||
IFACE_TYPE_100GE_CPAK = 1550
|
|
||||||
IFACE_TYPE_100GE_QSFP28 = 1600
|
|
||||||
IFACE_TYPE_200GE_CFP2 = 1650
|
|
||||||
IFACE_TYPE_200GE_QSFP56 = 1700
|
|
||||||
IFACE_TYPE_400GE_QSFP_DD = 1750
|
|
||||||
IFACE_TYPE_400GE_OSFP = 1800
|
|
||||||
# Wireless
|
|
||||||
IFACE_TYPE_80211A = 2600
|
|
||||||
IFACE_TYPE_80211G = 2610
|
|
||||||
IFACE_TYPE_80211N = 2620
|
|
||||||
IFACE_TYPE_80211AC = 2630
|
|
||||||
IFACE_TYPE_80211AD = 2640
|
|
||||||
# Cellular
|
|
||||||
IFACE_TYPE_GSM = 2810
|
|
||||||
IFACE_TYPE_CDMA = 2820
|
|
||||||
IFACE_TYPE_LTE = 2830
|
|
||||||
# SONET
|
|
||||||
IFACE_TYPE_SONET_OC3 = 6100
|
|
||||||
IFACE_TYPE_SONET_OC12 = 6200
|
|
||||||
IFACE_TYPE_SONET_OC48 = 6300
|
|
||||||
IFACE_TYPE_SONET_OC192 = 6400
|
|
||||||
IFACE_TYPE_SONET_OC768 = 6500
|
|
||||||
IFACE_TYPE_SONET_OC1920 = 6600
|
|
||||||
IFACE_TYPE_SONET_OC3840 = 6700
|
|
||||||
# Fibrechannel
|
|
||||||
IFACE_TYPE_1GFC_SFP = 3010
|
|
||||||
IFACE_TYPE_2GFC_SFP = 3020
|
|
||||||
IFACE_TYPE_4GFC_SFP = 3040
|
|
||||||
IFACE_TYPE_8GFC_SFP_PLUS = 3080
|
|
||||||
IFACE_TYPE_16GFC_SFP_PLUS = 3160
|
|
||||||
IFACE_TYPE_32GFC_SFP28 = 3320
|
|
||||||
IFACE_TYPE_128GFC_QSFP28 = 3400
|
|
||||||
# InfiniBand
|
|
||||||
IFACE_TYPE_INFINIBAND_SDR = 7010
|
|
||||||
IFACE_TYPE_INFINIBAND_DDR = 7020
|
|
||||||
IFACE_TYPE_INFINIBAND_QDR = 7030
|
|
||||||
IFACE_TYPE_INFINIBAND_FDR10 = 7040
|
|
||||||
IFACE_TYPE_INFINIBAND_FDR = 7050
|
|
||||||
IFACE_TYPE_INFINIBAND_EDR = 7060
|
|
||||||
IFACE_TYPE_INFINIBAND_HDR = 7070
|
|
||||||
IFACE_TYPE_INFINIBAND_NDR = 7080
|
|
||||||
IFACE_TYPE_INFINIBAND_XDR = 7090
|
|
||||||
# Serial
|
|
||||||
IFACE_TYPE_T1 = 4000
|
|
||||||
IFACE_TYPE_E1 = 4010
|
|
||||||
IFACE_TYPE_T3 = 4040
|
|
||||||
IFACE_TYPE_E3 = 4050
|
|
||||||
# Stacking
|
|
||||||
IFACE_TYPE_STACKWISE = 5000
|
|
||||||
IFACE_TYPE_STACKWISE_PLUS = 5050
|
|
||||||
IFACE_TYPE_FLEXSTACK = 5100
|
|
||||||
IFACE_TYPE_FLEXSTACK_PLUS = 5150
|
|
||||||
IFACE_TYPE_JUNIPER_VCP = 5200
|
|
||||||
IFACE_TYPE_SUMMITSTACK = 5300
|
|
||||||
IFACE_TYPE_SUMMITSTACK128 = 5310
|
|
||||||
IFACE_TYPE_SUMMITSTACK256 = 5320
|
|
||||||
IFACE_TYPE_SUMMITSTACK512 = 5330
|
|
||||||
|
|
||||||
# Other
|
|
||||||
IFACE_TYPE_OTHER = 32767
|
|
||||||
|
|
||||||
IFACE_TYPE_CHOICES = [
|
|
||||||
[
|
|
||||||
'Virtual interfaces',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_VIRTUAL, 'Virtual'],
|
|
||||||
[IFACE_TYPE_LAG, 'Link Aggregation Group (LAG)'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Ethernet (fixed)',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_100ME_FIXED, '100BASE-TX (10/100ME)'],
|
|
||||||
[IFACE_TYPE_1GE_FIXED, '1000BASE-T (1GE)'],
|
|
||||||
[IFACE_TYPE_2GE_FIXED, '2.5GBASE-T (2.5GE)'],
|
|
||||||
[IFACE_TYPE_5GE_FIXED, '5GBASE-T (5GE)'],
|
|
||||||
[IFACE_TYPE_10GE_FIXED, '10GBASE-T (10GE)'],
|
|
||||||
[IFACE_TYPE_10GE_CX4, '10GBASE-CX4 (10GE)'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Ethernet (modular)',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_1GE_GBIC, 'GBIC (1GE)'],
|
|
||||||
[IFACE_TYPE_1GE_SFP, 'SFP (1GE)'],
|
|
||||||
[IFACE_TYPE_10GE_SFP_PLUS, 'SFP+ (10GE)'],
|
|
||||||
[IFACE_TYPE_10GE_XFP, 'XFP (10GE)'],
|
|
||||||
[IFACE_TYPE_10GE_XENPAK, 'XENPAK (10GE)'],
|
|
||||||
[IFACE_TYPE_10GE_X2, 'X2 (10GE)'],
|
|
||||||
[IFACE_TYPE_25GE_SFP28, 'SFP28 (25GE)'],
|
|
||||||
[IFACE_TYPE_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
|
|
||||||
[IFACE_TYPE_50GE_QSFP28, 'QSFP28 (50GE)'],
|
|
||||||
[IFACE_TYPE_100GE_CFP, 'CFP (100GE)'],
|
|
||||||
[IFACE_TYPE_100GE_CFP2, 'CFP2 (100GE)'],
|
|
||||||
[IFACE_TYPE_200GE_CFP2, 'CFP2 (200GE)'],
|
|
||||||
[IFACE_TYPE_100GE_CFP4, 'CFP4 (100GE)'],
|
|
||||||
[IFACE_TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'],
|
|
||||||
[IFACE_TYPE_100GE_QSFP28, 'QSFP28 (100GE)'],
|
|
||||||
[IFACE_TYPE_200GE_QSFP56, 'QSFP56 (200GE)'],
|
|
||||||
[IFACE_TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'],
|
|
||||||
[IFACE_TYPE_400GE_OSFP, 'OSFP (400GE)'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Wireless',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_80211A, 'IEEE 802.11a'],
|
|
||||||
[IFACE_TYPE_80211G, 'IEEE 802.11b/g'],
|
|
||||||
[IFACE_TYPE_80211N, 'IEEE 802.11n'],
|
|
||||||
[IFACE_TYPE_80211AC, 'IEEE 802.11ac'],
|
|
||||||
[IFACE_TYPE_80211AD, 'IEEE 802.11ad'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Cellular',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_GSM, 'GSM'],
|
|
||||||
[IFACE_TYPE_CDMA, 'CDMA'],
|
|
||||||
[IFACE_TYPE_LTE, 'LTE'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'SONET',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_SONET_OC3, 'OC-3/STM-1'],
|
|
||||||
[IFACE_TYPE_SONET_OC12, 'OC-12/STM-4'],
|
|
||||||
[IFACE_TYPE_SONET_OC48, 'OC-48/STM-16'],
|
|
||||||
[IFACE_TYPE_SONET_OC192, 'OC-192/STM-64'],
|
|
||||||
[IFACE_TYPE_SONET_OC768, 'OC-768/STM-256'],
|
|
||||||
[IFACE_TYPE_SONET_OC1920, 'OC-1920/STM-640'],
|
|
||||||
[IFACE_TYPE_SONET_OC3840, 'OC-3840/STM-1234'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'FibreChannel',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_1GFC_SFP, 'SFP (1GFC)'],
|
|
||||||
[IFACE_TYPE_2GFC_SFP, 'SFP (2GFC)'],
|
|
||||||
[IFACE_TYPE_4GFC_SFP, 'SFP (4GFC)'],
|
|
||||||
[IFACE_TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'],
|
|
||||||
[IFACE_TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'],
|
|
||||||
[IFACE_TYPE_32GFC_SFP28, 'SFP28 (32GFC)'],
|
|
||||||
[IFACE_TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'InfiniBand',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_INFINIBAND_SDR, 'SDR (2 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_DDR, 'DDR (4 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_QDR, 'QDR (8 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_FDR10, 'FDR10 (10 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_FDR, 'FDR (13.5 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_EDR, 'EDR (25 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_HDR, 'HDR (50 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_NDR, 'NDR (100 Gbps)'],
|
|
||||||
[IFACE_TYPE_INFINIBAND_XDR, 'XDR (250 Gbps)'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Serial',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_T1, 'T1 (1.544 Mbps)'],
|
|
||||||
[IFACE_TYPE_E1, 'E1 (2.048 Mbps)'],
|
|
||||||
[IFACE_TYPE_T3, 'T3 (45 Mbps)'],
|
|
||||||
[IFACE_TYPE_E3, 'E3 (34 Mbps)'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Stacking',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_STACKWISE, 'Cisco StackWise'],
|
|
||||||
[IFACE_TYPE_STACKWISE_PLUS, 'Cisco StackWise Plus'],
|
|
||||||
[IFACE_TYPE_FLEXSTACK, 'Cisco FlexStack'],
|
|
||||||
[IFACE_TYPE_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
|
|
||||||
[IFACE_TYPE_JUNIPER_VCP, 'Juniper VCP'],
|
|
||||||
[IFACE_TYPE_SUMMITSTACK, 'Extreme SummitStack'],
|
|
||||||
[IFACE_TYPE_SUMMITSTACK128, 'Extreme SummitStack-128'],
|
|
||||||
[IFACE_TYPE_SUMMITSTACK256, 'Extreme SummitStack-256'],
|
|
||||||
[IFACE_TYPE_SUMMITSTACK512, 'Extreme SummitStack-512'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Other',
|
|
||||||
[
|
|
||||||
[IFACE_TYPE_OTHER, 'Other'],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
VIRTUAL_IFACE_TYPES = [
|
VIRTUAL_IFACE_TYPES = [
|
||||||
IFACE_TYPE_VIRTUAL,
|
InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||||
IFACE_TYPE_LAG,
|
InterfaceTypeChoices.TYPE_LAG,
|
||||||
]
|
]
|
||||||
|
|
||||||
WIRELESS_IFACE_TYPES = [
|
WIRELESS_IFACE_TYPES = [
|
||||||
IFACE_TYPE_80211A,
|
InterfaceTypeChoices.TYPE_80211A,
|
||||||
IFACE_TYPE_80211G,
|
InterfaceTypeChoices.TYPE_80211G,
|
||||||
IFACE_TYPE_80211N,
|
InterfaceTypeChoices.TYPE_80211N,
|
||||||
IFACE_TYPE_80211AC,
|
InterfaceTypeChoices.TYPE_80211AC,
|
||||||
IFACE_TYPE_80211AD,
|
InterfaceTypeChoices.TYPE_80211AD,
|
||||||
]
|
]
|
||||||
|
|
||||||
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
|
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
|
||||||
|
|
||||||
IFACE_MODE_ACCESS = 100
|
|
||||||
IFACE_MODE_TAGGED = 200
|
|
||||||
IFACE_MODE_TAGGED_ALL = 300
|
|
||||||
IFACE_MODE_CHOICES = [
|
|
||||||
[IFACE_MODE_ACCESS, 'Access'],
|
|
||||||
[IFACE_MODE_TAGGED, 'Tagged'],
|
|
||||||
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Pass-through port types
|
|
||||||
PORT_TYPE_8P8C = 1000
|
|
||||||
PORT_TYPE_110_PUNCH = 1100
|
|
||||||
PORT_TYPE_BNC = 1200
|
|
||||||
PORT_TYPE_ST = 2000
|
|
||||||
PORT_TYPE_SC = 2100
|
|
||||||
PORT_TYPE_SC_APC = 2110
|
|
||||||
PORT_TYPE_FC = 2200
|
|
||||||
PORT_TYPE_LC = 2300
|
|
||||||
PORT_TYPE_LC_APC = 2310
|
|
||||||
PORT_TYPE_MTRJ = 2400
|
|
||||||
PORT_TYPE_MPO = 2500
|
|
||||||
PORT_TYPE_LSH = 2600
|
|
||||||
PORT_TYPE_LSH_APC = 2610
|
|
||||||
PORT_TYPE_CHOICES = [
|
|
||||||
[
|
|
||||||
'Copper',
|
|
||||||
[
|
|
||||||
[PORT_TYPE_8P8C, '8P8C'],
|
|
||||||
[PORT_TYPE_110_PUNCH, '110 Punch'],
|
|
||||||
[PORT_TYPE_BNC, 'BNC'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Fiber Optic',
|
|
||||||
[
|
|
||||||
[PORT_TYPE_FC, 'FC'],
|
|
||||||
[PORT_TYPE_LC, 'LC'],
|
|
||||||
[PORT_TYPE_LC_APC, 'LC/APC'],
|
|
||||||
[PORT_TYPE_LSH, 'LSH'],
|
|
||||||
[PORT_TYPE_LSH_APC, 'LSH/APC'],
|
|
||||||
[PORT_TYPE_MPO, 'MPO'],
|
|
||||||
[PORT_TYPE_MTRJ, 'MTRJ'],
|
|
||||||
[PORT_TYPE_SC, 'SC'],
|
|
||||||
[PORT_TYPE_SC_APC, 'SC/APC'],
|
|
||||||
[PORT_TYPE_ST, 'ST'],
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
# Device statuses
|
|
||||||
DEVICE_STATUS_OFFLINE = 0
|
|
||||||
DEVICE_STATUS_ACTIVE = 1
|
|
||||||
DEVICE_STATUS_PLANNED = 2
|
|
||||||
DEVICE_STATUS_STAGED = 3
|
|
||||||
DEVICE_STATUS_FAILED = 4
|
|
||||||
DEVICE_STATUS_INVENTORY = 5
|
|
||||||
DEVICE_STATUS_DECOMMISSIONING = 6
|
|
||||||
DEVICE_STATUS_CHOICES = [
|
|
||||||
[DEVICE_STATUS_ACTIVE, 'Active'],
|
|
||||||
[DEVICE_STATUS_OFFLINE, 'Offline'],
|
|
||||||
[DEVICE_STATUS_PLANNED, 'Planned'],
|
|
||||||
[DEVICE_STATUS_STAGED, 'Staged'],
|
|
||||||
[DEVICE_STATUS_FAILED, 'Failed'],
|
|
||||||
[DEVICE_STATUS_INVENTORY, 'Inventory'],
|
|
||||||
[DEVICE_STATUS_DECOMMISSIONING, 'Decommissioning'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Site statuses
|
|
||||||
SITE_STATUS_ACTIVE = 1
|
|
||||||
SITE_STATUS_PLANNED = 2
|
|
||||||
SITE_STATUS_RETIRED = 4
|
|
||||||
SITE_STATUS_CHOICES = [
|
|
||||||
[SITE_STATUS_ACTIVE, 'Active'],
|
|
||||||
[SITE_STATUS_PLANNED, 'Planned'],
|
|
||||||
[SITE_STATUS_RETIRED, 'Retired'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Bootstrap CSS classes for device/rack statuses
|
|
||||||
STATUS_CLASSES = {
|
|
||||||
0: 'warning',
|
|
||||||
1: 'success',
|
|
||||||
2: 'info',
|
|
||||||
3: 'primary',
|
|
||||||
4: 'danger',
|
|
||||||
5: 'default',
|
|
||||||
6: 'warning',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Console/power/interface connection statuses
|
# Console/power/interface connection statuses
|
||||||
CONNECTION_STATUS_PLANNED = False
|
CONNECTION_STATUS_PLANNED = False
|
||||||
CONNECTION_STATUS_CONNECTED = True
|
CONNECTION_STATUS_CONNECTED = True
|
||||||
@ -390,56 +34,6 @@ CABLE_TERMINATION_TYPES = [
|
|||||||
'circuittermination',
|
'circuittermination',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Cable types
|
|
||||||
CABLE_TYPE_CAT3 = 1300
|
|
||||||
CABLE_TYPE_CAT5 = 1500
|
|
||||||
CABLE_TYPE_CAT5E = 1510
|
|
||||||
CABLE_TYPE_CAT6 = 1600
|
|
||||||
CABLE_TYPE_CAT6A = 1610
|
|
||||||
CABLE_TYPE_CAT7 = 1700
|
|
||||||
CABLE_TYPE_DAC_ACTIVE = 1800
|
|
||||||
CABLE_TYPE_DAC_PASSIVE = 1810
|
|
||||||
CABLE_TYPE_COAXIAL = 1900
|
|
||||||
CABLE_TYPE_MMF = 3000
|
|
||||||
CABLE_TYPE_MMF_OM1 = 3010
|
|
||||||
CABLE_TYPE_MMF_OM2 = 3020
|
|
||||||
CABLE_TYPE_MMF_OM3 = 3030
|
|
||||||
CABLE_TYPE_MMF_OM4 = 3040
|
|
||||||
CABLE_TYPE_SMF = 3500
|
|
||||||
CABLE_TYPE_SMF_OS1 = 3510
|
|
||||||
CABLE_TYPE_SMF_OS2 = 3520
|
|
||||||
CABLE_TYPE_AOC = 3800
|
|
||||||
CABLE_TYPE_POWER = 5000
|
|
||||||
CABLE_TYPE_CHOICES = (
|
|
||||||
(
|
|
||||||
'Copper', (
|
|
||||||
(CABLE_TYPE_CAT3, 'CAT3'),
|
|
||||||
(CABLE_TYPE_CAT5, 'CAT5'),
|
|
||||||
(CABLE_TYPE_CAT5E, 'CAT5e'),
|
|
||||||
(CABLE_TYPE_CAT6, 'CAT6'),
|
|
||||||
(CABLE_TYPE_CAT6A, 'CAT6a'),
|
|
||||||
(CABLE_TYPE_CAT7, 'CAT7'),
|
|
||||||
(CABLE_TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
|
|
||||||
(CABLE_TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
|
|
||||||
(CABLE_TYPE_COAXIAL, 'Coaxial'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Fiber', (
|
|
||||||
(CABLE_TYPE_MMF, 'Multimode Fiber'),
|
|
||||||
(CABLE_TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
|
||||||
(CABLE_TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
|
||||||
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
|
||||||
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
|
||||||
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
|
|
||||||
(CABLE_TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
|
|
||||||
(CABLE_TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
|
|
||||||
(CABLE_TYPE_AOC, 'Active Optical Cabling (AOC)'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(CABLE_TYPE_POWER, 'Power'),
|
|
||||||
)
|
|
||||||
|
|
||||||
CABLE_TERMINATION_TYPE_CHOICES = {
|
CABLE_TERMINATION_TYPE_CHOICES = {
|
||||||
# (API endpoint, human-friendly name)
|
# (API endpoint, human-friendly name)
|
||||||
'consoleport': ('console-ports', 'Console port'),
|
'consoleport': ('console-ports', 'Console port'),
|
||||||
@ -461,57 +55,3 @@ COMPATIBLE_TERMINATION_TYPES = {
|
|||||||
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
||||||
'circuittermination': ['interface', 'frontport', 'rearport'],
|
'circuittermination': ['interface', 'frontport', 'rearport'],
|
||||||
}
|
}
|
||||||
|
|
||||||
LENGTH_UNIT_METER = 1200
|
|
||||||
LENGTH_UNIT_CENTIMETER = 1100
|
|
||||||
LENGTH_UNIT_MILLIMETER = 1000
|
|
||||||
LENGTH_UNIT_FOOT = 2100
|
|
||||||
LENGTH_UNIT_INCH = 2000
|
|
||||||
CABLE_LENGTH_UNIT_CHOICES = (
|
|
||||||
(LENGTH_UNIT_METER, 'Meters'),
|
|
||||||
(LENGTH_UNIT_CENTIMETER, 'Centimeters'),
|
|
||||||
(LENGTH_UNIT_FOOT, 'Feet'),
|
|
||||||
(LENGTH_UNIT_INCH, 'Inches'),
|
|
||||||
)
|
|
||||||
RACK_DIMENSION_UNIT_CHOICES = (
|
|
||||||
(LENGTH_UNIT_MILLIMETER, 'Millimeters'),
|
|
||||||
(LENGTH_UNIT_INCH, 'Inches'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Power feeds
|
|
||||||
POWERFEED_TYPE_PRIMARY = 1
|
|
||||||
POWERFEED_TYPE_REDUNDANT = 2
|
|
||||||
POWERFEED_TYPE_CHOICES = (
|
|
||||||
(POWERFEED_TYPE_PRIMARY, 'Primary'),
|
|
||||||
(POWERFEED_TYPE_REDUNDANT, 'Redundant'),
|
|
||||||
)
|
|
||||||
POWERFEED_SUPPLY_AC = 1
|
|
||||||
POWERFEED_SUPPLY_DC = 2
|
|
||||||
POWERFEED_SUPPLY_CHOICES = (
|
|
||||||
(POWERFEED_SUPPLY_AC, 'AC'),
|
|
||||||
(POWERFEED_SUPPLY_DC, 'DC'),
|
|
||||||
)
|
|
||||||
POWERFEED_PHASE_SINGLE = 1
|
|
||||||
POWERFEED_PHASE_3PHASE = 3
|
|
||||||
POWERFEED_PHASE_CHOICES = (
|
|
||||||
(POWERFEED_PHASE_SINGLE, 'Single phase'),
|
|
||||||
(POWERFEED_PHASE_3PHASE, 'Three-phase'),
|
|
||||||
)
|
|
||||||
POWERFEED_STATUS_OFFLINE = 0
|
|
||||||
POWERFEED_STATUS_ACTIVE = 1
|
|
||||||
POWERFEED_STATUS_PLANNED = 2
|
|
||||||
POWERFEED_STATUS_FAILED = 4
|
|
||||||
POWERFEED_STATUS_CHOICES = (
|
|
||||||
(POWERFEED_STATUS_ACTIVE, 'Active'),
|
|
||||||
(POWERFEED_STATUS_OFFLINE, 'Offline'),
|
|
||||||
(POWERFEED_STATUS_PLANNED, 'Planned'),
|
|
||||||
(POWERFEED_STATUS_FAILED, 'Failed'),
|
|
||||||
)
|
|
||||||
POWERFEED_LEG_A = 1
|
|
||||||
POWERFEED_LEG_B = 2
|
|
||||||
POWERFEED_LEG_C = 3
|
|
||||||
POWERFEED_LEG_CHOICES = (
|
|
||||||
(POWERFEED_LEG_A, 'A'),
|
|
||||||
(POWERFEED_LEG_B, 'B'),
|
|
||||||
(POWERFEED_LEG_C, 'C'),
|
|
||||||
)
|
|
||||||
|
@ -49,7 +49,7 @@ class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=SITE_STATUS_CHOICES,
|
choices=SiteStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
region_id = TreeNodeMultipleChoiceFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
@ -147,7 +147,7 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
label='Group',
|
label='Group',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=RACK_STATUS_CHOICES,
|
choices=RackStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
@ -511,7 +511,7 @@ class DeviceFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilter
|
|||||||
label='Device model (slug)',
|
label='Device model (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
choices=DeviceStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
is_full_depth = django_filters.BooleanFilter(
|
is_full_depth = django_filters.BooleanFilter(
|
||||||
@ -663,7 +663,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
|||||||
|
|
||||||
class ConsolePortFilter(DeviceComponentFilterSet):
|
class ConsolePortFilter(DeviceComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
@ -679,7 +679,7 @@ class ConsolePortFilter(DeviceComponentFilterSet):
|
|||||||
|
|
||||||
class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
@ -695,7 +695,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
|||||||
|
|
||||||
class PowerPortFilter(DeviceComponentFilterSet):
|
class PowerPortFilter(DeviceComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PowerPortTypes.CHOICES,
|
choices=PowerPortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
@ -711,7 +711,7 @@ class PowerPortFilter(DeviceComponentFilterSet):
|
|||||||
|
|
||||||
class PowerOutletFilter(DeviceComponentFilterSet):
|
class PowerOutletFilter(DeviceComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
@ -789,7 +789,7 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
label='Assigned VID'
|
label='Assigned VID'
|
||||||
)
|
)
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
choices=InterfaceTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -980,7 +980,7 @@ class CableFilter(django_filters.FilterSet):
|
|||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=CABLE_TYPE_CHOICES
|
choices=CableTypeChoices
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=CONNECTION_STATUS_CHOICES
|
choices=CONNECTION_STATUS_CHOICES
|
||||||
|
@ -1910,7 +1910,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": 1,
|
"position": 1,
|
||||||
"face": 0,
|
"face": "front",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": 1,
|
"primary_ip4": 1,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -1931,7 +1931,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": 17,
|
"position": 17,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": 5,
|
"primary_ip4": 5,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -1952,7 +1952,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": 33,
|
"position": 33,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -1973,7 +1973,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": 34,
|
"position": 34,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -1994,7 +1994,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 2,
|
"rack": 2,
|
||||||
"position": 34,
|
"position": 34,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2015,7 +2015,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 2,
|
"rack": 2,
|
||||||
"position": 33,
|
"position": 33,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2036,7 +2036,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 2,
|
"rack": 2,
|
||||||
"position": 1,
|
"position": 1,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": 3,
|
"primary_ip4": 3,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2057,7 +2057,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 2,
|
"rack": 2,
|
||||||
"position": 17,
|
"position": 17,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": 19,
|
"primary_ip4": 19,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2078,7 +2078,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": 42,
|
"position": 42,
|
||||||
"face": 0,
|
"face": "rear",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2099,7 +2099,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 1,
|
"rack": 1,
|
||||||
"position": null,
|
"position": null,
|
||||||
"face": null,
|
"face": "",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
@ -2120,7 +2120,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"rack": 2,
|
"rack": 2,
|
||||||
"position": null,
|
"position": null,
|
||||||
"face": null,
|
"face": "",
|
||||||
"status": true,
|
"status": true,
|
||||||
"primary_ip4": null,
|
"primary_ip4": null,
|
||||||
"primary_ip6": null,
|
"primary_ip6": null,
|
||||||
|
@ -93,13 +93,13 @@ class InterfaceCommonForm:
|
|||||||
tagged_vlans = self.cleaned_data['tagged_vlans']
|
tagged_vlans = self.cleaned_data['tagged_vlans']
|
||||||
|
|
||||||
# Untagged interfaces cannot be assigned tagged VLANs
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans:
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'mode': "An access interface cannot have tagged VLANs assigned."
|
'mode': "An access interface cannot have tagged VLANs assigned."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
||||||
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL:
|
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
|
||||||
self.cleaned_data['tagged_vlans'] = []
|
self.cleaned_data['tagged_vlans'] = []
|
||||||
|
|
||||||
|
|
||||||
@ -250,7 +250,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
class SiteCSVForm(forms.ModelForm):
|
class SiteCSVForm(forms.ModelForm):
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=SITE_STATUS_CHOICES,
|
choices=SiteStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
@ -289,7 +289,7 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
widget=forms.MultipleHiddenInput
|
widget=forms.MultipleHiddenInput
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(SITE_STATUS_CHOICES),
|
choices=add_blank_choice(SiteStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -338,7 +338,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=SITE_STATUS_CHOICES,
|
choices=SiteStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -500,7 +500,7 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=RACK_STATUS_CHOICES,
|
choices=RackStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
@ -514,19 +514,16 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=RACK_TYPE_CHOICES,
|
choices=RackTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Rack type'
|
help_text='Rack type'
|
||||||
)
|
)
|
||||||
width = forms.ChoiceField(
|
width = forms.ChoiceField(
|
||||||
choices=(
|
choices=RackWidthChoices,
|
||||||
(RACK_WIDTH_19IN, '19'),
|
|
||||||
(RACK_WIDTH_23IN, '23'),
|
|
||||||
),
|
|
||||||
help_text='Rail-to-rail width (in inches)'
|
help_text='Rail-to-rail width (in inches)'
|
||||||
)
|
)
|
||||||
outer_unit = CSVChoiceField(
|
outer_unit = CSVChoiceField(
|
||||||
choices=RACK_DIMENSION_UNIT_CHOICES,
|
choices=RackDimensionUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Unit for outer dimensions'
|
help_text='Unit for outer dimensions'
|
||||||
)
|
)
|
||||||
@ -598,7 +595,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(RACK_STATUS_CHOICES),
|
choices=add_blank_choice(RackStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -620,12 +617,12 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(RACK_TYPE_CHOICES),
|
choices=add_blank_choice(RackTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
width = forms.ChoiceField(
|
width = forms.ChoiceField(
|
||||||
choices=add_blank_choice(RACK_WIDTH_CHOICES),
|
choices=add_blank_choice(RackWidthChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -647,7 +644,7 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
min_value=1
|
min_value=1
|
||||||
)
|
)
|
||||||
outer_unit = forms.ChoiceField(
|
outer_unit = forms.ChoiceField(
|
||||||
choices=add_blank_choice(RACK_DIMENSION_UNIT_CHOICES),
|
choices=add_blank_choice(RackDimensionUnitChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -692,7 +689,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=RACK_STATUS_CHOICES,
|
choices=RackStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -909,12 +906,10 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
value_field="slug",
|
value_field="slug",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
subdevice_role = forms.NullBooleanField(
|
subdevice_role = forms.MultipleChoiceField(
|
||||||
|
choices=add_blank_choice(SubdeviceRoleChoices),
|
||||||
required=False,
|
required=False,
|
||||||
label='Subdevice role',
|
widget=StaticSelect2Multiple()
|
||||||
widget=StaticSelect2(
|
|
||||||
choices=add_blank_choice(SUBDEVICE_ROLE_CHOICES)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
console_ports = forms.NullBooleanField(
|
console_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -981,7 +976,7 @@ class ConsolePortTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1003,7 +998,7 @@ class ConsoleServerPortTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypes.CHOICES),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1025,7 +1020,7 @@ class PowerPortTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerPortTypes.CHOICES),
|
choices=add_blank_choice(PowerPortTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
maximum_draw = forms.IntegerField(
|
maximum_draw = forms.IntegerField(
|
||||||
@ -1067,7 +1062,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerOutletTypes.CHOICES),
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
power_port = forms.ModelChoiceField(
|
power_port = forms.ModelChoiceField(
|
||||||
@ -1075,7 +1070,7 @@ class PowerOutletTemplateCreateForm(ComponentForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
feed_leg = forms.ChoiceField(
|
feed_leg = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
|
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -1108,7 +1103,7 @@ class InterfaceTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
choices=InterfaceTypeChoices,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
mgmt_only = forms.BooleanField(
|
mgmt_only = forms.BooleanField(
|
||||||
@ -1123,7 +1118,7 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_TYPE_CHOICES),
|
choices=add_blank_choice(InterfaceTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -1165,7 +1160,7 @@ class FrontPortTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
rear_port_set = forms.MultipleChoiceField(
|
||||||
@ -1235,7 +1230,7 @@ class RearPortTemplateCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
positions = forms.IntegerField(
|
positions = forms.IntegerField(
|
||||||
@ -1334,7 +1329,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
|
|
||||||
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=InterfaceTypes.TYPE_CHOICES
|
choices=InterfaceTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1343,15 +1338,10 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
'device_type', 'name', 'type', 'mgmt_only',
|
'device_type', 'name', 'type', 'mgmt_only',
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean_type(self):
|
|
||||||
# Convert slug value to field integer value
|
|
||||||
slug = self.cleaned_data['type']
|
|
||||||
return InterfaceTypes.slug_to_integer(slug)
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PortTypes.TYPE_CHOICES
|
choices=PortTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
rear_port = forms.ModelChoiceField(
|
rear_port = forms.ModelChoiceField(
|
||||||
queryset=RearPortTemplate.objects.all(),
|
queryset=RearPortTemplate.objects.all(),
|
||||||
@ -1365,15 +1355,10 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
'device_type', 'name', 'type', 'rear_port', 'rear_port_position',
|
'device_type', 'name', 'type', 'rear_port', 'rear_port_position',
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean_type(self):
|
|
||||||
# Convert slug value to field integer value
|
|
||||||
slug = self.cleaned_data['type']
|
|
||||||
return PortTypes.slug_to_integer(slug)
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PortTypes.TYPE_CHOICES
|
choices=PortTypeChoices.CHOICES
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1382,11 +1367,6 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
'device_type', 'name', 'type', 'positions',
|
'device_type', 'name', 'type', 'positions',
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean_type(self):
|
|
||||||
# Convert slug value to field integer value
|
|
||||||
slug = self.cleaned_data['type']
|
|
||||||
return PortTypes.slug_to_integer(slug)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
|
class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
|
||||||
|
|
||||||
@ -1702,7 +1682,7 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
choices=DeviceStatusChoices,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1746,7 +1726,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
help_text='Name of parent rack'
|
help_text='Name of parent rack'
|
||||||
)
|
)
|
||||||
face = CSVChoiceField(
|
face = CSVChoiceField(
|
||||||
choices=RACK_FACE_CHOICES,
|
choices=DeviceFaceChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Mounted rack face'
|
help_text='Mounted rack face'
|
||||||
)
|
)
|
||||||
@ -1870,7 +1850,7 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(DEVICE_STATUS_CHOICES),
|
choices=add_blank_choice(DeviceStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -1981,7 +1961,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
choices=DeviceStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -2063,7 +2043,7 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
|
|||||||
|
|
||||||
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
|
class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
choices=InterfaceTypeChoices,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
enabled = forms.BooleanField(
|
enabled = forms.BooleanField(
|
||||||
@ -2115,7 +2095,7 @@ class ConsolePortCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypes.CHOICES),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2172,7 +2152,7 @@ class ConsoleServerPortCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypes.CHOICES),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2191,7 +2171,7 @@ class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditF
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypes.CHOICES),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2264,7 +2244,7 @@ class PowerPortCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerPortTypes.CHOICES),
|
choices=add_blank_choice(PowerPortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2344,7 +2324,7 @@ class PowerOutletCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerOutletTypes.CHOICES),
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2353,7 +2333,7 @@ class PowerOutletCreateForm(ComponentForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
feed_leg = forms.ChoiceField(
|
feed_leg = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
|
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
@ -2391,7 +2371,7 @@ class PowerOutletCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
feed_leg = CSVChoiceField(
|
feed_leg = CSVChoiceField(
|
||||||
choices=POWERFEED_LEG_CHOICES,
|
choices=PowerOutletFeedLegChoices,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2428,11 +2408,11 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
feed_leg = forms.ChoiceField(
|
feed_leg = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
|
choices=add_blank_choice(PowerOutletFeedLegChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
power_port = forms.ModelChoiceField(
|
power_port = forms.ModelChoiceField(
|
||||||
@ -2529,12 +2509,14 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
|||||||
if self.is_bound:
|
if self.is_bound:
|
||||||
device = Device.objects.get(pk=self.data['device'])
|
device = Device.objects.get(pk=self.data['device'])
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
self.fields['lag'].queryset = Interface.objects.filter(
|
||||||
device__in=[device, device.get_vc_master()], type=IFACE_TYPE_LAG
|
device__in=[device, device.get_vc_master()],
|
||||||
|
type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
device = self.instance.device
|
device = self.instance.device
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
self.fields['lag'].queryset = Interface.objects.filter(
|
||||||
device__in=[self.instance.device, self.instance.device.get_vc_master()], type=IFACE_TYPE_LAG
|
device__in=[self.instance.device, self.instance.device.get_vc_master()],
|
||||||
|
type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
|
|
||||||
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
|
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
|
||||||
@ -2573,7 +2555,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
choices=InterfaceTypeChoices,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
enabled = forms.BooleanField(
|
enabled = forms.BooleanField(
|
||||||
@ -2605,7 +2587,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
mode = forms.ChoiceField(
|
mode = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_MODE_CHOICES),
|
choices=add_blank_choice(InterfaceModeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
@ -2642,7 +2624,8 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
|
|||||||
# Limit LAG choices to interfaces belonging to this device (or its VC master)
|
# Limit LAG choices to interfaces belonging to this device (or its VC master)
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
self.fields['lag'].queryset = Interface.objects.filter(
|
||||||
device__in=[self.parent, self.parent.get_vc_master()], type=IFACE_TYPE_LAG
|
device__in=[self.parent, self.parent.get_vc_master()],
|
||||||
|
type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['lag'].queryset = Interface.objects.none()
|
self.fields['lag'].queryset = Interface.objects.none()
|
||||||
@ -2707,10 +2690,10 @@ class InterfaceCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
choices=InterfaceTypeChoices,
|
||||||
)
|
)
|
||||||
mode = CSVChoiceField(
|
mode = CSVChoiceField(
|
||||||
choices=IFACE_MODE_CHOICES,
|
choices=InterfaceModeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2732,7 +2715,7 @@ class InterfaceCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
if device:
|
if device:
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
self.fields['lag'].queryset = Interface.objects.filter(
|
||||||
device__in=[device, device.get_vc_master()], type=IFACE_TYPE_LAG
|
device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['lag'].queryset = Interface.objects.none()
|
self.fields['lag'].queryset = Interface.objects.none()
|
||||||
@ -2751,7 +2734,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_TYPE_CHOICES),
|
choices=add_blank_choice(InterfaceTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2785,7 +2768,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
mode = forms.ChoiceField(
|
mode = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_MODE_CHOICES),
|
choices=add_blank_choice(InterfaceModeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -2821,7 +2804,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
|||||||
if device is not None:
|
if device is not None:
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
self.fields['lag'].queryset = Interface.objects.filter(
|
||||||
device__in=[device, device.get_vc_master()],
|
device__in=[device, device.get_vc_master()],
|
||||||
type=IFACE_TYPE_LAG
|
type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['lag'].choices = []
|
self.fields['lag'].choices = []
|
||||||
@ -2911,7 +2894,7 @@ class FrontPortCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
rear_port_set = forms.MultipleChoiceField(
|
||||||
@ -2983,7 +2966,7 @@ class FrontPortCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -3019,7 +3002,7 @@ class FrontPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PORT_TYPE_CHOICES),
|
choices=add_blank_choice(PortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -3077,7 +3060,7 @@ class RearPortCreateForm(ComponentForm):
|
|||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
positions = forms.IntegerField(
|
positions = forms.IntegerField(
|
||||||
@ -3101,7 +3084,7 @@ class RearPortCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=PORT_TYPE_CHOICES,
|
choices=PortTypeChoices,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -3115,7 +3098,7 @@ class RearPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PORT_TYPE_CHOICES),
|
choices=add_blank_choice(PortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -3449,12 +3432,12 @@ class CableCSVForm(forms.ModelForm):
|
|||||||
help_text='Connection status'
|
help_text='Connection status'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=CABLE_TYPE_CHOICES,
|
choices=CableTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Cable type'
|
help_text='Cable type'
|
||||||
)
|
)
|
||||||
length_unit = CSVChoiceField(
|
length_unit = CSVChoiceField(
|
||||||
choices=CABLE_LENGTH_UNIT_CHOICES,
|
choices=CableLengthUnitChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Length unit'
|
help_text='Length unit'
|
||||||
)
|
)
|
||||||
@ -3534,7 +3517,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput
|
widget=forms.MultipleHiddenInput
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(CABLE_TYPE_CHOICES),
|
choices=add_blank_choice(CableTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -3559,7 +3542,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
length_unit = forms.ChoiceField(
|
length_unit = forms.ChoiceField(
|
||||||
choices=add_blank_choice(CABLE_LENGTH_UNIT_CHOICES),
|
choices=add_blank_choice(CableLengthUnitChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -3608,7 +3591,7 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
choices=add_blank_choice(CABLE_TYPE_CHOICES),
|
choices=add_blank_choice(CableTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -3677,7 +3660,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
|||||||
rack=device_bay.device.rack,
|
rack=device_bay.device.rack,
|
||||||
parent_bay__isnull=True,
|
parent_bay__isnull=True,
|
||||||
device_type__u_height=0,
|
device_type__u_height=0,
|
||||||
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD
|
device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
|
||||||
).exclude(pk=device_bay.device.pk)
|
).exclude(pk=device_bay.device.pk)
|
||||||
|
|
||||||
|
|
||||||
@ -3725,7 +3708,7 @@ class DeviceBayCSVForm(forms.ModelForm):
|
|||||||
rack=device.rack,
|
rack=device.rack,
|
||||||
parent_bay__isnull=True,
|
parent_bay__isnull=True,
|
||||||
device_type__u_height=0,
|
device_type__u_height=0,
|
||||||
device_type__subdevice_role=SUBDEVICE_ROLE_CHILD
|
device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
|
||||||
).exclude(pk=device.pk)
|
).exclude(pk=device.pk)
|
||||||
else:
|
else:
|
||||||
self.fields['installed_device'].queryset = Interface.objects.none()
|
self.fields['installed_device'].queryset = Interface.objects.none()
|
||||||
@ -4214,22 +4197,22 @@ class PowerFeedCSVForm(forms.ModelForm):
|
|||||||
help_text="Rack name (optional)"
|
help_text="Rack name (optional)"
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=POWERFEED_STATUS_CHOICES,
|
choices=PowerFeedStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
type = CSVChoiceField(
|
type = CSVChoiceField(
|
||||||
choices=POWERFEED_TYPE_CHOICES,
|
choices=PowerFeedTypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Primary or redundant'
|
help_text='Primary or redundant'
|
||||||
)
|
)
|
||||||
supply = CSVChoiceField(
|
supply = CSVChoiceField(
|
||||||
choices=POWERFEED_SUPPLY_CHOICES,
|
choices=PowerFeedSupplyChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='AC/DC'
|
help_text='AC/DC'
|
||||||
)
|
)
|
||||||
phase = CSVChoiceField(
|
phase = CSVChoiceField(
|
||||||
choices=POWERFEED_PHASE_CHOICES,
|
choices=PowerFeedPhaseChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Single or three-phase'
|
help_text='Single or three-phase'
|
||||||
)
|
)
|
||||||
@ -4289,25 +4272,25 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
|
choices=add_blank_choice(PowerFeedStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
|
choices=add_blank_choice(PowerFeedTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
supply = forms.ChoiceField(
|
supply = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
|
choices=add_blank_choice(PowerFeedSupplyChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
phase = forms.ChoiceField(
|
phase = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
|
choices=add_blank_choice(PowerFeedPhaseChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -4368,22 +4351,22 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=POWERFEED_STATUS_CHOICES,
|
choices=PowerFeedStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
|
choices=add_blank_choice(PowerFeedTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
supply = forms.ChoiceField(
|
supply = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_SUPPLY_CHOICES),
|
choices=add_blank_choice(PowerFeedSupplyChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
phase = forms.ChoiceField(
|
phase = forms.ChoiceField(
|
||||||
choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
|
choices=add_blank_choice(PowerFeedPhaseChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
|
35
netbox/dcim/migrations/0078_3569_site_fields.py
Normal file
35
netbox/dcim/migrations/0078_3569_site_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
92
netbox/dcim/migrations/0079_3569_rack_fields.py
Normal file
92
netbox/dcim/migrations/0079_3569_rack_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
39
netbox/dcim/migrations/0080_3569_devicetype_fields.py
Normal file
39
netbox/dcim/migrations/0080_3569_devicetype_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
65
netbox/dcim/migrations/0081_3569_device_fields.py
Normal file
65
netbox/dcim/migrations/0081_3569_device_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
147
netbox/dcim/migrations/0082_3569_interface_fields.py
Normal file
147
netbox/dcim/migrations/0082_3569_interface_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
93
netbox/dcim/migrations/0082_3569_port_fields.py
Normal file
93
netbox/dcim/migrations/0082_3569_port_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
]
|
85
netbox/dcim/migrations/0083_3569_cable_fields.py
Normal file
85
netbox/dcim/migrations/0083_3569_cable_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
100
netbox/dcim/migrations/0084_3569_powerfeed_fields.py
Normal file
100
netbox/dcim/migrations/0084_3569_powerfeed_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
62
netbox/dcim/migrations/0085_3569_poweroutlet_fields.py
Normal file
62
netbox/dcim/migrations/0085_3569_poweroutlet_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -245,9 +245,10 @@ class Site(ChangeLoggedModel, CustomFieldModel):
|
|||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=SITE_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=SITE_STATUS_ACTIVE
|
choices=SiteStatusChoices,
|
||||||
|
default=SiteStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
region = models.ForeignKey(
|
region = models.ForeignKey(
|
||||||
to='dcim.Region',
|
to='dcim.Region',
|
||||||
@ -331,6 +332,12 @@ class Site(ChangeLoggedModel, CustomFieldModel):
|
|||||||
'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments',
|
'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
SiteStatusChoices.STATUS_ACTIVE: 'success',
|
||||||
|
SiteStatusChoices.STATUS_PLANNED: 'info',
|
||||||
|
SiteStatusChoices.STATUS_RETIRED: 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
@ -362,7 +369,7 @@ class Site(ChangeLoggedModel, CustomFieldModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -473,9 +480,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=RACK_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=RACK_STATUS_ACTIVE
|
choices=RackStatusChoices,
|
||||||
|
default=RackStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
to='dcim.RackRole',
|
to='dcim.RackRole',
|
||||||
@ -497,15 +505,15 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
verbose_name='Asset tag',
|
verbose_name='Asset tag',
|
||||||
help_text='A unique tag used to identify this rack'
|
help_text='A unique tag used to identify this rack'
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=RACK_TYPE_CHOICES,
|
choices=RackTypeChoices,
|
||||||
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
|
||||||
verbose_name='Type'
|
verbose_name='Type'
|
||||||
)
|
)
|
||||||
width = models.PositiveSmallIntegerField(
|
width = models.PositiveSmallIntegerField(
|
||||||
choices=RACK_WIDTH_CHOICES,
|
choices=RackWidthChoices,
|
||||||
default=RACK_WIDTH_19IN,
|
default=RackWidthChoices.WIDTH_19IN,
|
||||||
verbose_name='Width',
|
verbose_name='Width',
|
||||||
help_text='Rail-to-rail width'
|
help_text='Rail-to-rail width'
|
||||||
)
|
)
|
||||||
@ -527,10 +535,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
outer_unit = models.PositiveSmallIntegerField(
|
outer_unit = models.CharField(
|
||||||
choices=RACK_DIMENSION_UNIT_CHOICES,
|
max_length=50,
|
||||||
|
choices=RackDimensionUnitChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
comments = models.TextField(
|
comments = models.TextField(
|
||||||
blank=True
|
blank=True
|
||||||
@ -552,6 +560,14 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
RackStatusChoices.STATUS_RESERVED: 'warning',
|
||||||
|
RackStatusChoices.STATUS_AVAILABLE: 'success',
|
||||||
|
RackStatusChoices.STATUS_PLANNED: 'info',
|
||||||
|
RackStatusChoices.STATUS_ACTIVE: 'primary',
|
||||||
|
RackStatusChoices.STATUS_DEPRECATED: 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['site', 'group', 'name']
|
ordering = ['site', 'group', 'name']
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@ -568,10 +584,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
# Validate outer dimensions and unit
|
# Validate outer dimensions and unit
|
||||||
if (self.outer_width is not None or self.outer_depth is not None) and self.outer_unit is None:
|
if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit:
|
||||||
raise ValidationError("Must specify a unit when setting an outer width/depth")
|
raise ValidationError("Must specify a unit when setting an outer width/depth")
|
||||||
elif self.outer_width is None and self.outer_depth is None:
|
elif self.outer_width is None and self.outer_depth is None:
|
||||||
self.outer_unit = None
|
self.outer_unit = ''
|
||||||
|
|
||||||
if self.pk:
|
if self.pk:
|
||||||
# Validate that Rack is tall enough to house the installed Devices
|
# Validate that Rack is tall enough to house the installed Devices
|
||||||
@ -644,9 +660,9 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False):
|
def get_rack_units(self, face=DeviceFaceChoices.FACE_FRONT, exclude=None, remove_redundant=False):
|
||||||
"""
|
"""
|
||||||
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
Return a list of rack units as dictionaries. Example: {'device': None, 'face': 0, 'id': 48, 'name': 'U48'}
|
||||||
Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.
|
Each key 'device' is either a Device or None. By default, multi-U devices are repeated for each U they occupy.
|
||||||
@ -678,10 +694,10 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return [u for u in elevation.values()]
|
return [u for u in elevation.values()]
|
||||||
|
|
||||||
def get_front_elevation(self):
|
def get_front_elevation(self):
|
||||||
return self.get_rack_units(face=RACK_FACE_FRONT, remove_redundant=True)
|
return self.get_rack_units(face=DeviceFaceChoices.FACE_FRONT, remove_redundant=True)
|
||||||
|
|
||||||
def get_rear_elevation(self):
|
def get_rear_elevation(self):
|
||||||
return self.get_rack_units(face=RACK_FACE_REAR, remove_redundant=True)
|
return self.get_rack_units(face=DeviceFaceChoices.FACE_REAR, remove_redundant=True)
|
||||||
|
|
||||||
def get_available_units(self, u_height=1, rack_face=None, exclude=list()):
|
def get_available_units(self, u_height=1, rack_face=None, exclude=list()):
|
||||||
"""
|
"""
|
||||||
@ -910,12 +926,13 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
|||||||
verbose_name='Is full depth',
|
verbose_name='Is full depth',
|
||||||
help_text='Device consumes both front and rear rack faces'
|
help_text='Device consumes both front and rear rack faces'
|
||||||
)
|
)
|
||||||
subdevice_role = models.NullBooleanField(
|
subdevice_role = models.CharField(
|
||||||
default=None,
|
max_length=50,
|
||||||
|
choices=SubdeviceRoleChoices,
|
||||||
|
blank=True,
|
||||||
verbose_name='Parent/child status',
|
verbose_name='Parent/child status',
|
||||||
choices=SUBDEVICE_ROLE_CHOICES,
|
help_text='Parent devices house child devices in device bays. Leave blank '
|
||||||
help_text='Parent devices house child devices in device bays. Select '
|
'if this device type is neither a parent nor a child.'
|
||||||
'"None" if this device type is neither a parent nor a child.'
|
|
||||||
)
|
)
|
||||||
comments = models.TextField(
|
comments = models.TextField(
|
||||||
blank=True
|
blank=True
|
||||||
@ -959,7 +976,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
|||||||
self.part_number,
|
self.part_number,
|
||||||
self.u_height,
|
self.u_height,
|
||||||
self.is_full_depth,
|
self.is_full_depth,
|
||||||
self.get_subdevice_role_display() if self.subdevice_role else None,
|
self.get_subdevice_role_display(),
|
||||||
self.comments,
|
self.comments,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -979,13 +996,15 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
|||||||
"{}U".format(d, d.rack, self.u_height)
|
"{}U".format(d, d.rack, self.u_height)
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.subdevice_role != SUBDEVICE_ROLE_PARENT and self.device_bay_templates.count():
|
if (
|
||||||
|
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
|
||||||
|
) and self.device_bay_templates.count():
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'subdevice_role': "Must delete all device bay templates associated with this device before "
|
'subdevice_role': "Must delete all device bay templates associated with this device before "
|
||||||
"declassifying it as a parent device."
|
"declassifying it as a parent device."
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.u_height and self.subdevice_role == SUBDEVICE_ROLE_CHILD:
|
if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': "Child device types must be 0U."
|
'u_height': "Child device types must be 0U."
|
||||||
})
|
})
|
||||||
@ -996,11 +1015,11 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_parent_device(self):
|
def is_parent_device(self):
|
||||||
return bool(self.subdevice_role)
|
return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_child_device(self):
|
def is_child_device(self):
|
||||||
return bool(self.subdevice_role is False)
|
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplate(ComponentTemplateModel):
|
class ConsolePortTemplate(ComponentTemplateModel):
|
||||||
@ -1017,7 +1036,7 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1052,7 +1071,7 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1087,7 +1106,7 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypes.CHOICES,
|
choices=PowerPortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
maximum_draw = models.PositiveSmallIntegerField(
|
maximum_draw = models.PositiveSmallIntegerField(
|
||||||
@ -1135,7 +1154,7 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
power_port = models.ForeignKey(
|
power_port = models.ForeignKey(
|
||||||
@ -1145,10 +1164,10 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
null=True,
|
null=True,
|
||||||
related_name='poweroutlet_templates'
|
related_name='poweroutlet_templates'
|
||||||
)
|
)
|
||||||
feed_leg = models.PositiveSmallIntegerField(
|
feed_leg = models.CharField(
|
||||||
choices=POWERFEED_LEG_CHOICES,
|
max_length=50,
|
||||||
|
choices=PowerOutletFeedLegChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
|
||||||
help_text="Phase (for three-phase feeds)"
|
help_text="Phase (for three-phase feeds)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1194,9 +1213,9 @@ class InterfaceTemplate(ComponentTemplateModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
max_length=50,
|
||||||
default=IFACE_TYPE_10GE_SFP_PLUS
|
choices=InterfaceTypeChoices
|
||||||
)
|
)
|
||||||
mgmt_only = models.BooleanField(
|
mgmt_only = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
@ -1233,8 +1252,9 @@ class FrontPortTemplate(ComponentTemplateModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=PORT_TYPE_CHOICES
|
max_length=50,
|
||||||
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
rear_port = models.ForeignKey(
|
rear_port = models.ForeignKey(
|
||||||
to='dcim.RearPortTemplate',
|
to='dcim.RearPortTemplate',
|
||||||
@ -1300,8 +1320,9 @@ class RearPortTemplate(ComponentTemplateModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=PORT_TYPE_CHOICES
|
max_length=50,
|
||||||
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
positions = models.PositiveSmallIntegerField(
|
positions = models.PositiveSmallIntegerField(
|
||||||
default=1,
|
default=1,
|
||||||
@ -1526,16 +1547,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
verbose_name='Position (U)',
|
verbose_name='Position (U)',
|
||||||
help_text='The lowest-numbered unit occupied by the device'
|
help_text='The lowest-numbered unit occupied by the device'
|
||||||
)
|
)
|
||||||
face = models.PositiveSmallIntegerField(
|
face = models.CharField(
|
||||||
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
choices=DeviceFaceChoices,
|
||||||
choices=RACK_FACE_CHOICES,
|
|
||||||
verbose_name='Rack face'
|
verbose_name='Rack face'
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=DEVICE_STATUS_ACTIVE,
|
choices=DeviceStatusChoices,
|
||||||
verbose_name='Status'
|
default=DeviceStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
primary_ip4 = models.OneToOneField(
|
primary_ip4 = models.OneToOneField(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
@ -1597,6 +1618,16 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
|
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
DeviceStatusChoices.STATUS_OFFLINE: 'warning',
|
||||||
|
DeviceStatusChoices.STATUS_ACTIVE: 'success',
|
||||||
|
DeviceStatusChoices.STATUS_PLANNED: 'info',
|
||||||
|
DeviceStatusChoices.STATUS_STAGED: 'primary',
|
||||||
|
DeviceStatusChoices.STATUS_FAILED: 'danger',
|
||||||
|
DeviceStatusChoices.STATUS_INVENTORY: 'default',
|
||||||
|
DeviceStatusChoices.STATUS_DECOMMISSIONING: 'warning',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@ -1625,7 +1656,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if self.rack is None:
|
if self.rack is None:
|
||||||
if self.face is not None:
|
if self.face:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'face': "Cannot select a rack face without assigning a rack.",
|
'face': "Cannot select a rack face without assigning a rack.",
|
||||||
})
|
})
|
||||||
@ -1635,7 +1666,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Validate position/face combination
|
# Validate position/face combination
|
||||||
if self.position and self.face is None:
|
if self.position and not self.face:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'face': "Must specify rack face when defining rack position.",
|
'face': "Must specify rack face when defining rack position.",
|
||||||
})
|
})
|
||||||
@ -1850,7 +1881,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
return Device.objects.filter(parent_bay__device=self.pk)
|
return Device.objects.filter(parent_bay__device=self.pk)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1871,7 +1902,7 @@ class ConsolePort(CableTermination, ComponentModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
connected_endpoint = models.OneToOneField(
|
connected_endpoint = models.OneToOneField(
|
||||||
@ -1928,7 +1959,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypes.CHOICES,
|
choices=ConsolePortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
connection_status = models.NullBooleanField(
|
connection_status = models.NullBooleanField(
|
||||||
@ -1977,7 +2008,7 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypes.CHOICES,
|
choices=PowerPortTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
maximum_draw = models.PositiveSmallIntegerField(
|
maximum_draw = models.PositiveSmallIntegerField(
|
||||||
@ -2077,8 +2108,8 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Calculate per-leg aggregates for three-phase feeds
|
# Calculate per-leg aggregates for three-phase feeds
|
||||||
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE:
|
if self._connected_powerfeed and self._connected_powerfeed.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
|
||||||
for leg, leg_name in POWERFEED_LEG_CHOICES:
|
for leg, leg_name in PowerOutletFeedLegChoices:
|
||||||
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
|
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
|
||||||
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
|
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
|
||||||
maximum_draw_total=Sum('maximum_draw'),
|
maximum_draw_total=Sum('maximum_draw'),
|
||||||
@ -2120,7 +2151,7 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypes.CHOICES,
|
choices=PowerOutletTypeChoices,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
power_port = models.ForeignKey(
|
power_port = models.ForeignKey(
|
||||||
@ -2130,10 +2161,10 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|||||||
null=True,
|
null=True,
|
||||||
related_name='poweroutlets'
|
related_name='poweroutlets'
|
||||||
)
|
)
|
||||||
feed_leg = models.PositiveSmallIntegerField(
|
feed_leg = models.CharField(
|
||||||
choices=POWERFEED_LEG_CHOICES,
|
max_length=50,
|
||||||
|
choices=PowerOutletFeedLegChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
|
||||||
help_text="Phase (for three-phase feeds)"
|
help_text="Phase (for three-phase feeds)"
|
||||||
)
|
)
|
||||||
connection_status = models.NullBooleanField(
|
connection_status = models.NullBooleanField(
|
||||||
@ -2226,9 +2257,9 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Parent LAG'
|
verbose_name='Parent LAG'
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=IFACE_TYPE_CHOICES,
|
max_length=50,
|
||||||
default=IFACE_TYPE_10GE_SFP_PLUS
|
choices=InterfaceTypeChoices
|
||||||
)
|
)
|
||||||
enabled = models.BooleanField(
|
enabled = models.BooleanField(
|
||||||
default=True
|
default=True
|
||||||
@ -2249,10 +2280,10 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
verbose_name='OOB Management',
|
verbose_name='OOB Management',
|
||||||
help_text='This interface is used only for out-of-band management'
|
help_text='This interface is used only for out-of-band management'
|
||||||
)
|
)
|
||||||
mode = models.PositiveSmallIntegerField(
|
mode = models.CharField(
|
||||||
choices=IFACE_MODE_CHOICES,
|
max_length=50,
|
||||||
|
choices=InterfaceModeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
untagged_vlan = models.ForeignKey(
|
untagged_vlan = models.ForeignKey(
|
||||||
to='ipam.VLAN',
|
to='ipam.VLAN',
|
||||||
@ -2311,7 +2342,7 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
raise ValidationError("An interface must belong to either a device or a virtual machine.")
|
raise ValidationError("An interface must belong to either a device or a virtual machine.")
|
||||||
|
|
||||||
# VM interfaces must be virtual
|
# VM interfaces must be virtual
|
||||||
if self.virtual_machine and self.type is not IFACE_TYPE_VIRTUAL:
|
if self.virtual_machine and self.type is not InterfaceTypeChoices.TYPE_VIRTUAL:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'type': "Virtual machines can only have virtual interfaces."
|
'type': "Virtual machines can only have virtual interfaces."
|
||||||
})
|
})
|
||||||
@ -2340,7 +2371,7 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Only a LAG can have LAG members
|
# Only a LAG can have LAG members
|
||||||
if self.type != IFACE_TYPE_LAG and self.member_interfaces.exists():
|
if self.type != InterfaceTypeChoices.TYPE_LAG and self.member_interfaces.exists():
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'type': "Cannot change interface type; it has LAG members ({}).".format(
|
'type': "Cannot change interface type; it has LAG members ({}).".format(
|
||||||
", ".join([iface.name for iface in self.member_interfaces.all()])
|
", ".join([iface.name for iface in self.member_interfaces.all()])
|
||||||
@ -2361,7 +2392,7 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
self.untagged_vlan = None
|
self.untagged_vlan = None
|
||||||
|
|
||||||
# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
|
# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
|
||||||
if self.pk and self.mode is not IFACE_MODE_TAGGED:
|
if self.pk and self.mode is not InterfaceModeChoices.MODE_TAGGED:
|
||||||
self.tagged_vlans.clear()
|
self.tagged_vlans.clear()
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
@ -2423,7 +2454,7 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_lag(self):
|
def is_lag(self):
|
||||||
return self.type == IFACE_TYPE_LAG
|
return self.type == InterfaceTypeChoices.TYPE_LAG
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def count_ipaddresses(self):
|
def count_ipaddresses(self):
|
||||||
@ -2446,8 +2477,9 @@ class FrontPort(CableTermination, ComponentModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=PORT_TYPE_CHOICES
|
max_length=50,
|
||||||
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
rear_port = models.ForeignKey(
|
rear_port = models.ForeignKey(
|
||||||
to='dcim.RearPort',
|
to='dcim.RearPort',
|
||||||
@ -2513,8 +2545,9 @@ class RearPort(CableTermination, ComponentModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=PORT_TYPE_CHOICES
|
max_length=50,
|
||||||
|
choices=PortTypeChoices
|
||||||
)
|
)
|
||||||
positions = models.PositiveSmallIntegerField(
|
positions = models.PositiveSmallIntegerField(
|
||||||
default=1,
|
default=1,
|
||||||
@ -2776,10 +2809,10 @@ class Cable(ChangeLoggedModel):
|
|||||||
ct_field='termination_b_type',
|
ct_field='termination_b_type',
|
||||||
fk_field='termination_b_id'
|
fk_field='termination_b_id'
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=CABLE_TYPE_CHOICES,
|
max_length=50,
|
||||||
blank=True,
|
choices=CableTypeChoices,
|
||||||
null=True
|
blank=True
|
||||||
)
|
)
|
||||||
status = models.BooleanField(
|
status = models.BooleanField(
|
||||||
choices=CONNECTION_STATUS_CHOICES,
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
@ -2796,10 +2829,10 @@ class Cable(ChangeLoggedModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
length_unit = models.PositiveSmallIntegerField(
|
length_unit = models.CharField(
|
||||||
choices=CABLE_LENGTH_UNIT_CHOICES,
|
max_length=50,
|
||||||
|
choices=CableLengthUnitChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
# Stores the normalized length (in meters) for database ordering
|
# Stores the normalized length (in meters) for database ordering
|
||||||
_abs_length = models.DecimalField(
|
_abs_length = models.DecimalField(
|
||||||
@ -2927,10 +2960,10 @@ class Cable(ChangeLoggedModel):
|
|||||||
))
|
))
|
||||||
|
|
||||||
# Validate length and length_unit
|
# Validate length and length_unit
|
||||||
if self.length is not None and self.length_unit is None:
|
if self.length is not None and not self.length_unit:
|
||||||
raise ValidationError("Must specify a unit when setting a cable length")
|
raise ValidationError("Must specify a unit when setting a cable length")
|
||||||
elif self.length is None:
|
elif self.length is None:
|
||||||
self.length_unit = None
|
self.length_unit = ''
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -3074,21 +3107,25 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=POWERFEED_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=POWERFEED_STATUS_ACTIVE
|
choices=PowerFeedStatusChoices,
|
||||||
|
default=PowerFeedStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=POWERFEED_TYPE_CHOICES,
|
max_length=50,
|
||||||
default=POWERFEED_TYPE_PRIMARY
|
choices=PowerFeedTypeChoices,
|
||||||
|
default=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
supply = models.PositiveSmallIntegerField(
|
supply = models.CharField(
|
||||||
choices=POWERFEED_SUPPLY_CHOICES,
|
max_length=50,
|
||||||
default=POWERFEED_SUPPLY_AC
|
choices=PowerFeedSupplyChoices,
|
||||||
|
default=PowerFeedSupplyChoices.SUPPLY_AC
|
||||||
)
|
)
|
||||||
phase = models.PositiveSmallIntegerField(
|
phase = models.CharField(
|
||||||
choices=POWERFEED_PHASE_CHOICES,
|
max_length=50,
|
||||||
default=POWERFEED_PHASE_SINGLE
|
choices=PowerFeedPhaseChoices,
|
||||||
|
default=PowerFeedPhaseChoices.PHASE_SINGLE
|
||||||
)
|
)
|
||||||
voltage = models.PositiveSmallIntegerField(
|
voltage = models.PositiveSmallIntegerField(
|
||||||
validators=[MinValueValidator(1)],
|
validators=[MinValueValidator(1)],
|
||||||
@ -3123,6 +3160,18 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
'amperage', 'max_utilization', 'comments',
|
'amperage', 'max_utilization', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
PowerFeedStatusChoices.STATUS_OFFLINE: 'warning',
|
||||||
|
PowerFeedStatusChoices.STATUS_ACTIVE: 'success',
|
||||||
|
PowerFeedStatusChoices.STATUS_PLANNED: 'info',
|
||||||
|
PowerFeedStatusChoices.STATUS_FAILED: 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_CLASS_MAP = {
|
||||||
|
PowerFeedTypeChoices.TYPE_PRIMARY: 'success',
|
||||||
|
PowerFeedTypeChoices.TYPE_REDUNDANT: 'info',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['power_panel', 'name']
|
ordering = ['power_panel', 'name']
|
||||||
unique_together = ['power_panel', 'name']
|
unique_together = ['power_panel', 'name']
|
||||||
@ -3162,7 +3211,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
|
|
||||||
# Cache the available_power property on the instance
|
# Cache the available_power property on the instance
|
||||||
kva = self.voltage * self.amperage * (self.max_utilization / 100)
|
kva = self.voltage * self.amperage * (self.max_utilization / 100)
|
||||||
if self.phase == POWERFEED_PHASE_3PHASE:
|
if self.phase == PowerFeedPhaseChoices.PHASE_3PHASE:
|
||||||
self.available_power = round(kva * 1.732)
|
self.available_power = round(kva * 1.732)
|
||||||
else:
|
else:
|
||||||
self.available_power = round(kva)
|
self.available_power = round(kva)
|
||||||
@ -3170,7 +3219,7 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
|
|||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def get_type_class(self):
|
def get_type_class(self):
|
||||||
return STATUS_CLASSES[self.type]
|
return self.TYPE_CLASS_MAP.get(self.type)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
@ -156,10 +156,6 @@ DEVICE_PRIMARY_IP = """
|
|||||||
{{ record.primary_ip4.address.ip|default:"" }}
|
{{ record.primary_ip4.address.ip|default:"" }}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SUBDEVICE_ROLE_TEMPLATE = """
|
|
||||||
{% if record.subdevice_role == True %}Parent{% elif record.subdevice_role == False %}Child{% else %}—{% endif %}
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEVICETYPE_INSTANCES_TEMPLATE = """
|
DEVICETYPE_INSTANCES_TEMPLATE = """
|
||||||
<a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a>
|
<a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a>
|
||||||
"""
|
"""
|
||||||
@ -391,10 +387,6 @@ class DeviceTypeTable(BaseTable):
|
|||||||
verbose_name='Device Type'
|
verbose_name='Device Type'
|
||||||
)
|
)
|
||||||
is_full_depth = BooleanColumn(verbose_name='Full Depth')
|
is_full_depth = BooleanColumn(verbose_name='Full Depth')
|
||||||
subdevice_role = tables.TemplateColumn(
|
|
||||||
template_code=SUBDEVICE_ROLE_TEMPLATE,
|
|
||||||
verbose_name='Subdevice Role'
|
|
||||||
)
|
|
||||||
instance_count = tables.TemplateColumn(
|
instance_count = tables.TemplateColumn(
|
||||||
template_code=DEVICETYPE_INSTANCES_TEMPLATE,
|
template_code=DEVICETYPE_INSTANCES_TEMPLATE,
|
||||||
verbose_name='Instances'
|
verbose_name='Instances'
|
||||||
|
@ -3,6 +3,7 @@ from netaddr import IPNetwork
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
@ -180,7 +181,7 @@ class SiteTest(APITestCase):
|
|||||||
'name': 'Test Site 4',
|
'name': 'Test Site 4',
|
||||||
'slug': 'test-site-4',
|
'slug': 'test-site-4',
|
||||||
'region': self.region1.pk,
|
'region': self.region1.pk,
|
||||||
'status': SITE_STATUS_ACTIVE,
|
'status': SiteStatusChoices.STATUS_ACTIVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('dcim-api:site-list')
|
url = reverse('dcim-api:site-list')
|
||||||
@ -200,19 +201,19 @@ class SiteTest(APITestCase):
|
|||||||
'name': 'Test Site 4',
|
'name': 'Test Site 4',
|
||||||
'slug': 'test-site-4',
|
'slug': 'test-site-4',
|
||||||
'region': self.region1.pk,
|
'region': self.region1.pk,
|
||||||
'status': SITE_STATUS_ACTIVE,
|
'status': SiteStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Site 5',
|
'name': 'Test Site 5',
|
||||||
'slug': 'test-site-5',
|
'slug': 'test-site-5',
|
||||||
'region': self.region1.pk,
|
'region': self.region1.pk,
|
||||||
'status': SITE_STATUS_ACTIVE,
|
'status': SiteStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Site 6',
|
'name': 'Test Site 6',
|
||||||
'slug': 'test-site-6',
|
'slug': 'test-site-6',
|
||||||
'region': self.region1.pk,
|
'region': self.region1.pk,
|
||||||
'status': SITE_STATUS_ACTIVE,
|
'status': SiteStatusChoices.STATUS_ACTIVE,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2473,7 +2474,7 @@ class InterfaceTest(APITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name': 'Test Interface 4',
|
'name': 'Test Interface 4',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan3.id,
|
'untagged_vlan': self.vlan3.id,
|
||||||
'tagged_vlans': [self.vlan1.id, self.vlan2.id],
|
'tagged_vlans': [self.vlan1.id, self.vlan2.id],
|
||||||
}
|
}
|
||||||
@ -2520,21 +2521,21 @@ class InterfaceTest(APITestCase):
|
|||||||
{
|
{
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name': 'Test Interface 4',
|
'name': 'Test Interface 4',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name': 'Test Interface 5',
|
'name': 'Test Interface 5',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name': 'Test Interface 6',
|
'name': 'Test Interface 6',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
@ -2553,7 +2554,7 @@ class InterfaceTest(APITestCase):
|
|||||||
def test_update_interface(self):
|
def test_update_interface(self):
|
||||||
|
|
||||||
lag_interface = Interface.objects.create(
|
lag_interface = Interface.objects.create(
|
||||||
device=self.device, name='Test LAG Interface', type=IFACE_TYPE_LAG
|
device=self.device, name='Test LAG Interface', type=InterfaceTypeChoices.TYPE_LAG
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2590,11 +2591,11 @@ class DeviceBayTest(APITestCase):
|
|||||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||||
self.devicetype1 = DeviceType.objects.create(
|
self.devicetype1 = DeviceType.objects.create(
|
||||||
manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type',
|
manufacturer=manufacturer, model='Parent Device Type', slug='parent-device-type',
|
||||||
subdevice_role=SUBDEVICE_ROLE_PARENT
|
subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
|
||||||
)
|
)
|
||||||
self.devicetype2 = DeviceType.objects.create(
|
self.devicetype2 = DeviceType.objects.create(
|
||||||
manufacturer=manufacturer, model='Child Device Type', slug='child-device-type',
|
manufacturer=manufacturer, model='Child Device Type', slug='child-device-type',
|
||||||
subdevice_role=SUBDEVICE_ROLE_CHILD
|
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
|
||||||
)
|
)
|
||||||
devicerole = DeviceRole.objects.create(
|
devicerole = DeviceRole.objects.create(
|
||||||
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
|
||||||
@ -2841,7 +2842,7 @@ class CableTest(APITestCase):
|
|||||||
)
|
)
|
||||||
for device in [self.device1, self.device2]:
|
for device in [self.device1, self.device2]:
|
||||||
for i in range(0, 10):
|
for i in range(0, 10):
|
||||||
Interface(device=device, type=IFACE_TYPE_1GE_FIXED, name='eth{}'.format(i)).save()
|
Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth{}'.format(i)).save()
|
||||||
|
|
||||||
self.cable1 = Cable(
|
self.cable1 = Cable(
|
||||||
termination_a=self.device1.interfaces.get(name='eth0'),
|
termination_a=self.device1.interfaces.get(name='eth0'),
|
||||||
@ -3033,16 +3034,16 @@ class ConnectionTest(APITestCase):
|
|||||||
device=self.device2, name='Test Console Server Port 1'
|
device=self.device2, name='Test Console Server Port 1'
|
||||||
)
|
)
|
||||||
rearport1 = RearPort.objects.create(
|
rearport1 = RearPort.objects.create(
|
||||||
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C
|
device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport1 = FrontPort.objects.create(
|
frontport1 = FrontPort.objects.create(
|
||||||
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1
|
device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
|
||||||
)
|
)
|
||||||
rearport2 = RearPort.objects.create(
|
rearport2 = RearPort.objects.create(
|
||||||
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C
|
device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport2 = FrontPort.objects.create(
|
frontport2 = FrontPort.objects.create(
|
||||||
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2
|
device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
|
||||||
)
|
)
|
||||||
|
|
||||||
url = reverse('dcim-api:cable-list')
|
url = reverse('dcim-api:cable-list')
|
||||||
@ -3161,16 +3162,16 @@ class ConnectionTest(APITestCase):
|
|||||||
device=self.device2, name='Test Interface 2'
|
device=self.device2, name='Test Interface 2'
|
||||||
)
|
)
|
||||||
rearport1 = RearPort.objects.create(
|
rearport1 = RearPort.objects.create(
|
||||||
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C
|
device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport1 = FrontPort.objects.create(
|
frontport1 = FrontPort.objects.create(
|
||||||
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1
|
device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
|
||||||
)
|
)
|
||||||
rearport2 = RearPort.objects.create(
|
rearport2 = RearPort.objects.create(
|
||||||
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C
|
device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport2 = FrontPort.objects.create(
|
frontport2 = FrontPort.objects.create(
|
||||||
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2
|
device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
|
||||||
)
|
)
|
||||||
|
|
||||||
url = reverse('dcim-api:cable-list')
|
url = reverse('dcim-api:cable-list')
|
||||||
@ -3272,16 +3273,16 @@ class ConnectionTest(APITestCase):
|
|||||||
circuit=circuit, term_side='A', site=self.site, port_speed=10000
|
circuit=circuit, term_side='A', site=self.site, port_speed=10000
|
||||||
)
|
)
|
||||||
rearport1 = RearPort.objects.create(
|
rearport1 = RearPort.objects.create(
|
||||||
device=self.panel1, name='Test Rear Port 1', type=PORT_TYPE_8P8C
|
device=self.panel1, name='Test Rear Port 1', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport1 = FrontPort.objects.create(
|
frontport1 = FrontPort.objects.create(
|
||||||
device=self.panel1, name='Test Front Port 1', type=PORT_TYPE_8P8C, rear_port=rearport1
|
device=self.panel1, name='Test Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport1
|
||||||
)
|
)
|
||||||
rearport2 = RearPort.objects.create(
|
rearport2 = RearPort.objects.create(
|
||||||
device=self.panel2, name='Test Rear Port 2', type=PORT_TYPE_8P8C
|
device=self.panel2, name='Test Rear Port 2', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
frontport2 = FrontPort.objects.create(
|
frontport2 = FrontPort.objects.create(
|
||||||
device=self.panel2, name='Test Front Port 2', type=PORT_TYPE_8P8C, rear_port=rearport2
|
device=self.panel2, name='Test Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rearport2
|
||||||
)
|
)
|
||||||
|
|
||||||
url = reverse('dcim-api:cable-list')
|
url = reverse('dcim-api:cable-list')
|
||||||
@ -3410,23 +3411,23 @@ class VirtualChassisTest(APITestCase):
|
|||||||
device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
|
device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
|
||||||
)
|
)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device1, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device1, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device2, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device2, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device3, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device3, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device4, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device4, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device5, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device5, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device6, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device6, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device7, name='1/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device7, name='1/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device8, name='2/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device8, name='2/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
for i in range(0, 13):
|
for i in range(0, 13):
|
||||||
Interface.objects.create(device=self.device9, name='3/{}'.format(i), type=IFACE_TYPE_1GE_FIXED)
|
Interface.objects.create(device=self.device9, name='3/{}'.format(i), type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
|
|
||||||
# Create two VirtualChassis with three members each
|
# Create two VirtualChassis with three members each
|
||||||
self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1')
|
self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1')
|
||||||
@ -3678,22 +3679,22 @@ class PowerFeedTest(APITestCase):
|
|||||||
site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 2'
|
site=self.site1, rack_group=self.rackgroup1, name='Test Power Panel 2'
|
||||||
)
|
)
|
||||||
self.powerfeed1 = PowerFeed.objects.create(
|
self.powerfeed1 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel1, rack=self.rack1, name='Test Power Feed 1A', type=POWERFEED_TYPE_PRIMARY
|
power_panel=self.powerpanel1, rack=self.rack1, name='Test Power Feed 1A', type=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
self.powerfeed2 = PowerFeed.objects.create(
|
self.powerfeed2 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel2, rack=self.rack1, name='Test Power Feed 1B', type=POWERFEED_TYPE_REDUNDANT
|
power_panel=self.powerpanel2, rack=self.rack1, name='Test Power Feed 1B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
|
||||||
)
|
)
|
||||||
self.powerfeed3 = PowerFeed.objects.create(
|
self.powerfeed3 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel1, rack=self.rack2, name='Test Power Feed 2A', type=POWERFEED_TYPE_PRIMARY
|
power_panel=self.powerpanel1, rack=self.rack2, name='Test Power Feed 2A', type=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
self.powerfeed4 = PowerFeed.objects.create(
|
self.powerfeed4 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel2, rack=self.rack2, name='Test Power Feed 2B', type=POWERFEED_TYPE_REDUNDANT
|
power_panel=self.powerpanel2, rack=self.rack2, name='Test Power Feed 2B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
|
||||||
)
|
)
|
||||||
self.powerfeed5 = PowerFeed.objects.create(
|
self.powerfeed5 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel1, rack=self.rack3, name='Test Power Feed 3A', type=POWERFEED_TYPE_PRIMARY
|
power_panel=self.powerpanel1, rack=self.rack3, name='Test Power Feed 3A', type=PowerFeedTypeChoices.TYPE_PRIMARY
|
||||||
)
|
)
|
||||||
self.powerfeed6 = PowerFeed.objects.create(
|
self.powerfeed6 = PowerFeed.objects.create(
|
||||||
power_panel=self.powerpanel2, rack=self.rack3, name='Test Power Feed 3B', type=POWERFEED_TYPE_REDUNDANT
|
power_panel=self.powerpanel2, rack=self.rack3, name='Test Power Feed 3B', type=PowerFeedTypeChoices.TYPE_REDUNDANT
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_powerfeed(self):
|
def test_get_powerfeed(self):
|
||||||
@ -3726,7 +3727,7 @@ class PowerFeedTest(APITestCase):
|
|||||||
'name': 'Test Power Feed 4A',
|
'name': 'Test Power Feed 4A',
|
||||||
'power_panel': self.powerpanel1.pk,
|
'power_panel': self.powerpanel1.pk,
|
||||||
'rack': self.rack4.pk,
|
'rack': self.rack4.pk,
|
||||||
'type': POWERFEED_TYPE_PRIMARY,
|
'type': PowerFeedTypeChoices.TYPE_PRIMARY,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('dcim-api:powerfeed-list')
|
url = reverse('dcim-api:powerfeed-list')
|
||||||
@ -3746,13 +3747,13 @@ class PowerFeedTest(APITestCase):
|
|||||||
'name': 'Test Power Feed 4A',
|
'name': 'Test Power Feed 4A',
|
||||||
'power_panel': self.powerpanel1.pk,
|
'power_panel': self.powerpanel1.pk,
|
||||||
'rack': self.rack4.pk,
|
'rack': self.rack4.pk,
|
||||||
'type': POWERFEED_TYPE_PRIMARY,
|
'type': PowerFeedTypeChoices.TYPE_PRIMARY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Test Power Feed 4B',
|
'name': 'Test Power Feed 4B',
|
||||||
'power_panel': self.powerpanel1.pk,
|
'power_panel': self.powerpanel1.pk,
|
||||||
'rack': self.rack4.pk,
|
'rack': self.rack4.pk,
|
||||||
'type': POWERFEED_TYPE_REDUNDANT,
|
'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3769,7 +3770,7 @@ class PowerFeedTest(APITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'name': 'Test Power Feed X',
|
'name': 'Test Power Feed X',
|
||||||
'rack': self.rack4.pk,
|
'rack': self.rack4.pk,
|
||||||
'type': POWERFEED_TYPE_REDUNDANT,
|
'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})
|
url = reverse('dcim-api:powerfeed-detail', kwargs={'pk': self.powerfeed1.pk})
|
||||||
|
@ -21,10 +21,10 @@ class DeviceTestCase(TestCase):
|
|||||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
||||||
'site': get_id(Site, 'test1'),
|
'site': get_id(Site, 'test1'),
|
||||||
'rack': '1',
|
'rack': '1',
|
||||||
'face': RACK_FACE_FRONT,
|
'face': DeviceFaceChoices.FACE_FRONT,
|
||||||
'position': 41,
|
'position': 41,
|
||||||
'platform': get_id(Platform, 'juniper-junos'),
|
'platform': get_id(Platform, 'juniper-junos'),
|
||||||
'status': DEVICE_STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid(), test.fields['position'].choices)
|
self.assertTrue(test.is_valid(), test.fields['position'].choices)
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(test.save())
|
||||||
@ -38,10 +38,10 @@ class DeviceTestCase(TestCase):
|
|||||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
||||||
'site': get_id(Site, 'test1'),
|
'site': get_id(Site, 'test1'),
|
||||||
'rack': '1',
|
'rack': '1',
|
||||||
'face': RACK_FACE_FRONT,
|
'face': DeviceFaceChoices.FACE_FRONT,
|
||||||
'position': 1,
|
'position': 1,
|
||||||
'platform': get_id(Platform, 'juniper-junos'),
|
'platform': get_id(Platform, 'juniper-junos'),
|
||||||
'status': DEVICE_STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertFalse(test.is_valid())
|
self.assertFalse(test.is_valid())
|
||||||
|
|
||||||
@ -54,10 +54,10 @@ class DeviceTestCase(TestCase):
|
|||||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
||||||
'site': get_id(Site, 'test1'),
|
'site': get_id(Site, 'test1'),
|
||||||
'rack': '1',
|
'rack': '1',
|
||||||
'face': None,
|
'face': '',
|
||||||
'position': None,
|
'position': None,
|
||||||
'platform': None,
|
'platform': None,
|
||||||
'status': DEVICE_STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid())
|
self.assertTrue(test.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(test.save())
|
||||||
@ -71,10 +71,10 @@ class DeviceTestCase(TestCase):
|
|||||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
||||||
'site': get_id(Site, 'test1'),
|
'site': get_id(Site, 'test1'),
|
||||||
'rack': '1',
|
'rack': '1',
|
||||||
'face': RACK_FACE_REAR,
|
'face': DeviceFaceChoices.FACE_REAR,
|
||||||
'position': None,
|
'position': None,
|
||||||
'platform': None,
|
'platform': None,
|
||||||
'status': DEVICE_STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid())
|
self.assertTrue(test.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(test.save())
|
||||||
|
@ -87,7 +87,7 @@ class RackTestCase(TestCase):
|
|||||||
site=self.site1,
|
site=self.site1,
|
||||||
rack=rack1,
|
rack=rack1,
|
||||||
position=43,
|
position=43,
|
||||||
face=RACK_FACE_FRONT,
|
face=DeviceFaceChoices.FACE_FRONT,
|
||||||
)
|
)
|
||||||
device1.save()
|
device1.save()
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class RackTestCase(TestCase):
|
|||||||
site=self.site1,
|
site=self.site1,
|
||||||
rack=self.rack,
|
rack=self.rack,
|
||||||
position=10,
|
position=10,
|
||||||
face=RACK_FACE_REAR,
|
face=DeviceFaceChoices.FACE_REAR,
|
||||||
)
|
)
|
||||||
device1.save()
|
device1.save()
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ class RackTestCase(TestCase):
|
|||||||
site=self.site1,
|
site=self.site1,
|
||||||
rack=self.rack,
|
rack=self.rack,
|
||||||
position=None,
|
position=None,
|
||||||
face=None,
|
face='',
|
||||||
)
|
)
|
||||||
self.assertTrue(pdu)
|
self.assertTrue(pdu)
|
||||||
|
|
||||||
@ -187,20 +187,20 @@ class DeviceTestCase(TestCase):
|
|||||||
device_type=self.device_type,
|
device_type=self.device_type,
|
||||||
name='Power Outlet 1',
|
name='Power Outlet 1',
|
||||||
power_port=ppt,
|
power_port=ppt,
|
||||||
feed_leg=POWERFEED_LEG_A
|
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
InterfaceTemplate(
|
InterfaceTemplate(
|
||||||
device_type=self.device_type,
|
device_type=self.device_type,
|
||||||
name='Interface 1',
|
name='Interface 1',
|
||||||
type=IFACE_TYPE_1GE_FIXED,
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
||||||
mgmt_only=True
|
mgmt_only=True
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
rpt = RearPortTemplate(
|
rpt = RearPortTemplate(
|
||||||
device_type=self.device_type,
|
device_type=self.device_type,
|
||||||
name='Rear Port 1',
|
name='Rear Port 1',
|
||||||
type=PORT_TYPE_8P8C,
|
type=PortTypeChoices.TYPE_8P8C,
|
||||||
positions=8
|
positions=8
|
||||||
)
|
)
|
||||||
rpt.save()
|
rpt.save()
|
||||||
@ -208,7 +208,7 @@ class DeviceTestCase(TestCase):
|
|||||||
FrontPortTemplate(
|
FrontPortTemplate(
|
||||||
device_type=self.device_type,
|
device_type=self.device_type,
|
||||||
name='Front Port 1',
|
name='Front Port 1',
|
||||||
type=PORT_TYPE_8P8C,
|
type=PortTypeChoices.TYPE_8P8C,
|
||||||
rear_port=rpt,
|
rear_port=rpt,
|
||||||
rear_port_position=2
|
rear_port_position=2
|
||||||
).save()
|
).save()
|
||||||
@ -251,27 +251,27 @@ class DeviceTestCase(TestCase):
|
|||||||
device=d,
|
device=d,
|
||||||
name='Power Outlet 1',
|
name='Power Outlet 1',
|
||||||
power_port=pp,
|
power_port=pp,
|
||||||
feed_leg=POWERFEED_LEG_A
|
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
|
||||||
)
|
)
|
||||||
|
|
||||||
Interface.objects.get(
|
Interface.objects.get(
|
||||||
device=d,
|
device=d,
|
||||||
name='Interface 1',
|
name='Interface 1',
|
||||||
type=IFACE_TYPE_1GE_FIXED,
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
||||||
mgmt_only=True
|
mgmt_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
rp = RearPort.objects.get(
|
rp = RearPort.objects.get(
|
||||||
device=d,
|
device=d,
|
||||||
name='Rear Port 1',
|
name='Rear Port 1',
|
||||||
type=PORT_TYPE_8P8C,
|
type=PortTypeChoices.TYPE_8P8C,
|
||||||
positions=8
|
positions=8
|
||||||
)
|
)
|
||||||
|
|
||||||
FrontPort.objects.get(
|
FrontPort.objects.get(
|
||||||
device=d,
|
device=d,
|
||||||
name='Front Port 1',
|
name='Front Port 1',
|
||||||
type=PORT_TYPE_8P8C,
|
type=PortTypeChoices.TYPE_8P8C,
|
||||||
rear_port=rp,
|
rear_port=rp,
|
||||||
rear_port_position=2
|
rear_port_position=2
|
||||||
)
|
)
|
||||||
@ -379,7 +379,7 @@ class CableTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
A cable cannot terminate to a virtual interface
|
A cable cannot terminate to a virtual interface
|
||||||
"""
|
"""
|
||||||
virtual_interface = Interface(device=self.device1, name="V1", type=IFACE_TYPE_VIRTUAL)
|
virtual_interface = Interface(device=self.device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
|
||||||
cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
|
cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cable.clean()
|
cable.clean()
|
||||||
@ -388,7 +388,7 @@ class CableTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
A cable cannot terminate to a wireless interface
|
A cable cannot terminate to a wireless interface
|
||||||
"""
|
"""
|
||||||
wireless_interface = Interface(device=self.device1, name="W1", type=IFACE_TYPE_80211A)
|
wireless_interface = Interface(device=self.device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
|
||||||
cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
|
cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cable.clean()
|
cable.clean()
|
||||||
@ -421,16 +421,16 @@ class CablePathTestCase(TestCase):
|
|||||||
device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site
|
device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site
|
||||||
)
|
)
|
||||||
self.rear_port1 = RearPort.objects.create(
|
self.rear_port1 = RearPort.objects.create(
|
||||||
device=self.panel1, name='Rear Port 1', type=PORT_TYPE_8P8C
|
device=self.panel1, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
self.front_port1 = FrontPort.objects.create(
|
self.front_port1 = FrontPort.objects.create(
|
||||||
device=self.panel1, name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=self.rear_port1
|
device=self.panel1, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port1
|
||||||
)
|
)
|
||||||
self.rear_port2 = RearPort.objects.create(
|
self.rear_port2 = RearPort.objects.create(
|
||||||
device=self.panel2, name='Rear Port 2', type=PORT_TYPE_8P8C
|
device=self.panel2, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C
|
||||||
)
|
)
|
||||||
self.front_port2 = FrontPort.objects.create(
|
self.front_port2 = FrontPort.objects.create(
|
||||||
device=self.panel2, name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=self.rear_port2
|
device=self.panel2, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port2
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_path_completion(self):
|
def test_path_completion(self):
|
||||||
|
@ -255,15 +255,15 @@ power-outlets:
|
|||||||
- name: Power Outlet 1
|
- name: Power Outlet 1
|
||||||
type: iec-60320-c13
|
type: iec-60320-c13
|
||||||
power_port: Power Port 1
|
power_port: Power Port 1
|
||||||
feed_leg: 1
|
feed_leg: A
|
||||||
- name: Power Outlet 2
|
- name: Power Outlet 2
|
||||||
type: iec-60320-c13
|
type: iec-60320-c13
|
||||||
power_port: Power Port 1
|
power_port: Power Port 1
|
||||||
feed_leg: 1
|
feed_leg: A
|
||||||
- name: Power Outlet 3
|
- name: Power Outlet 3
|
||||||
type: iec-60320-c13
|
type: iec-60320-c13
|
||||||
power_port: Power Port 1
|
power_port: Power Port 1
|
||||||
feed_leg: 1
|
feed_leg: A
|
||||||
interfaces:
|
interfaces:
|
||||||
- name: Interface 1
|
- name: Interface 1
|
||||||
type: 1000base-t
|
type: 1000base-t
|
||||||
@ -326,29 +326,29 @@ device-bays:
|
|||||||
self.assertEqual(dt.consoleport_templates.count(), 3)
|
self.assertEqual(dt.consoleport_templates.count(), 3)
|
||||||
cp1 = ConsolePortTemplate.objects.first()
|
cp1 = ConsolePortTemplate.objects.first()
|
||||||
self.assertEqual(cp1.name, 'Console Port 1')
|
self.assertEqual(cp1.name, 'Console Port 1')
|
||||||
self.assertEqual(cp1.type, ConsolePortTypes.TYPE_DE9)
|
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
||||||
|
|
||||||
self.assertEqual(dt.consoleserverport_templates.count(), 3)
|
self.assertEqual(dt.consoleserverport_templates.count(), 3)
|
||||||
csp1 = ConsoleServerPortTemplate.objects.first()
|
csp1 = ConsoleServerPortTemplate.objects.first()
|
||||||
self.assertEqual(csp1.name, 'Console Server Port 1')
|
self.assertEqual(csp1.name, 'Console Server Port 1')
|
||||||
self.assertEqual(csp1.type, ConsolePortTypes.TYPE_RJ45)
|
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
||||||
|
|
||||||
self.assertEqual(dt.powerport_templates.count(), 3)
|
self.assertEqual(dt.powerport_templates.count(), 3)
|
||||||
pp1 = PowerPortTemplate.objects.first()
|
pp1 = PowerPortTemplate.objects.first()
|
||||||
self.assertEqual(pp1.name, 'Power Port 1')
|
self.assertEqual(pp1.name, 'Power Port 1')
|
||||||
self.assertEqual(pp1.type, PowerPortTypes.TYPE_IEC_C14)
|
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
||||||
|
|
||||||
self.assertEqual(dt.poweroutlet_templates.count(), 3)
|
self.assertEqual(dt.poweroutlet_templates.count(), 3)
|
||||||
po1 = PowerOutletTemplate.objects.first()
|
po1 = PowerOutletTemplate.objects.first()
|
||||||
self.assertEqual(po1.name, 'Power Outlet 1')
|
self.assertEqual(po1.name, 'Power Outlet 1')
|
||||||
self.assertEqual(po1.type, PowerOutletTypes.TYPE_IEC_C13)
|
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
||||||
self.assertEqual(po1.power_port, pp1)
|
self.assertEqual(po1.power_port, pp1)
|
||||||
self.assertEqual(po1.feed_leg, POWERFEED_LEG_A)
|
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
||||||
|
|
||||||
self.assertEqual(dt.interface_templates.count(), 3)
|
self.assertEqual(dt.interface_templates.count(), 3)
|
||||||
iface1 = InterfaceTemplate.objects.first()
|
iface1 = InterfaceTemplate.objects.first()
|
||||||
self.assertEqual(iface1.name, 'Interface 1')
|
self.assertEqual(iface1.name, 'Interface 1')
|
||||||
self.assertEqual(iface1.type, IFACE_TYPE_1GE_FIXED)
|
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
self.assertTrue(iface1.mgmt_only)
|
self.assertTrue(iface1.mgmt_only)
|
||||||
|
|
||||||
self.assertEqual(dt.rearport_templates.count(), 3)
|
self.assertEqual(dt.rearport_templates.count(), 3)
|
||||||
@ -514,28 +514,28 @@ class CableTestCase(TestCase):
|
|||||||
device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole)
|
device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole)
|
||||||
device2.save()
|
device2.save()
|
||||||
|
|
||||||
iface1 = Interface(device=device1, name='Interface 1', type=IFACE_TYPE_1GE_FIXED)
|
iface1 = Interface(device=device1, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface1.save()
|
iface1.save()
|
||||||
iface2 = Interface(device=device1, name='Interface 2', type=IFACE_TYPE_1GE_FIXED)
|
iface2 = Interface(device=device1, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface2.save()
|
iface2.save()
|
||||||
iface3 = Interface(device=device1, name='Interface 3', type=IFACE_TYPE_1GE_FIXED)
|
iface3 = Interface(device=device1, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface3.save()
|
iface3.save()
|
||||||
iface4 = Interface(device=device2, name='Interface 1', type=IFACE_TYPE_1GE_FIXED)
|
iface4 = Interface(device=device2, name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface4.save()
|
iface4.save()
|
||||||
iface5 = Interface(device=device2, name='Interface 2', type=IFACE_TYPE_1GE_FIXED)
|
iface5 = Interface(device=device2, name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface5.save()
|
iface5.save()
|
||||||
iface6 = Interface(device=device2, name='Interface 3', type=IFACE_TYPE_1GE_FIXED)
|
iface6 = Interface(device=device2, name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
|
||||||
iface6.save()
|
iface6.save()
|
||||||
|
|
||||||
Cable(termination_a=iface1, termination_b=iface4, type=CABLE_TYPE_CAT6).save()
|
Cable(termination_a=iface1, termination_b=iface4, type=CableTypeChoices.TYPE_CAT6).save()
|
||||||
Cable(termination_a=iface2, termination_b=iface5, type=CABLE_TYPE_CAT6).save()
|
Cable(termination_a=iface2, termination_b=iface5, type=CableTypeChoices.TYPE_CAT6).save()
|
||||||
Cable(termination_a=iface3, termination_b=iface6, type=CABLE_TYPE_CAT6).save()
|
Cable(termination_a=iface3, termination_b=iface6, type=CableTypeChoices.TYPE_CAT6).save()
|
||||||
|
|
||||||
def test_cable_list(self):
|
def test_cable_list(self):
|
||||||
|
|
||||||
url = reverse('dcim:cable_list')
|
url = reverse('dcim:cable_list')
|
||||||
params = {
|
params = {
|
||||||
"type": CABLE_TYPE_CAT6,
|
"type": CableTypeChoices.TYPE_CAT6,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||||
|
@ -5,7 +5,7 @@ from django.db import transaction
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from extras.constants import *
|
from extras.choices import *
|
||||||
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
|
from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
|
||||||
from utilities.api import ValidatedModelSerializer
|
from utilities.api import ValidatedModelSerializer
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
|||||||
if value not in [None, '']:
|
if value not in [None, '']:
|
||||||
|
|
||||||
# Validate integer
|
# Validate integer
|
||||||
if cf.type == CF_TYPE_INTEGER:
|
if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||||
try:
|
try:
|
||||||
int(value)
|
int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -46,13 +46,13 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Validate boolean
|
# Validate boolean
|
||||||
if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
if cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value not in [True, False, 1, 0]:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"Invalid value for boolean field {}: {}".format(field_name, value)
|
"Invalid value for boolean field {}: {}".format(field_name, value)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate date
|
# Validate date
|
||||||
if cf.type == CF_TYPE_DATE:
|
if cf.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||||
try:
|
try:
|
||||||
datetime.strptime(value, '%Y-%m-%d')
|
datetime.strptime(value, '%Y-%m-%d')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -61,7 +61,7 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Validate selected choice
|
# Validate selected choice
|
||||||
if cf.type == CF_TYPE_SELECT:
|
if cf.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -100,7 +100,7 @@ class CustomFieldModelSerializer(ValidatedModelSerializer):
|
|||||||
instance.custom_fields = {}
|
instance.custom_fields = {}
|
||||||
for field in fields:
|
for field in fields:
|
||||||
value = instance.cf.get(field.name)
|
value = instance.cf.get(field.name)
|
||||||
if field.type == CF_TYPE_SELECT and value is not None:
|
if field.type == CustomFieldTypeChoices.TYPE_SELECT and value is not None:
|
||||||
instance.custom_fields[field.name] = CustomFieldChoiceSerializer(value).data
|
instance.custom_fields[field.name] = CustomFieldChoiceSerializer(value).data
|
||||||
else:
|
else:
|
||||||
instance.custom_fields[field.name] = value
|
instance.custom_fields[field.name] = value
|
||||||
|
@ -8,6 +8,7 @@ from dcim.api.nested_serializers import (
|
|||||||
NestedRegionSerializer, NestedSiteSerializer,
|
NestedRegionSerializer, NestedSiteSerializer,
|
||||||
)
|
)
|
||||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
||||||
|
from extras.choices import *
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.models import (
|
from extras.models import (
|
||||||
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
|
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, ReportResult, Tag,
|
||||||
@ -56,8 +57,8 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class ExportTemplateSerializer(ValidatedModelSerializer):
|
class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||||
template_language = ChoiceField(
|
template_language = ChoiceField(
|
||||||
choices=TEMPLATE_LANGUAGE_CHOICES,
|
choices=ExportTemplateLanguageChoices,
|
||||||
default=TEMPLATE_LANGUAGE_JINJA2
|
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -255,7 +256,7 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
|
|||||||
read_only=True
|
read_only=True
|
||||||
)
|
)
|
||||||
action = ChoiceField(
|
action = ChoiceField(
|
||||||
choices=OBJECTCHANGE_ACTION_CHOICES,
|
choices=ObjectChangeActionChoices,
|
||||||
read_only=True
|
read_only=True
|
||||||
)
|
)
|
||||||
changed_object_type = ContentTypeField(
|
changed_object_type = ContentTypeField(
|
||||||
|
140
netbox/extras/choices.py
Normal file
140
netbox/extras/choices.py
Normal 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,
|
||||||
|
}
|
@ -19,32 +19,6 @@ CUSTOMFIELD_MODELS = [
|
|||||||
'virtualization.virtualmachine',
|
'virtualization.virtualmachine',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Custom field types
|
|
||||||
CF_TYPE_TEXT = 100
|
|
||||||
CF_TYPE_INTEGER = 200
|
|
||||||
CF_TYPE_BOOLEAN = 300
|
|
||||||
CF_TYPE_DATE = 400
|
|
||||||
CF_TYPE_URL = 500
|
|
||||||
CF_TYPE_SELECT = 600
|
|
||||||
CUSTOMFIELD_TYPE_CHOICES = (
|
|
||||||
(CF_TYPE_TEXT, 'Text'),
|
|
||||||
(CF_TYPE_INTEGER, 'Integer'),
|
|
||||||
(CF_TYPE_BOOLEAN, 'Boolean (true/false)'),
|
|
||||||
(CF_TYPE_DATE, 'Date'),
|
|
||||||
(CF_TYPE_URL, 'URL'),
|
|
||||||
(CF_TYPE_SELECT, 'Selection'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Custom field filter logic choices
|
|
||||||
CF_FILTER_DISABLED = 0
|
|
||||||
CF_FILTER_LOOSE = 1
|
|
||||||
CF_FILTER_EXACT = 2
|
|
||||||
CF_FILTER_CHOICES = (
|
|
||||||
(CF_FILTER_DISABLED, 'Disabled'),
|
|
||||||
(CF_FILTER_LOOSE, 'Loose'),
|
|
||||||
(CF_FILTER_EXACT, 'Exact'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Custom links
|
# Custom links
|
||||||
CUSTOMLINK_MODELS = [
|
CUSTOMLINK_MODELS = [
|
||||||
'circuits.circuit',
|
'circuits.circuit',
|
||||||
@ -68,23 +42,6 @@ CUSTOMLINK_MODELS = [
|
|||||||
'virtualization.virtualmachine',
|
'virtualization.virtualmachine',
|
||||||
]
|
]
|
||||||
|
|
||||||
BUTTON_CLASS_DEFAULT = 'default'
|
|
||||||
BUTTON_CLASS_PRIMARY = 'primary'
|
|
||||||
BUTTON_CLASS_SUCCESS = 'success'
|
|
||||||
BUTTON_CLASS_INFO = 'info'
|
|
||||||
BUTTON_CLASS_WARNING = 'warning'
|
|
||||||
BUTTON_CLASS_DANGER = 'danger'
|
|
||||||
BUTTON_CLASS_LINK = 'link'
|
|
||||||
BUTTON_CLASS_CHOICES = (
|
|
||||||
(BUTTON_CLASS_DEFAULT, 'Default'),
|
|
||||||
(BUTTON_CLASS_PRIMARY, 'Primary (blue)'),
|
|
||||||
(BUTTON_CLASS_SUCCESS, 'Success (green)'),
|
|
||||||
(BUTTON_CLASS_INFO, 'Info (aqua)'),
|
|
||||||
(BUTTON_CLASS_WARNING, 'Warning (orange)'),
|
|
||||||
(BUTTON_CLASS_DANGER, 'Danger (red)'),
|
|
||||||
(BUTTON_CLASS_LINK, 'None (link)'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Graph types
|
# Graph types
|
||||||
GRAPH_TYPE_INTERFACE = 100
|
GRAPH_TYPE_INTERFACE = 100
|
||||||
GRAPH_TYPE_DEVICE = 150
|
GRAPH_TYPE_DEVICE = 150
|
||||||
@ -128,42 +85,6 @@ EXPORTTEMPLATE_MODELS = [
|
|||||||
'virtualization.virtualmachine',
|
'virtualization.virtualmachine',
|
||||||
]
|
]
|
||||||
|
|
||||||
# ExportTemplate language choices
|
|
||||||
TEMPLATE_LANGUAGE_DJANGO = 10
|
|
||||||
TEMPLATE_LANGUAGE_JINJA2 = 20
|
|
||||||
TEMPLATE_LANGUAGE_CHOICES = (
|
|
||||||
(TEMPLATE_LANGUAGE_DJANGO, 'Django'),
|
|
||||||
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Change log actions
|
|
||||||
OBJECTCHANGE_ACTION_CREATE = 1
|
|
||||||
OBJECTCHANGE_ACTION_UPDATE = 2
|
|
||||||
OBJECTCHANGE_ACTION_DELETE = 3
|
|
||||||
OBJECTCHANGE_ACTION_CHOICES = (
|
|
||||||
(OBJECTCHANGE_ACTION_CREATE, 'Created'),
|
|
||||||
(OBJECTCHANGE_ACTION_UPDATE, 'Updated'),
|
|
||||||
(OBJECTCHANGE_ACTION_DELETE, 'Deleted'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# User action types
|
|
||||||
ACTION_CREATE = 1
|
|
||||||
ACTION_IMPORT = 2
|
|
||||||
ACTION_EDIT = 3
|
|
||||||
ACTION_BULK_EDIT = 4
|
|
||||||
ACTION_DELETE = 5
|
|
||||||
ACTION_BULK_DELETE = 6
|
|
||||||
ACTION_BULK_CREATE = 7
|
|
||||||
ACTION_CHOICES = (
|
|
||||||
(ACTION_CREATE, 'created'),
|
|
||||||
(ACTION_BULK_CREATE, 'bulk created'),
|
|
||||||
(ACTION_IMPORT, 'imported'),
|
|
||||||
(ACTION_EDIT, 'modified'),
|
|
||||||
(ACTION_BULK_EDIT, 'bulk edited'),
|
|
||||||
(ACTION_DELETE, 'deleted'),
|
|
||||||
(ACTION_BULK_DELETE, 'bulk deleted'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Report logging levels
|
# Report logging levels
|
||||||
LOG_DEFAULT = 0
|
LOG_DEFAULT = 0
|
||||||
LOG_SUCCESS = 10
|
LOG_SUCCESS = 10
|
||||||
@ -178,14 +99,6 @@ LOG_LEVEL_CODES = {
|
|||||||
LOG_FAILURE: 'failure',
|
LOG_FAILURE: 'failure',
|
||||||
}
|
}
|
||||||
|
|
||||||
# webhook content types
|
|
||||||
WEBHOOK_CT_JSON = 1
|
|
||||||
WEBHOOK_CT_X_WWW_FORM_ENCODED = 2
|
|
||||||
WEBHOOK_CT_CHOICES = (
|
|
||||||
(WEBHOOK_CT_JSON, 'application/json'),
|
|
||||||
(WEBHOOK_CT_X_WWW_FORM_ENCODED, 'application/x-www-form-urlencoded'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Models which support registered webhooks
|
# Models which support registered webhooks
|
||||||
WEBHOOK_MODELS = [
|
WEBHOOK_MODELS = [
|
||||||
'circuits.circuit',
|
'circuits.circuit',
|
||||||
|
@ -4,6 +4,7 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from dcim.models import DeviceRole, Platform, Region, Site
|
from dcim.models import DeviceRole, Platform, Region, Site
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
|
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# Selection fields get special treatment (values must be integers)
|
# Selection fields get special treatment (values must be integers)
|
||||||
if self.cf_type == CF_TYPE_SELECT:
|
if self.cf_type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
try:
|
try:
|
||||||
# Treat 0 as None
|
# Treat 0 as None
|
||||||
if int(value) == 0:
|
if int(value) == 0:
|
||||||
@ -42,7 +43,8 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
# Apply the assigned filter logic (exact or loose)
|
# Apply the assigned filter logic (exact or loose)
|
||||||
if self.cf_type == CF_TYPE_BOOLEAN or self.filter_logic == CF_FILTER_EXACT:
|
if (self.cf_type == CustomFieldTypeChoices.TYPE_BOOLEAN or
|
||||||
|
self.filter_logic == CustomFieldFilterLogicChoices.FILTER_EXACT):
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
custom_field_values__field__name=self.field_name,
|
custom_field_values__field__name=self.field_name,
|
||||||
custom_field_values__serialized_value=value
|
custom_field_values__serialized_value=value
|
||||||
@ -65,7 +67,11 @@ class CustomFieldFilterSet(django_filters.FilterSet):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||||
custom_fields = CustomField.objects.filter(obj_type=obj_type).exclude(filter_logic=CF_FILTER_DISABLED)
|
custom_fields = CustomField.objects.filter(
|
||||||
|
obj_type=obj_type
|
||||||
|
).exclude(
|
||||||
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
||||||
|
)
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ from utilities.forms import (
|
|||||||
CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2,
|
CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2,
|
||||||
BOOLEAN_WITH_BLANK_CHOICES,
|
BOOLEAN_WITH_BLANK_CHOICES,
|
||||||
)
|
)
|
||||||
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
|
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
|
||||||
|
|
||||||
@ -28,18 +29,18 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
field_dict = OrderedDict()
|
field_dict = OrderedDict()
|
||||||
custom_fields = CustomField.objects.filter(obj_type=content_type)
|
custom_fields = CustomField.objects.filter(obj_type=content_type)
|
||||||
if filterable_only:
|
if filterable_only:
|
||||||
custom_fields = custom_fields.exclude(filter_logic=CF_FILTER_DISABLED)
|
custom_fields = custom_fields.exclude(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED)
|
||||||
|
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
field_name = 'cf_{}'.format(str(cf.name))
|
field_name = 'cf_{}'.format(str(cf.name))
|
||||||
initial = cf.default if not bulk_edit else None
|
initial = cf.default if not bulk_edit else None
|
||||||
|
|
||||||
# Integer
|
# Integer
|
||||||
if cf.type == CF_TYPE_INTEGER:
|
if cf.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||||
field = forms.IntegerField(required=cf.required, initial=initial)
|
field = forms.IntegerField(required=cf.required, initial=initial)
|
||||||
|
|
||||||
# Boolean
|
# Boolean
|
||||||
elif cf.type == CF_TYPE_BOOLEAN:
|
elif cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||||
choices = (
|
choices = (
|
||||||
(None, '---------'),
|
(None, '---------'),
|
||||||
(1, 'True'),
|
(1, 'True'),
|
||||||
@ -56,11 +57,11 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Date
|
# Date
|
||||||
elif cf.type == CF_TYPE_DATE:
|
elif cf.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||||
field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
|
field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
|
||||||
|
|
||||||
# Select
|
# Select
|
||||||
elif cf.type == CF_TYPE_SELECT:
|
elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
||||||
if not cf.required or bulk_edit or filterable_only:
|
if not cf.required or bulk_edit or filterable_only:
|
||||||
choices = [(None, '---------')] + choices
|
choices = [(None, '---------')] + choices
|
||||||
@ -74,7 +75,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
||||||
|
|
||||||
# URL
|
# URL
|
||||||
elif cf.type == CF_TYPE_URL:
|
elif cf.type == CustomFieldTypeChoices.TYPE_URL:
|
||||||
field = LaxURLField(required=cf.required, initial=initial)
|
field = LaxURLField(required=cf.required, initial=initial)
|
||||||
|
|
||||||
# Text
|
# Text
|
||||||
@ -400,7 +401,7 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
action = forms.ChoiceField(
|
action = forms.ChoiceField(
|
||||||
choices=add_blank_choice(OBJECTCHANGE_ACTION_CHOICES),
|
choices=add_blank_choice(ObjectChangeActionChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
user = forms.ModelChoiceField(
|
user = forms.ModelChoiceField(
|
||||||
|
@ -10,7 +10,7 @@ from django.utils import timezone
|
|||||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||||
|
|
||||||
from utilities.querysets import DummyQuerySet
|
from utilities.querysets import DummyQuerySet
|
||||||
from .constants import *
|
from .choices import ObjectChangeActionChoices
|
||||||
from .models import ObjectChange
|
from .models import ObjectChange
|
||||||
from .signals import purge_changelog
|
from .signals import purge_changelog
|
||||||
from .webhooks import enqueue_webhooks
|
from .webhooks import enqueue_webhooks
|
||||||
@ -23,7 +23,7 @@ def handle_changed_object(sender, instance, **kwargs):
|
|||||||
Fires when an object is created or updated.
|
Fires when an object is created or updated.
|
||||||
"""
|
"""
|
||||||
# Queue the object for processing once the request completes
|
# Queue the object for processing once the request completes
|
||||||
action = OBJECTCHANGE_ACTION_CREATE if kwargs['created'] else OBJECTCHANGE_ACTION_UPDATE
|
action = ObjectChangeActionChoices.ACTION_CREATE if kwargs['created'] else ObjectChangeActionChoices.ACTION_UPDATE
|
||||||
_thread_locals.changed_objects.append(
|
_thread_locals.changed_objects.append(
|
||||||
(instance, action)
|
(instance, action)
|
||||||
)
|
)
|
||||||
@ -46,7 +46,7 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
|
|
||||||
# Queue the copy of the object for processing once the request completes
|
# Queue the copy of the object for processing once the request completes
|
||||||
_thread_locals.changed_objects.append(
|
_thread_locals.changed_objects.append(
|
||||||
(copy, OBJECTCHANGE_ACTION_DELETE)
|
(copy, ObjectChangeActionChoices.ACTION_DELETE)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ class ObjectChangeMiddleware(object):
|
|||||||
for instance, action in _thread_locals.changed_objects:
|
for instance, action in _thread_locals.changed_objects:
|
||||||
|
|
||||||
# Refresh cached custom field values
|
# Refresh cached custom field values
|
||||||
if action in [OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_UPDATE]:
|
if action in [ObjectChangeActionChoices.ACTION_CREATE, ObjectChangeActionChoices.ACTION_UPDATE]:
|
||||||
if hasattr(instance, 'cache_custom_fields'):
|
if hasattr(instance, 'cache_custom_fields'):
|
||||||
instance.cache_custom_fields()
|
instance.cache_custom_fields()
|
||||||
|
|
||||||
@ -116,11 +116,11 @@ class ObjectChangeMiddleware(object):
|
|||||||
enqueue_webhooks(instance, request.user, request.id, action)
|
enqueue_webhooks(instance, request.user, request.id, action)
|
||||||
|
|
||||||
# Increment metric counters
|
# Increment metric counters
|
||||||
if action == OBJECTCHANGE_ACTION_CREATE:
|
if action == ObjectChangeActionChoices.ACTION_CREATE:
|
||||||
model_inserts.labels(instance._meta.model_name).inc()
|
model_inserts.labels(instance._meta.model_name).inc()
|
||||||
elif action == OBJECTCHANGE_ACTION_UPDATE:
|
elif action == ObjectChangeActionChoices.ACTION_UPDATE:
|
||||||
model_updates.labels(instance._meta.model_name).inc()
|
model_updates.labels(instance._meta.model_name).inc()
|
||||||
elif action == OBJECTCHANGE_ACTION_DELETE:
|
elif action == ObjectChangeActionChoices.ACTION_DELETE:
|
||||||
model_deletes.labels(instance._meta.model_name).inc()
|
model_deletes.labels(instance._meta.model_name).inc()
|
||||||
|
|
||||||
# Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in
|
# Housekeeping: 1% chance of clearing out expired ObjectChanges. This applies only to requests which result in
|
||||||
|
@ -8,8 +8,6 @@ import django.db.models.deletion
|
|||||||
import extras.models
|
import extras.models
|
||||||
from django.db.utils import OperationalError
|
from django.db.utils import OperationalError
|
||||||
|
|
||||||
from extras.constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_FILTER_LOOSE, CF_TYPE_SELECT
|
|
||||||
|
|
||||||
|
|
||||||
def verify_postgresql_version(apps, schema_editor):
|
def verify_postgresql_version(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
|
@ -2,21 +2,19 @@
|
|||||||
# Generated by Django 1.11.9 on 2018-02-21 19:48
|
# Generated by Django 1.11.9 on 2018-02-21 19:48
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
from extras.constants import CF_FILTER_DISABLED, CF_FILTER_EXACT, CF_FILTER_LOOSE, CF_TYPE_SELECT
|
|
||||||
|
|
||||||
|
|
||||||
def is_filterable_to_filter_logic(apps, schema_editor):
|
def is_filterable_to_filter_logic(apps, schema_editor):
|
||||||
CustomField = apps.get_model('extras', 'CustomField')
|
CustomField = apps.get_model('extras', 'CustomField')
|
||||||
CustomField.objects.filter(is_filterable=False).update(filter_logic=CF_FILTER_DISABLED)
|
CustomField.objects.filter(is_filterable=False).update(filter_logic=0)
|
||||||
CustomField.objects.filter(is_filterable=True).update(filter_logic=CF_FILTER_LOOSE)
|
CustomField.objects.filter(is_filterable=True).update(filter_logic=1)
|
||||||
# Select fields match on primary key only
|
# Select fields match on primary key only
|
||||||
CustomField.objects.filter(is_filterable=True, type=CF_TYPE_SELECT).update(filter_logic=CF_FILTER_EXACT)
|
CustomField.objects.filter(is_filterable=True, type=600).update(filter_logic=2)
|
||||||
|
|
||||||
|
|
||||||
def filter_logic_to_is_filterable(apps, schema_editor):
|
def filter_logic_to_is_filterable(apps, schema_editor):
|
||||||
CustomField = apps.get_model('extras', 'CustomField')
|
CustomField = apps.get_model('extras', 'CustomField')
|
||||||
CustomField.objects.filter(filter_logic=CF_FILTER_DISABLED).update(is_filterable=False)
|
CustomField.objects.filter(filter_logic=0).update(is_filterable=False)
|
||||||
CustomField.objects.exclude(filter_logic=CF_FILTER_DISABLED).update(is_filterable=True)
|
CustomField.objects.exclude(filter_logic=0).update(is_filterable=True)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
69
netbox/extras/migrations/0029_3569_customfield_fields.py
Normal file
69
netbox/extras/migrations/0029_3569_customfield_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
36
netbox/extras/migrations/0030_3569_objectchange_fields.py
Normal file
36
netbox/extras/migrations/0030_3569_objectchange_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
35
netbox/extras/migrations/0031_3569_exporttemplate_fields.py
Normal file
35
netbox/extras/migrations/0031_3569_exporttemplate_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
35
netbox/extras/migrations/0032_3569_webhook_fields.py
Normal file
35
netbox/extras/migrations/0032_3569_webhook_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -15,6 +15,7 @@ from taggit.models import TagBase, GenericTaggedItemBase
|
|||||||
|
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
from utilities.utils import deepmerge, model_names_to_filter_dict
|
from utilities.utils import deepmerge, model_names_to_filter_dict
|
||||||
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .querysets import ConfigContextQuerySet
|
from .querysets import ConfigContextQuerySet
|
||||||
|
|
||||||
@ -62,9 +63,10 @@ class Webhook(models.Model):
|
|||||||
verbose_name='URL',
|
verbose_name='URL',
|
||||||
help_text="A POST will be sent to this URL when the webhook is called."
|
help_text="A POST will be sent to this URL when the webhook is called."
|
||||||
)
|
)
|
||||||
http_content_type = models.PositiveSmallIntegerField(
|
http_content_type = models.CharField(
|
||||||
choices=WEBHOOK_CT_CHOICES,
|
max_length=50,
|
||||||
default=WEBHOOK_CT_JSON,
|
choices=WebhookContentTypeChoices,
|
||||||
|
default=WebhookContentTypeChoices.CONTENTTYPE_JSON,
|
||||||
verbose_name='HTTP content type'
|
verbose_name='HTTP content type'
|
||||||
)
|
)
|
||||||
additional_headers = JSONField(
|
additional_headers = JSONField(
|
||||||
@ -182,9 +184,10 @@ class CustomField(models.Model):
|
|||||||
limit_choices_to=get_custom_field_models,
|
limit_choices_to=get_custom_field_models,
|
||||||
help_text='The object(s) to which this field applies.'
|
help_text='The object(s) to which this field applies.'
|
||||||
)
|
)
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.CharField(
|
||||||
choices=CUSTOMFIELD_TYPE_CHOICES,
|
max_length=50,
|
||||||
default=CF_TYPE_TEXT
|
choices=CustomFieldTypeChoices,
|
||||||
|
default=CustomFieldTypeChoices.TYPE_TEXT
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -205,9 +208,10 @@ class CustomField(models.Model):
|
|||||||
help_text='If true, this field is required when creating new objects '
|
help_text='If true, this field is required when creating new objects '
|
||||||
'or editing an existing object.'
|
'or editing an existing object.'
|
||||||
)
|
)
|
||||||
filter_logic = models.PositiveSmallIntegerField(
|
filter_logic = models.CharField(
|
||||||
choices=CF_FILTER_CHOICES,
|
max_length=50,
|
||||||
default=CF_FILTER_LOOSE,
|
choices=CustomFieldFilterLogicChoices,
|
||||||
|
default=CustomFieldFilterLogicChoices.FILTER_LOOSE,
|
||||||
help_text='Loose matches any instance of a given string; exact '
|
help_text='Loose matches any instance of a given string; exact '
|
||||||
'matches the entire field.'
|
'matches the entire field.'
|
||||||
)
|
)
|
||||||
@ -233,15 +237,15 @@ class CustomField(models.Model):
|
|||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return ''
|
return ''
|
||||||
if self.type == CF_TYPE_BOOLEAN:
|
if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||||
return str(int(bool(value)))
|
return str(int(bool(value)))
|
||||||
if self.type == CF_TYPE_DATE:
|
if self.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||||
# Could be date/datetime object or string
|
# Could be date/datetime object or string
|
||||||
try:
|
try:
|
||||||
return value.strftime('%Y-%m-%d')
|
return value.strftime('%Y-%m-%d')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return value
|
return value
|
||||||
if self.type == CF_TYPE_SELECT:
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
# Could be ModelChoiceField or TypedChoiceField
|
# Could be ModelChoiceField or TypedChoiceField
|
||||||
return str(value.id) if hasattr(value, 'id') else str(value)
|
return str(value.id) if hasattr(value, 'id') else str(value)
|
||||||
return value
|
return value
|
||||||
@ -252,14 +256,14 @@ class CustomField(models.Model):
|
|||||||
"""
|
"""
|
||||||
if serialized_value == '':
|
if serialized_value == '':
|
||||||
return None
|
return None
|
||||||
if self.type == CF_TYPE_INTEGER:
|
if self.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||||
return int(serialized_value)
|
return int(serialized_value)
|
||||||
if self.type == CF_TYPE_BOOLEAN:
|
if self.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||||
return bool(int(serialized_value))
|
return bool(int(serialized_value))
|
||||||
if self.type == CF_TYPE_DATE:
|
if self.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||||
# Read date as YYYY-MM-DD
|
# Read date as YYYY-MM-DD
|
||||||
return date(*[int(n) for n in serialized_value.split('-')])
|
return date(*[int(n) for n in serialized_value.split('-')])
|
||||||
if self.type == CF_TYPE_SELECT:
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
return self.choices.get(pk=int(serialized_value))
|
return self.choices.get(pk=int(serialized_value))
|
||||||
return serialized_value
|
return serialized_value
|
||||||
|
|
||||||
@ -312,7 +316,7 @@ class CustomFieldChoice(models.Model):
|
|||||||
to='extras.CustomField',
|
to='extras.CustomField',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='choices',
|
related_name='choices',
|
||||||
limit_choices_to={'type': CF_TYPE_SELECT}
|
limit_choices_to={'type': CustomFieldTypeChoices.TYPE_SELECT}
|
||||||
)
|
)
|
||||||
value = models.CharField(
|
value = models.CharField(
|
||||||
max_length=100
|
max_length=100
|
||||||
@ -330,14 +334,17 @@ class CustomFieldChoice(models.Model):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.field.type != CF_TYPE_SELECT:
|
if self.field.type != CustomFieldTypeChoices.TYPE_SELECT:
|
||||||
raise ValidationError("Custom field choices can only be assigned to selection fields.")
|
raise ValidationError("Custom field choices can only be assigned to selection fields.")
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
# When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it
|
# When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it
|
||||||
pk = self.pk
|
pk = self.pk
|
||||||
super().delete(using, keep_parents)
|
super().delete(using, keep_parents)
|
||||||
CustomFieldValue.objects.filter(field__type=CF_TYPE_SELECT, serialized_value=str(pk)).delete()
|
CustomFieldValue.objects.filter(
|
||||||
|
field__type=CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
|
serialized_value=str(pk)
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -381,8 +388,8 @@ class CustomLink(models.Model):
|
|||||||
)
|
)
|
||||||
button_class = models.CharField(
|
button_class = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=BUTTON_CLASS_CHOICES,
|
choices=CustomLinkButtonClassChoices,
|
||||||
default=BUTTON_CLASS_DEFAULT,
|
default=CustomLinkButtonClassChoices.CLASS_DEFAULT,
|
||||||
help_text="The class of the first link in a group will be used for the dropdown button"
|
help_text="The class of the first link in a group will be used for the dropdown button"
|
||||||
)
|
)
|
||||||
new_window = models.BooleanField(
|
new_window = models.BooleanField(
|
||||||
@ -458,9 +465,10 @@ class ExportTemplate(models.Model):
|
|||||||
max_length=200,
|
max_length=200,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
template_language = models.PositiveSmallIntegerField(
|
template_language = models.CharField(
|
||||||
choices=TEMPLATE_LANGUAGE_CHOICES,
|
max_length=50,
|
||||||
default=TEMPLATE_LANGUAGE_JINJA2
|
choices=ExportTemplateLanguageChoices,
|
||||||
|
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
|
||||||
)
|
)
|
||||||
template_code = models.TextField(
|
template_code = models.TextField(
|
||||||
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.'
|
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.'
|
||||||
@ -796,8 +804,9 @@ class ObjectChange(models.Model):
|
|||||||
request_id = models.UUIDField(
|
request_id = models.UUIDField(
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
action = models.PositiveSmallIntegerField(
|
action = models.CharField(
|
||||||
choices=OBJECTCHANGE_ACTION_CHOICES
|
max_length=50,
|
||||||
|
choices=ObjectChangeActionChoices
|
||||||
)
|
)
|
||||||
changed_object_type = models.ForeignKey(
|
changed_object_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
|
@ -38,11 +38,11 @@ OBJECTCHANGE_TIME = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
OBJECTCHANGE_ACTION = """
|
OBJECTCHANGE_ACTION = """
|
||||||
{% if record.action == 1 %}
|
{% if record.action == 'create' %}
|
||||||
<span class="label label-success">Created</span>
|
<span class="label label-success">Created</span>
|
||||||
{% elif record.action == 2 %}
|
{% elif record.action == 'update' %}
|
||||||
<span class="label label-primary">Updated</span>
|
<span class="label label-primary">Updated</span>
|
||||||
{% elif record.action == 3 %}
|
{% elif record.action == 'delete' %}
|
||||||
<span class="label label-danger">Deleted</span>
|
<span class="label label-danger">Deleted</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
@ -3,6 +3,7 @@ from django.urls import reverse
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
|
from extras.choices import *
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.models import CustomField, CustomFieldValue, ObjectChange
|
from extras.models import CustomField, CustomFieldValue, ObjectChange
|
||||||
from utilities.testing import APITestCase
|
from utilities.testing import APITestCase
|
||||||
@ -17,7 +18,7 @@ class ChangeLogTest(APITestCase):
|
|||||||
# Create a custom field on the Site model
|
# Create a custom field on the Site model
|
||||||
ct = ContentType.objects.get_for_model(Site)
|
ct = ContentType.objects.get_for_model(Site)
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
type=CF_TYPE_TEXT,
|
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||||
name='my_field',
|
name='my_field',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
@ -49,7 +50,7 @@ class ChangeLogTest(APITestCase):
|
|||||||
changed_object_id=site.pk
|
changed_object_id=site.pk
|
||||||
)
|
)
|
||||||
self.assertEqual(oc.changed_object, site)
|
self.assertEqual(oc.changed_object, site)
|
||||||
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_CREATE)
|
self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE)
|
||||||
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
|
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
|
||||||
self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
|
self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ class ChangeLogTest(APITestCase):
|
|||||||
changed_object_id=site.pk
|
changed_object_id=site.pk
|
||||||
)
|
)
|
||||||
self.assertEqual(oc.changed_object, site)
|
self.assertEqual(oc.changed_object, site)
|
||||||
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_UPDATE)
|
self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE)
|
||||||
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
|
self.assertEqual(oc.object_data['custom_fields'], data['custom_fields'])
|
||||||
self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
|
self.assertListEqual(sorted(oc.object_data['tags']), data['tags'])
|
||||||
|
|
||||||
@ -110,6 +111,6 @@ class ChangeLogTest(APITestCase):
|
|||||||
oc = ObjectChange.objects.first()
|
oc = ObjectChange.objects.first()
|
||||||
self.assertEqual(oc.changed_object, None)
|
self.assertEqual(oc.changed_object, None)
|
||||||
self.assertEqual(oc.object_repr, site.name)
|
self.assertEqual(oc.object_repr, site.name)
|
||||||
self.assertEqual(oc.action, OBJECTCHANGE_ACTION_DELETE)
|
self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE)
|
||||||
self.assertEqual(oc.object_data['custom_fields'], {'my_field': 'ABC'})
|
self.assertEqual(oc.object_data['custom_fields'], {'my_field': 'ABC'})
|
||||||
self.assertListEqual(sorted(oc.object_data['tags']), ['bar', 'foo'])
|
self.assertListEqual(sorted(oc.object_data['tags']), ['bar', 'foo'])
|
||||||
|
@ -6,7 +6,7 @@ from django.urls import reverse
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_SELECT, CF_TYPE_URL, CF_TYPE_SELECT
|
from extras.choices import *
|
||||||
from extras.models import CustomField, CustomFieldValue, CustomFieldChoice
|
from extras.models import CustomField, CustomFieldValue, CustomFieldChoice
|
||||||
from utilities.testing import APITestCase
|
from utilities.testing import APITestCase
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
@ -25,13 +25,13 @@ class CustomFieldTest(TestCase):
|
|||||||
def test_simple_fields(self):
|
def test_simple_fields(self):
|
||||||
|
|
||||||
DATA = (
|
DATA = (
|
||||||
{'field_type': CF_TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
|
{'field_type': CustomFieldTypeChoices.TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
|
||||||
{'field_type': CF_TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
|
{'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
|
||||||
{'field_type': CF_TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
|
{'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
|
||||||
{'field_type': CF_TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
|
{'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
|
||||||
{'field_type': CF_TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
|
{'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
|
||||||
{'field_type': CF_TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None},
|
{'field_type': CustomFieldTypeChoices.TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None},
|
||||||
{'field_type': CF_TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''},
|
{'field_type': CustomFieldTypeChoices.TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''},
|
||||||
)
|
)
|
||||||
|
|
||||||
obj_type = ContentType.objects.get_for_model(Site)
|
obj_type = ContentType.objects.get_for_model(Site)
|
||||||
@ -67,7 +67,7 @@ class CustomFieldTest(TestCase):
|
|||||||
obj_type = ContentType.objects.get_for_model(Site)
|
obj_type = ContentType.objects.get_for_model(Site)
|
||||||
|
|
||||||
# Create a custom field
|
# Create a custom field
|
||||||
cf = CustomField(type=CF_TYPE_SELECT, name='my_field', required=False)
|
cf = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='my_field', required=False)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.obj_type.set([obj_type])
|
cf.obj_type.set([obj_type])
|
||||||
cf.save()
|
cf.save()
|
||||||
@ -107,37 +107,37 @@ class CustomFieldAPITest(APITestCase):
|
|||||||
content_type = ContentType.objects.get_for_model(Site)
|
content_type = ContentType.objects.get_for_model(Site)
|
||||||
|
|
||||||
# Text custom field
|
# Text custom field
|
||||||
self.cf_text = CustomField(type=CF_TYPE_TEXT, name='magic_word')
|
self.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='magic_word')
|
||||||
self.cf_text.save()
|
self.cf_text.save()
|
||||||
self.cf_text.obj_type.set([content_type])
|
self.cf_text.obj_type.set([content_type])
|
||||||
self.cf_text.save()
|
self.cf_text.save()
|
||||||
|
|
||||||
# Integer custom field
|
# Integer custom field
|
||||||
self.cf_integer = CustomField(type=CF_TYPE_INTEGER, name='magic_number')
|
self.cf_integer = CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='magic_number')
|
||||||
self.cf_integer.save()
|
self.cf_integer.save()
|
||||||
self.cf_integer.obj_type.set([content_type])
|
self.cf_integer.obj_type.set([content_type])
|
||||||
self.cf_integer.save()
|
self.cf_integer.save()
|
||||||
|
|
||||||
# Boolean custom field
|
# Boolean custom field
|
||||||
self.cf_boolean = CustomField(type=CF_TYPE_BOOLEAN, name='is_magic')
|
self.cf_boolean = CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='is_magic')
|
||||||
self.cf_boolean.save()
|
self.cf_boolean.save()
|
||||||
self.cf_boolean.obj_type.set([content_type])
|
self.cf_boolean.obj_type.set([content_type])
|
||||||
self.cf_boolean.save()
|
self.cf_boolean.save()
|
||||||
|
|
||||||
# Date custom field
|
# Date custom field
|
||||||
self.cf_date = CustomField(type=CF_TYPE_DATE, name='magic_date')
|
self.cf_date = CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='magic_date')
|
||||||
self.cf_date.save()
|
self.cf_date.save()
|
||||||
self.cf_date.obj_type.set([content_type])
|
self.cf_date.obj_type.set([content_type])
|
||||||
self.cf_date.save()
|
self.cf_date.save()
|
||||||
|
|
||||||
# URL custom field
|
# URL custom field
|
||||||
self.cf_url = CustomField(type=CF_TYPE_URL, name='magic_url')
|
self.cf_url = CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='magic_url')
|
||||||
self.cf_url.save()
|
self.cf_url.save()
|
||||||
self.cf_url.obj_type.set([content_type])
|
self.cf_url.obj_type.set([content_type])
|
||||||
self.cf_url.save()
|
self.cf_url.save()
|
||||||
|
|
||||||
# Select custom field
|
# Select custom field
|
||||||
self.cf_select = CustomField(type=CF_TYPE_SELECT, name='magic_choice')
|
self.cf_select = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='magic_choice')
|
||||||
self.cf_select.save()
|
self.cf_select.save()
|
||||||
self.cf_select.obj_type.set([content_type])
|
self.cf_select.obj_type.set([content_type])
|
||||||
self.cf_select.save()
|
self.cf_select.save()
|
||||||
@ -308,8 +308,8 @@ class CustomFieldChoiceAPITest(APITestCase):
|
|||||||
|
|
||||||
vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
|
vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
|
||||||
|
|
||||||
self.cf_1 = CustomField.objects.create(name="cf_1", type=CF_TYPE_SELECT)
|
self.cf_1 = CustomField.objects.create(name="cf_1", type=CustomFieldTypeChoices.TYPE_SELECT)
|
||||||
self.cf_2 = CustomField.objects.create(name="cf_2", type=CF_TYPE_SELECT)
|
self.cf_2 = CustomField.objects.create(name="cf_2", type=CustomFieldTypeChoices.TYPE_SELECT)
|
||||||
|
|
||||||
self.cf_choice_1 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_1", weight=100)
|
self.cf_choice_1 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_1", weight=100)
|
||||||
self.cf_choice_2 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_2", weight=50)
|
self.cf_choice_2 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_2", weight=50)
|
||||||
|
@ -6,7 +6,7 @@ from django.test import Client, TestCase
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.constants import OBJECTCHANGE_ACTION_UPDATE
|
from extras.choices import ObjectChangeActionChoices
|
||||||
from extras.models import ConfigContext, ObjectChange, Tag
|
from extras.models import ConfigContext, ObjectChange, Tag
|
||||||
from utilities.testing import create_test_user
|
from utilities.testing import create_test_user
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class ObjectChangeTestCase(TestCase):
|
|||||||
|
|
||||||
# Create three ObjectChanges
|
# Create three ObjectChanges
|
||||||
for i in range(1, 4):
|
for i in range(1, 4):
|
||||||
oc = site.to_objectchange(action=OBJECTCHANGE_ACTION_UPDATE)
|
oc = site.to_objectchange(action=ObjectChangeActionChoices.ACTION_UPDATE)
|
||||||
oc.user = user
|
oc.user = user
|
||||||
oc.request_id = uuid.uuid4()
|
oc.request_id = uuid.uuid4()
|
||||||
oc.save()
|
oc.save()
|
||||||
|
@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
|
|
||||||
from extras.models import Webhook
|
from extras.models import Webhook
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
|
||||||
|
|
||||||
@ -18,9 +19,9 @@ def enqueue_webhooks(instance, user, request_id, action):
|
|||||||
|
|
||||||
# Retrieve any applicable Webhooks
|
# Retrieve any applicable Webhooks
|
||||||
action_flag = {
|
action_flag = {
|
||||||
OBJECTCHANGE_ACTION_CREATE: 'type_create',
|
ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
|
||||||
OBJECTCHANGE_ACTION_UPDATE: 'type_update',
|
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
|
||||||
OBJECTCHANGE_ACTION_DELETE: 'type_delete',
|
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
|
||||||
}[action]
|
}[action]
|
||||||
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
||||||
webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True})
|
webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True})
|
||||||
|
@ -6,6 +6,7 @@ import requests
|
|||||||
from django_rq import job
|
from django_rq import job
|
||||||
from rest_framework.utils.encoders import JSONEncoder
|
from rest_framework.utils.encoders import JSONEncoder
|
||||||
|
|
||||||
|
from .choices import ObjectChangeActionChoices
|
||||||
from .constants import *
|
from .constants import *
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque
|
|||||||
Make a POST request to the defined Webhook
|
Make a POST request to the defined Webhook
|
||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
'event': dict(OBJECTCHANGE_ACTION_CHOICES)[event].lower(),
|
'event': dict(ObjectChangeActionChoices)[event].lower(),
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'model': model_name,
|
'model': model_name,
|
||||||
'username': username,
|
'username': username,
|
||||||
|
@ -8,8 +8,8 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
|
|||||||
from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.constants import *
|
from ipam.choices import *
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from ipam.models import AF_CHOICES, Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from utilities.api import (
|
from utilities.api import (
|
||||||
ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer,
|
ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer,
|
||||||
@ -102,7 +102,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
site = NestedSiteSerializer(required=False, allow_null=True)
|
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||||
group = NestedVLANGroupSerializer(required=False, allow_null=True)
|
group = NestedVLANGroupSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=VLAN_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=VLANStatusChoices, required=False)
|
||||||
role = NestedRoleSerializer(required=False, allow_null=True)
|
role = NestedRoleSerializer(required=False, allow_null=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
prefix_count = serializers.IntegerField(read_only=True)
|
prefix_count = serializers.IntegerField(read_only=True)
|
||||||
@ -140,7 +140,7 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
vlan = NestedVLANSerializer(required=False, allow_null=True)
|
vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=PREFIX_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=PrefixStatusChoices, required=False)
|
||||||
role = NestedRoleSerializer(required=False, allow_null=True)
|
role = NestedRoleSerializer(required=False, allow_null=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
@ -200,8 +200,8 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=IPAddressStatusChoices, required=False)
|
||||||
role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False, allow_null=True)
|
role = ChoiceField(choices=IPAddressRoleChoices, required=False, allow_null=True)
|
||||||
interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
|
interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
|
||||||
nat_inside = NestedIPAddressSerializer(required=False, allow_null=True)
|
nat_inside = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
nat_outside = NestedIPAddressSerializer(read_only=True)
|
nat_outside = NestedIPAddressSerializer(read_only=True)
|
||||||
@ -239,7 +239,7 @@ class AvailableIPSerializer(serializers.Serializer):
|
|||||||
class ServiceSerializer(CustomFieldModelSerializer):
|
class ServiceSerializer(CustomFieldModelSerializer):
|
||||||
device = NestedDeviceSerializer(required=False, allow_null=True)
|
device = NestedDeviceSerializer(required=False, allow_null=True)
|
||||||
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
|
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
|
||||||
protocol = ChoiceField(choices=IP_PROTOCOL_CHOICES)
|
protocol = ChoiceField(choices=ServiceProtocolChoices)
|
||||||
ipaddresses = SerializedPKRelatedField(
|
ipaddresses = SerializedPKRelatedField(
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
serializer=NestedIPAddressSerializer,
|
serializer=NestedIPAddressSerializer,
|
||||||
|
130
netbox/ipam/choices.py
Normal file
130
netbox/ipam/choices.py
Normal 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,
|
||||||
|
}
|
@ -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'),
|
|
||||||
)
|
|
@ -9,7 +9,7 @@ from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
|
|||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=PREFIX_STATUS_CHOICES,
|
choices=PrefixStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
@ -310,11 +310,11 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
|||||||
label='Interface (ID)',
|
label='Interface (ID)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
choices=IPAddressStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
role = django_filters.MultipleChoiceFilter(
|
role = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_ROLE_CHOICES
|
choices=IPAddressRoleChoices
|
||||||
)
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
@ -424,7 +424,7 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
|||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
choices=VLANStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"vrf": null,
|
"vrf": null,
|
||||||
"vlan": null,
|
"vlan": null,
|
||||||
"status": 1,
|
"status": "active",
|
||||||
"role": 1,
|
"role": 1,
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"vrf": null,
|
"vrf": null,
|
||||||
"vlan": null,
|
"vlan": null,
|
||||||
"status": 1,
|
"status": "active",
|
||||||
"role": 1,
|
"role": 1,
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
@ -322,7 +322,7 @@
|
|||||||
"site": 1,
|
"site": 1,
|
||||||
"vid": 999,
|
"vid": 999,
|
||||||
"name": "TEST",
|
"name": "TEST",
|
||||||
"status": 1,
|
"status": "active",
|
||||||
"role": 1
|
"role": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ from utilities.forms import (
|
|||||||
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
IP_FAMILY_CHOICES = [
|
IP_FAMILY_CHOICES = [
|
||||||
@ -374,7 +374,7 @@ class PrefixCSVForm(forms.ModelForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=PREFIX_STATUS_CHOICES,
|
choices=PrefixStatusChoices,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
@ -459,7 +459,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PREFIX_STATUS_CHOICES),
|
choices=add_blank_choice(PrefixStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -527,7 +527,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=PREFIX_STATUS_CHOICES,
|
choices=PrefixStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -764,11 +764,11 @@ class IPAddressCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
choices=IPAddressStatusChoices,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
role = CSVChoiceField(
|
role = CSVChoiceField(
|
||||||
choices=IPADDRESS_ROLE_CHOICES,
|
choices=IPAddressRoleChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Functional role'
|
help_text='Functional role'
|
||||||
)
|
)
|
||||||
@ -893,12 +893,12 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
|
choices=add_blank_choice(IPAddressStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
role = forms.ChoiceField(
|
role = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
|
choices=add_blank_choice(IPAddressRoleChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -972,12 +972,12 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
choices=IPAddressStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
role = forms.MultipleChoiceField(
|
role = forms.MultipleChoiceField(
|
||||||
choices=IPADDRESS_ROLE_CHOICES,
|
choices=IPAddressRoleChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -1111,7 +1111,7 @@ class VLANCSVForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
choices=VLANStatusChoices,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
@ -1180,7 +1180,7 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(VLAN_STATUS_CHOICES),
|
choices=add_blank_choice(VLANStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -1229,7 +1229,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
choices=VLANStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -1292,7 +1292,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
label='Search'
|
label='Search'
|
||||||
)
|
)
|
||||||
protocol = forms.ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
choices=add_blank_choice(ServiceProtocolChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -1307,7 +1307,7 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
protocol = forms.ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
|
choices=add_blank_choice(ServiceProtocolChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
|
37
netbox/ipam/migrations/0028_3569_prefix_fields.py
Normal file
37
netbox/ipam/migrations/0028_3569_prefix_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
69
netbox/ipam/migrations/0029_3569_ipaddress_fields.py
Normal file
69
netbox/ipam/migrations/0029_3569_ipaddress_fields.py
Normal 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),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
36
netbox/ipam/migrations/0030_3569_vlan_fields.py
Normal file
36
netbox/ipam/migrations/0030_3569_vlan_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
35
netbox/ipam/migrations/0031_3569_service_fields.py
Normal file
35
netbox/ipam/migrations/0031_3569_service_fields.py
Normal 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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -14,12 +14,29 @@ from extras.models import CustomFieldModel, ObjectChange, TaggedItem
|
|||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .fields import IPNetworkField, IPAddressField
|
from .fields import IPNetworkField, IPAddressField
|
||||||
from .querysets import PrefixQuerySet
|
from .querysets import PrefixQuerySet
|
||||||
from .validators import DNSValidator
|
from .validators import DNSValidator
|
||||||
|
|
||||||
|
|
||||||
|
# IP address families
|
||||||
|
AF_CHOICES = (
|
||||||
|
(4, 'IPv4'),
|
||||||
|
(6, 'IPv6'),
|
||||||
|
)
|
||||||
|
|
||||||
|
IPADDRESS_ROLES_NONUNIQUE = (
|
||||||
|
# IPAddress roles which are exempt from unique address enforcement
|
||||||
|
IPAddressRoleChoices.ROLE_ANYCAST,
|
||||||
|
IPAddressRoleChoices.ROLE_VIP,
|
||||||
|
IPAddressRoleChoices.ROLE_VRRP,
|
||||||
|
IPAddressRoleChoices.ROLE_HSRP,
|
||||||
|
IPAddressRoleChoices.ROLE_GLBP,
|
||||||
|
IPAddressRoleChoices.ROLE_CARP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VRF(ChangeLoggedModel, CustomFieldModel):
|
class VRF(ChangeLoggedModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
||||||
@ -297,9 +314,10 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
null=True,
|
null=True,
|
||||||
verbose_name='VLAN'
|
verbose_name='VLAN'
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=PREFIX_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=PREFIX_STATUS_ACTIVE,
|
choices=PrefixStatusChoices,
|
||||||
|
default=PrefixStatusChoices.STATUS_ACTIVE,
|
||||||
verbose_name='Status',
|
verbose_name='Status',
|
||||||
help_text='Operational status of this prefix'
|
help_text='Operational status of this prefix'
|
||||||
)
|
)
|
||||||
@ -333,6 +351,13 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
|
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
'container': 'default',
|
||||||
|
'active': 'primary',
|
||||||
|
'reserved': 'info',
|
||||||
|
'deprecated': 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix']
|
ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix']
|
||||||
verbose_name_plural = 'prefixes'
|
verbose_name_plural = 'prefixes'
|
||||||
@ -404,7 +429,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
prefix_length = property(fset=_set_prefix_length)
|
prefix_length = property(fset=_set_prefix_length)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CHOICE_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
def get_duplicates(self):
|
def get_duplicates(self):
|
||||||
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
||||||
@ -414,7 +439,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
|
Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
|
||||||
Prefixes belonging to any VRF.
|
Prefixes belonging to any VRF.
|
||||||
"""
|
"""
|
||||||
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
|
if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
|
||||||
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
|
return Prefix.objects.filter(prefix__net_contained=str(self.prefix))
|
||||||
else:
|
else:
|
||||||
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||||
@ -424,7 +449,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
|
Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
|
||||||
child IPAddresses belonging to any VRF.
|
child IPAddresses belonging to any VRF.
|
||||||
"""
|
"""
|
||||||
if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
|
if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
|
||||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
|
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
|
||||||
else:
|
else:
|
||||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||||
@ -490,7 +515,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
|
Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
|
||||||
"container", calculate utilization based on child prefixes. For all others, count child IP addresses.
|
"container", calculate utilization based on child prefixes. For all others, count child IP addresses.
|
||||||
"""
|
"""
|
||||||
if self.status == PREFIX_STATUS_CONTAINER:
|
if self.status == PrefixStatusChoices.STATUS_CONTAINER:
|
||||||
queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||||
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
|
child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
|
||||||
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
||||||
@ -550,17 +575,16 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=IPADDRESS_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=IPADDRESS_STATUS_ACTIVE,
|
choices=IPAddressStatusChoices,
|
||||||
verbose_name='Status',
|
default=IPAddressStatusChoices.STATUS_ACTIVE,
|
||||||
help_text='The operational status of this IP'
|
help_text='The operational status of this IP'
|
||||||
)
|
)
|
||||||
role = models.PositiveSmallIntegerField(
|
role = models.CharField(
|
||||||
verbose_name='Role',
|
max_length=50,
|
||||||
choices=IPADDRESS_ROLE_CHOICES,
|
choices=IPAddressRoleChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
|
||||||
help_text='The functional role of this IP'
|
help_text='The functional role of this IP'
|
||||||
)
|
)
|
||||||
interface = models.ForeignKey(
|
interface = models.ForeignKey(
|
||||||
@ -604,6 +628,24 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
'dns_name', 'description',
|
'dns_name', 'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
'active': 'primary',
|
||||||
|
'reserved': 'info',
|
||||||
|
'deprecated': 'danger',
|
||||||
|
'dhcp': 'success',
|
||||||
|
}
|
||||||
|
|
||||||
|
ROLE_CLASS_MAP = {
|
||||||
|
'loopback': 'default',
|
||||||
|
'secondary': 'primary',
|
||||||
|
'anycast': 'warning',
|
||||||
|
'vip': 'success',
|
||||||
|
'vrrp': 'success',
|
||||||
|
'hsrp': 'success',
|
||||||
|
'glbp': 'success',
|
||||||
|
'carp': 'success',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['family', 'address']
|
ordering = ['family', 'address']
|
||||||
verbose_name = 'IP address'
|
verbose_name = 'IP address'
|
||||||
@ -737,10 +779,10 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CHOICE_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
def get_role_class(self):
|
def get_role_class(self):
|
||||||
return ROLE_CHOICE_CLASSES[self.role]
|
return self.ROLE_CLASS_MAP[self.role]
|
||||||
|
|
||||||
|
|
||||||
class VLANGroup(ChangeLoggedModel):
|
class VLANGroup(ChangeLoggedModel):
|
||||||
@ -831,10 +873,10 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=VLAN_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=1,
|
choices=VLANStatusChoices,
|
||||||
verbose_name='Status'
|
default=VLANStatusChoices.STATUS_ACTIVE
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
to='ipam.Role',
|
to='ipam.Role',
|
||||||
@ -857,6 +899,12 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
'active': 'primary',
|
||||||
|
'reserved': 'info',
|
||||||
|
'deprecated': 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['site', 'group', 'vid']
|
ordering = ['site', 'group', 'vid']
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@ -899,7 +947,7 @@ class VLAN(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return STATUS_CHOICE_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP[self.status]
|
||||||
|
|
||||||
def get_members(self):
|
def get_members(self):
|
||||||
# Return all interfaces assigned to this VLAN
|
# Return all interfaces assigned to this VLAN
|
||||||
@ -932,8 +980,9 @@ class Service(ChangeLoggedModel, CustomFieldModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=30
|
max_length=30
|
||||||
)
|
)
|
||||||
protocol = models.PositiveSmallIntegerField(
|
protocol = models.CharField(
|
||||||
choices=IP_PROTOCOL_CHOICES
|
max_length=50,
|
||||||
|
choices=ServiceProtocolChoices
|
||||||
)
|
)
|
||||||
port = models.PositiveIntegerField(
|
port = models.PositiveIntegerField(
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(65535)],
|
validators=[MinValueValidator(1), MaxValueValidator(65535)],
|
||||||
|
@ -5,7 +5,7 @@ from netaddr import IPNetwork
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from ipam.constants import IP_PROTOCOL_TCP, IP_PROTOCOL_UDP
|
from ipam.choices import ServiceProtocolChoices
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
from utilities.testing import APITestCase
|
from utilities.testing import APITestCase
|
||||||
|
|
||||||
@ -996,13 +996,13 @@ class ServiceTest(APITestCase):
|
|||||||
name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
|
name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
|
||||||
)
|
)
|
||||||
self.service1 = Service.objects.create(
|
self.service1 = Service.objects.create(
|
||||||
device=self.device1, name='Test Service 1', protocol=IP_PROTOCOL_TCP, port=1
|
device=self.device1, name='Test Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1
|
||||||
)
|
)
|
||||||
self.service1 = Service.objects.create(
|
self.service1 = Service.objects.create(
|
||||||
device=self.device1, name='Test Service 2', protocol=IP_PROTOCOL_TCP, port=2
|
device=self.device1, name='Test Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2
|
||||||
)
|
)
|
||||||
self.service1 = Service.objects.create(
|
self.service1 = Service.objects.create(
|
||||||
device=self.device1, name='Test Service 3', protocol=IP_PROTOCOL_TCP, port=3
|
device=self.device1, name='Test Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_service(self):
|
def test_get_service(self):
|
||||||
@ -1024,7 +1024,7 @@ class ServiceTest(APITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'device': self.device1.pk,
|
'device': self.device1.pk,
|
||||||
'name': 'Test Service 4',
|
'name': 'Test Service 4',
|
||||||
'protocol': IP_PROTOCOL_TCP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||||
'port': 4,
|
'port': 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1045,19 +1045,19 @@ class ServiceTest(APITestCase):
|
|||||||
{
|
{
|
||||||
'device': self.device1.pk,
|
'device': self.device1.pk,
|
||||||
'name': 'Test Service 4',
|
'name': 'Test Service 4',
|
||||||
'protocol': IP_PROTOCOL_TCP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||||
'port': 4,
|
'port': 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'device': self.device1.pk,
|
'device': self.device1.pk,
|
||||||
'name': 'Test Service 5',
|
'name': 'Test Service 5',
|
||||||
'protocol': IP_PROTOCOL_TCP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||||
'port': 5,
|
'port': 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'device': self.device1.pk,
|
'device': self.device1.pk,
|
||||||
'name': 'Test Service 6',
|
'name': 'Test Service 6',
|
||||||
'protocol': IP_PROTOCOL_TCP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||||
'port': 6,
|
'port': 6,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -1076,7 +1076,7 @@ class ServiceTest(APITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'device': self.device2.pk,
|
'device': self.device2.pk,
|
||||||
'name': 'Test Service X',
|
'name': 'Test Service X',
|
||||||
'protocol': IP_PROTOCOL_UDP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_UDP,
|
||||||
'port': 99,
|
'port': 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import netaddr
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from ipam.constants import IPADDRESS_ROLE_VIP
|
from ipam.choices import IPAddressRoleChoices
|
||||||
from ipam.models import IPAddress, Prefix, VRF
|
from ipam.models import IPAddress, Prefix, VRF
|
||||||
|
|
||||||
|
|
||||||
@ -61,5 +61,5 @@ class TestIPAddress(TestCase):
|
|||||||
|
|
||||||
@override_settings(ENFORCE_GLOBAL_UNIQUE=True)
|
@override_settings(ENFORCE_GLOBAL_UNIQUE=True)
|
||||||
def test_duplicate_nonunique_role(self):
|
def test_duplicate_nonunique_role(self):
|
||||||
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP)
|
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
|
||||||
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP)
|
IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
|
||||||
|
@ -5,7 +5,7 @@ from django.test import Client, TestCase
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from ipam.constants import IP_PROTOCOL_TCP
|
from ipam.choices import ServiceProtocolChoices
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
from utilities.testing import create_test_user
|
from utilities.testing import create_test_user
|
||||||
|
|
||||||
@ -264,9 +264,9 @@ class ServiceTestCase(TestCase):
|
|||||||
device.save()
|
device.save()
|
||||||
|
|
||||||
Service.objects.bulk_create([
|
Service.objects.bulk_create([
|
||||||
Service(device=device, name='Service 1', protocol=IP_PROTOCOL_TCP, port=101),
|
Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=101),
|
||||||
Service(device=device, name='Service 2', protocol=IP_PROTOCOL_TCP, port=102),
|
Service(device=device, name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=102),
|
||||||
Service(device=device, name='Service 3', protocol=IP_PROTOCOL_TCP, port=103),
|
Service(device=device, name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=103),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_service_list(self):
|
def test_service_list(self):
|
||||||
|
@ -14,7 +14,7 @@ from utilities.views import (
|
|||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||||
|
|
||||||
|
|
||||||
@ -217,13 +217,13 @@ class RIRListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
|
|
||||||
# Find all consumed space for each prefix status (we ignore containers for this purpose).
|
# Find all consumed space for each prefix status (we ignore containers for this purpose).
|
||||||
active_prefixes = netaddr.cidr_merge(
|
active_prefixes = netaddr.cidr_merge(
|
||||||
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)]
|
[p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_ACTIVE)]
|
||||||
)
|
)
|
||||||
reserved_prefixes = netaddr.cidr_merge(
|
reserved_prefixes = netaddr.cidr_merge(
|
||||||
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)]
|
[p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_RESERVED)]
|
||||||
)
|
)
|
||||||
deprecated_prefixes = netaddr.cidr_merge(
|
deprecated_prefixes = netaddr.cidr_merge(
|
||||||
[p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)]
|
[p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_DEPRECATED)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
|
# Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
|
||||||
@ -665,8 +665,8 @@ class IPAddressView(PermissionRequiredMixin, View):
|
|||||||
'nat_inside', 'interface__device'
|
'nat_inside', 'interface__device'
|
||||||
)
|
)
|
||||||
# Exclude anycast IPs if this IP is anycast
|
# Exclude anycast IPs if this IP is anycast
|
||||||
if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
|
if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
|
||||||
duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST)
|
duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
|
||||||
duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
|
duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
|
||||||
|
|
||||||
# Related IP table
|
# Related IP table
|
||||||
|
@ -13,6 +13,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
||||||
from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet
|
from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet
|
||||||
|
|
||||||
|
from utilities.choices import ChoiceSet
|
||||||
from .utils import dict_to_filter_params, dynamic_import
|
from .utils import dict_to_filter_params, dynamic_import
|
||||||
|
|
||||||
|
|
||||||
@ -64,14 +65,17 @@ class ChoiceField(Field):
|
|||||||
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.
|
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}.
|
||||||
"""
|
"""
|
||||||
def __init__(self, choices, **kwargs):
|
def __init__(self, choices, **kwargs):
|
||||||
|
self.choiceset = choices
|
||||||
self._choices = dict()
|
self._choices = dict()
|
||||||
|
|
||||||
|
# Unpack grouped choices
|
||||||
for k, v in choices:
|
for k, v in choices:
|
||||||
# Unpack grouped choices
|
|
||||||
if type(v) in [list, tuple]:
|
if type(v) in [list, tuple]:
|
||||||
for k2, v2 in v:
|
for k2, v2 in v:
|
||||||
self._choices[k2] = v2
|
self._choices[k2] = v2
|
||||||
else:
|
else:
|
||||||
self._choices[k] = v
|
self._choices[k] = v
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
@ -81,6 +85,11 @@ class ChoiceField(Field):
|
|||||||
('value', obj),
|
('value', obj),
|
||||||
('label', self._choices[obj])
|
('label', self._choices[obj])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# Include legacy numeric ID (where applicable)
|
||||||
|
if type(self.choiceset) is ChoiceSet and obj in self.choiceset.LEGACY_MAP:
|
||||||
|
data['id'] = self.choiceset.LEGACY_MAP.get(obj)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
@ -104,6 +113,10 @@ class ChoiceField(Field):
|
|||||||
try:
|
try:
|
||||||
if data in self._choices:
|
if data in self._choices:
|
||||||
return data
|
return data
|
||||||
|
# Check if data is a legacy numeric ID
|
||||||
|
slug = self.choiceset.id_to_slug(data)
|
||||||
|
if slug is not None:
|
||||||
|
return slug
|
||||||
except TypeError: # Input is an unhashable type
|
except TypeError: # Input is an unhashable type
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
36
netbox/utilities/choices.py
Normal file
36
netbox/utilities/choices.py
Normal 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)
|
@ -5,7 +5,7 @@ from collections import OrderedDict
|
|||||||
from django.core.serializers import serialize
|
from django.core.serializers import serialize
|
||||||
from django.db.models import Count, OuterRef, Subquery
|
from django.db.models import Count, OuterRef, Subquery
|
||||||
|
|
||||||
from dcim.constants import LENGTH_UNIT_CENTIMETER, LENGTH_UNIT_FOOT, LENGTH_UNIT_INCH, LENGTH_UNIT_METER
|
from dcim.choices import CableLengthUnitChoices
|
||||||
|
|
||||||
|
|
||||||
def csv_format(data):
|
def csv_format(data):
|
||||||
@ -165,12 +165,18 @@ def to_meters(length, unit):
|
|||||||
length = int(length)
|
length = int(length)
|
||||||
if length < 0:
|
if length < 0:
|
||||||
raise ValueError("Length must be a positive integer")
|
raise ValueError("Length must be a positive integer")
|
||||||
if unit == LENGTH_UNIT_METER:
|
|
||||||
|
valid_units = [u[0] for u in CableLengthUnitChoices]
|
||||||
|
if unit not in valid_units:
|
||||||
|
raise ValueError(
|
||||||
|
"Unknown unit {}. Must be one of the following: {}".format(unit, ', '.join(valid_units))
|
||||||
|
)
|
||||||
|
|
||||||
|
if unit == CableLengthUnitChoices.UNIT_METER:
|
||||||
return length
|
return length
|
||||||
if unit == LENGTH_UNIT_CENTIMETER:
|
if unit == CableLengthUnitChoices.UNIT_CENTIMETER:
|
||||||
return length / 100
|
return length / 100
|
||||||
if unit == LENGTH_UNIT_FOOT:
|
if unit == CableLengthUnitChoices.UNIT_FOOT:
|
||||||
return length * 0.3048
|
return length * 0.3048
|
||||||
if unit == LENGTH_UNIT_INCH:
|
if unit == CableLengthUnitChoices.UNIT_INCH:
|
||||||
return length * 0.3048 * 12
|
return length * 0.3048 * 12
|
||||||
raise ValueError("Unknown unit {}. Must be 'm', 'cm', 'ft', or 'in'.".format(unit))
|
|
||||||
|
@ -3,14 +3,14 @@ from rest_framework import serializers
|
|||||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||||
|
|
||||||
from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
|
||||||
from dcim.constants import IFACE_TYPE_CHOICES, IFACE_TYPE_VIRTUAL, IFACE_MODE_CHOICES
|
from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
|
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer
|
from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer
|
||||||
from virtualization.constants import VM_STATUS_CHOICES
|
from virtualization.choices import *
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class ClusterSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
status = ChoiceField(choices=VM_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
|
||||||
site = NestedSiteSerializer(read_only=True)
|
site = NestedSiteSerializer(read_only=True)
|
||||||
cluster = NestedClusterSerializer()
|
cluster = NestedClusterSerializer()
|
||||||
role = NestedDeviceRoleSerializer(required=False, allow_null=True)
|
role = NestedDeviceRoleSerializer(required=False, allow_null=True)
|
||||||
@ -98,8 +98,8 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
|||||||
|
|
||||||
class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
virtual_machine = NestedVirtualMachineSerializer()
|
virtual_machine = NestedVirtualMachineSerializer()
|
||||||
type = ChoiceField(choices=IFACE_TYPE_CHOICES, default=IFACE_TYPE_VIRTUAL, required=False)
|
type = ChoiceField(choices=InterfaceTypeChoices, default=InterfaceTypeChoices.TYPE_VIRTUAL, required=False)
|
||||||
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_null=True)
|
||||||
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||||
tagged_vlans = SerializedPKRelatedField(
|
tagged_vlans = SerializedPKRelatedField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
|
24
netbox/virtualization/choices.py
Normal file
24
netbox/virtualization/choices.py
Normal 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,
|
||||||
|
}
|
@ -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',
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ from tenancy.models import Tenant
|
|||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdate
|
|||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=VM_STATUS_CHOICES,
|
choices=VirtualMachineStatusChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
|
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
@ -2,7 +2,7 @@ from django import forms
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
|
|
||||||
from dcim.constants import IFACE_TYPE_VIRTUAL, IFACE_MODE_ACCESS, IFACE_MODE_TAGGED_ALL, IFACE_MODE_CHOICES
|
from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
|
||||||
from dcim.forms import INTERFACE_MODE_HELP_TEXT
|
from dcim.forms import INTERFACE_MODE_HELP_TEXT
|
||||||
from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
|
from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
|
||||||
@ -15,11 +15,11 @@ from utilities.forms import (
|
|||||||
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
|
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
|
||||||
SmallTextarea, StaticSelect2, StaticSelect2Multiple
|
SmallTextarea, StaticSelect2, StaticSelect2Multiple
|
||||||
)
|
)
|
||||||
from .constants import *
|
from .choices import *
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
VIFACE_TYPE_CHOICES = (
|
VIFACE_TYPE_CHOICES = (
|
||||||
(IFACE_TYPE_VIRTUAL, 'Virtual'),
|
(InterfaceTypeChoices.TYPE_VIRTUAL, 'Virtual'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -428,7 +428,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
class VirtualMachineCSVForm(forms.ModelForm):
|
class VirtualMachineCSVForm(forms.ModelForm):
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=VM_STATUS_CHOICES,
|
choices=VirtualMachineStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status of device'
|
help_text='Operational status of device'
|
||||||
)
|
)
|
||||||
@ -481,7 +481,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
|
|||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
choices=add_blank_choice(VM_STATUS_CHOICES),
|
choices=add_blank_choice(VirtualMachineStatusChoices),
|
||||||
required=False,
|
required=False,
|
||||||
initial='',
|
initial='',
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
@ -612,7 +612,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
choices=VM_STATUS_CHOICES,
|
choices=VirtualMachineStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2Multiple()
|
widget=StaticSelect2Multiple()
|
||||||
)
|
)
|
||||||
@ -717,13 +717,13 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
|
|||||||
tagged_vlans = self.cleaned_data['tagged_vlans']
|
tagged_vlans = self.cleaned_data['tagged_vlans']
|
||||||
|
|
||||||
# Untagged interfaces cannot be assigned tagged VLANs
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
if self.cleaned_data['mode'] == IFACE_MODE_ACCESS and tagged_vlans:
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'mode': "An access interface cannot have tagged VLANs assigned."
|
'mode': "An access interface cannot have tagged VLANs assigned."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
||||||
elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED_ALL:
|
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
|
||||||
self.cleaned_data['tagged_vlans'] = []
|
self.cleaned_data['tagged_vlans'] = []
|
||||||
|
|
||||||
|
|
||||||
@ -733,7 +733,7 @@ class InterfaceCreateForm(ComponentForm):
|
|||||||
)
|
)
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=VIFACE_TYPE_CHOICES,
|
choices=VIFACE_TYPE_CHOICES,
|
||||||
initial=IFACE_TYPE_VIRTUAL,
|
initial=InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||||
widget=forms.HiddenInput()
|
widget=forms.HiddenInput()
|
||||||
)
|
)
|
||||||
enabled = forms.BooleanField(
|
enabled = forms.BooleanField(
|
||||||
@ -754,7 +754,7 @@ class InterfaceCreateForm(ComponentForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
mode = forms.ChoiceField(
|
mode = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_MODE_CHOICES),
|
choices=add_blank_choice(InterfaceModeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
)
|
)
|
||||||
@ -839,7 +839,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
mode = forms.ChoiceField(
|
mode = forms.ChoiceField(
|
||||||
choices=add_blank_choice(IFACE_MODE_CHOICES),
|
choices=add_blank_choice(InterfaceModeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
@ -918,7 +918,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
|
|||||||
class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
|
class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm):
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=VIFACE_TYPE_CHOICES,
|
choices=VIFACE_TYPE_CHOICES,
|
||||||
initial=IFACE_TYPE_VIRTUAL,
|
initial=InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||||
widget=forms.HiddenInput()
|
widget=forms.HiddenInput()
|
||||||
)
|
)
|
||||||
enabled = forms.BooleanField(
|
enabled = forms.BooleanField(
|
||||||
|
@ -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
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -8,7 +8,7 @@ from taggit.managers import TaggableManager
|
|||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem
|
from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
from .constants import *
|
from .choices import *
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -193,9 +193,10 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
max_length=64,
|
max_length=64,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.CharField(
|
||||||
choices=VM_STATUS_CHOICES,
|
max_length=50,
|
||||||
default=DEVICE_STATUS_ACTIVE,
|
choices=VirtualMachineStatusChoices,
|
||||||
|
default=VirtualMachineStatusChoices.STATUS_ACTIVE,
|
||||||
verbose_name='Status'
|
verbose_name='Status'
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
@ -252,6 +253,12 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
|
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATUS_CLASS_MAP = {
|
||||||
|
'active': 'success',
|
||||||
|
'offline': 'warning',
|
||||||
|
'staged': 'primary',
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
@ -294,7 +301,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return VM_STATUS_CLASSES[self.status]
|
return self.STATUS_CLASS_MAP.get(self.status)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def primary_ip(self):
|
def primary_ip(self):
|
||||||
|
@ -2,7 +2,7 @@ from django.urls import reverse
|
|||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.constants import IFACE_TYPE_VIRTUAL, IFACE_MODE_TAGGED
|
from dcim.choices import InterfaceModeChoices, InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.models import IPAddress, VLAN
|
from ipam.models import IPAddress, VLAN
|
||||||
from utilities.testing import APITestCase
|
from utilities.testing import APITestCase
|
||||||
@ -489,17 +489,17 @@ class InterfaceTest(APITestCase):
|
|||||||
self.interface1 = Interface.objects.create(
|
self.interface1 = Interface.objects.create(
|
||||||
virtual_machine=self.virtualmachine,
|
virtual_machine=self.virtualmachine,
|
||||||
name='Test Interface 1',
|
name='Test Interface 1',
|
||||||
type=IFACE_TYPE_VIRTUAL
|
type=InterfaceTypeChoices.TYPE_VIRTUAL
|
||||||
)
|
)
|
||||||
self.interface2 = Interface.objects.create(
|
self.interface2 = Interface.objects.create(
|
||||||
virtual_machine=self.virtualmachine,
|
virtual_machine=self.virtualmachine,
|
||||||
name='Test Interface 2',
|
name='Test Interface 2',
|
||||||
type=IFACE_TYPE_VIRTUAL
|
type=InterfaceTypeChoices.TYPE_VIRTUAL
|
||||||
)
|
)
|
||||||
self.interface3 = Interface.objects.create(
|
self.interface3 = Interface.objects.create(
|
||||||
virtual_machine=self.virtualmachine,
|
virtual_machine=self.virtualmachine,
|
||||||
name='Test Interface 3',
|
name='Test Interface 3',
|
||||||
type=IFACE_TYPE_VIRTUAL
|
type=InterfaceTypeChoices.TYPE_VIRTUAL
|
||||||
)
|
)
|
||||||
|
|
||||||
self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1)
|
self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1)
|
||||||
@ -551,7 +551,7 @@ class InterfaceTest(APITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'virtual_machine': self.virtualmachine.pk,
|
'virtual_machine': self.virtualmachine.pk,
|
||||||
'name': 'Test Interface 4',
|
'name': 'Test Interface 4',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan3.id,
|
'untagged_vlan': self.vlan3.id,
|
||||||
'tagged_vlans': [self.vlan1.id, self.vlan2.id],
|
'tagged_vlans': [self.vlan1.id, self.vlan2.id],
|
||||||
}
|
}
|
||||||
@ -598,21 +598,21 @@ class InterfaceTest(APITestCase):
|
|||||||
{
|
{
|
||||||
'virtual_machine': self.virtualmachine.pk,
|
'virtual_machine': self.virtualmachine.pk,
|
||||||
'name': 'Test Interface 4',
|
'name': 'Test Interface 4',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'virtual_machine': self.virtualmachine.pk,
|
'virtual_machine': self.virtualmachine.pk,
|
||||||
'name': 'Test Interface 5',
|
'name': 'Test Interface 5',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'virtual_machine': self.virtualmachine.pk,
|
'virtual_machine': self.virtualmachine.pk,
|
||||||
'name': 'Test Interface 6',
|
'name': 'Test Interface 6',
|
||||||
'mode': IFACE_MODE_TAGGED,
|
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||||
'untagged_vlan': self.vlan2.id,
|
'untagged_vlan': self.vlan2.id,
|
||||||
'tagged_vlans': [self.vlan1.id],
|
'tagged_vlans': [self.vlan1.id],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user