mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-13 16:47:34 -06:00
* Closes #18153: Introduce virtual circuit types * Fix TagTestCase * Fix GraphQL API test
This commit is contained in:
parent
89d7487197
commit
83d62315cc
@ -18,6 +18,10 @@ The [provider account](./provideraccount.md) with which the virtual circuit is a
|
||||
|
||||
The unique identifier assigned to the virtual circuit by its [provider](./provider.md).
|
||||
|
||||
### Type
|
||||
|
||||
The assigned [virtual circuit type](./virtualcircuittype.md).
|
||||
|
||||
### Status
|
||||
|
||||
The operational status of the virtual circuit. By default, the following statuses are available:
|
||||
|
13
docs/models/circuits/virtualcircuittype.md
Normal file
13
docs/models/circuits/virtualcircuittype.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Virtual Circuit Types
|
||||
|
||||
Like physical [circuits](./circuit.md), [virtual circuits](./virtualcircuit.md) are classified by functional type. These types are completely customizable, and can help categorize circuits by function or technology.
|
||||
|
||||
## Fields
|
||||
|
||||
### Name
|
||||
|
||||
A unique human-friendly name.
|
||||
|
||||
### Slug
|
||||
|
||||
A unique URL-friendly identifier. (This value can be used for filtering.)
|
@ -6,7 +6,7 @@ from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, Virtu
|
||||
from circuits.constants import CIRCUIT_GROUP_ASSIGNMENT_MEMBER_MODELS, CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||
from circuits.models import (
|
||||
Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType, VirtualCircuit,
|
||||
VirtualCircuitTermination,
|
||||
VirtualCircuitTermination, VirtualCircuitType,
|
||||
)
|
||||
from dcim.api.serializers_.device_components import InterfaceSerializer
|
||||
from dcim.api.serializers_.cables import CabledObjectSerializer
|
||||
@ -25,6 +25,7 @@ __all__ = (
|
||||
'CircuitTypeSerializer',
|
||||
'VirtualCircuitSerializer',
|
||||
'VirtualCircuitTerminationSerializer',
|
||||
'VirtualCircuitTypeSerializer',
|
||||
)
|
||||
|
||||
|
||||
@ -175,17 +176,32 @@ class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
|
||||
return serializer(obj.member, nested=True, context=context).data
|
||||
|
||||
|
||||
class VirtualCircuitTypeSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
virtual_circuit_count = RelatedObjectCountField('virtual_circuits')
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'virtual_circuit_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'virtual_circuit_count')
|
||||
|
||||
|
||||
class VirtualCircuitSerializer(NetBoxModelSerializer):
|
||||
provider_network = ProviderNetworkSerializer(nested=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
type = VirtualCircuitTypeSerializer(nested=True)
|
||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'status', 'tenant',
|
||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'type', 'status',
|
||||
'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
|
||||
|
||||
|
@ -19,6 +19,7 @@ router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet
|
||||
|
||||
# Virtual circuits
|
||||
router.register('virtual-circuits', views.VirtualCircuitViewSet)
|
||||
router.register('virtual-circuit-types', views.VirtualCircuitTypeViewSet)
|
||||
router.register('virtual-circuit-terminations', views.VirtualCircuitTerminationViewSet)
|
||||
|
||||
app_name = 'circuits-api'
|
||||
|
@ -95,6 +95,16 @@ class ProviderNetworkViewSet(NetBoxModelViewSet):
|
||||
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuit types
|
||||
#
|
||||
|
||||
class VirtualCircuitTypeViewSet(NetBoxModelViewSet):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
serializer_class = serializers.VirtualCircuitTypeSerializer
|
||||
filterset_class = filtersets.VirtualCircuitTypeFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuits
|
||||
#
|
||||
|
@ -25,6 +25,7 @@ __all__ = (
|
||||
'ProviderFilterSet',
|
||||
'VirtualCircuitFilterSet',
|
||||
'VirtualCircuitTerminationFilterSet',
|
||||
'VirtualCircuitTypeFilterSet',
|
||||
)
|
||||
|
||||
|
||||
@ -462,6 +463,13 @@ class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTypeFilterSet(OrganizationalModelFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_network__provider',
|
||||
@ -489,6 +497,16 @@ class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
label=_('Provider network (ID)'),
|
||||
)
|
||||
type_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
label=_('Virtual circuit type (ID)'),
|
||||
)
|
||||
type = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='type__slug',
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
to_field_name='slug',
|
||||
label=_('Virtual circuit type (slug)'),
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=CircuitStatusChoices,
|
||||
null_value=None
|
||||
|
@ -32,6 +32,7 @@ __all__ = (
|
||||
'ProviderNetworkBulkEditForm',
|
||||
'VirtualCircuitBulkEditForm',
|
||||
'VirtualCircuitTerminationBulkEditForm',
|
||||
'VirtualCircuitTypeBulkEditForm',
|
||||
)
|
||||
|
||||
|
||||
@ -297,6 +298,24 @@ class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
||||
nullable_fields = ('priority',)
|
||||
|
||||
|
||||
class VirtualCircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = VirtualCircuitType
|
||||
fieldsets = (
|
||||
FieldSet('color', 'description'),
|
||||
)
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
@ -308,6 +327,11 @@ class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
required=False
|
||||
)
|
||||
type = DynamicModelChoiceField(
|
||||
label=_('Type'),
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
required=False
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(CircuitStatusChoices),
|
||||
|
@ -24,6 +24,7 @@ __all__ = (
|
||||
'VirtualCircuitImportForm',
|
||||
'VirtualCircuitTerminationImportForm',
|
||||
'VirtualCircuitTerminationImportRelatedForm',
|
||||
'VirtualCircuitTypeImportForm',
|
||||
)
|
||||
|
||||
|
||||
@ -194,6 +195,14 @@ class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
|
||||
fields = ('member_type', 'member_id', 'group', 'priority')
|
||||
|
||||
|
||||
class VirtualCircuitTypeImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||
|
||||
|
||||
class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||
provider_network = CSVModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
@ -208,6 +217,12 @@ class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||
help_text=_('Assigned provider account (if any)'),
|
||||
required=False
|
||||
)
|
||||
type = CSVModelChoiceField(
|
||||
label=_('Type'),
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text=_('Type of virtual circuit')
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=CircuitStatusChoices,
|
||||
@ -224,7 +239,8 @@ class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'status', 'tenant', 'description', 'comments', 'tags',
|
||||
'cid', 'provider_network', 'provider_account', 'type', 'status', 'tenant', 'description', 'comments',
|
||||
'tags',
|
||||
]
|
||||
|
||||
|
||||
|
@ -27,6 +27,7 @@ __all__ = (
|
||||
'ProviderNetworkFilterForm',
|
||||
'VirtualCircuitFilterForm',
|
||||
'VirtualCircuitTerminationFilterForm',
|
||||
'VirtualCircuitTypeFilterForm',
|
||||
)
|
||||
|
||||
|
||||
@ -302,12 +303,26 @@ class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualCircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = VirtualCircuitType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('color', name=_('Attributes')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualCircuit
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||
FieldSet('status', name=_('Attributes')),
|
||||
FieldSet('type', 'status', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'provider_id', 'provider_network_id')
|
||||
@ -332,6 +347,11 @@ class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBox
|
||||
},
|
||||
label=_('Provider network')
|
||||
)
|
||||
type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
required=False,
|
||||
label=_('Type')
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
label=_('Status'),
|
||||
choices=CircuitStatusChoices,
|
||||
|
@ -31,6 +31,7 @@ __all__ = (
|
||||
'ProviderNetworkForm',
|
||||
'VirtualCircuitForm',
|
||||
'VirtualCircuitTerminationForm',
|
||||
'VirtualCircuitTypeForm',
|
||||
)
|
||||
|
||||
|
||||
@ -305,6 +306,20 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
||||
self.instance.member = self.cleaned_data.get('member')
|
||||
|
||||
|
||||
class VirtualCircuitTypeForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
@ -316,11 +331,16 @@ class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
required=False
|
||||
)
|
||||
type = DynamicModelChoiceField(
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'provider_network', 'provider_account', 'cid', 'status', 'description', 'tags', name=_('Virtual circuit'),
|
||||
'provider_network', 'provider_account', 'cid', 'type', 'status', 'description', 'tags',
|
||||
name=_('Virtual circuit'),
|
||||
),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
@ -328,7 +348,7 @@ class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'cid', 'provider_network', 'provider_account', 'type', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
|
@ -14,6 +14,7 @@ __all__ = (
|
||||
'ProviderNetworkFilter',
|
||||
'VirtualCircuitFilter',
|
||||
'VirtualCircuitTerminationFilter',
|
||||
'VirtualCircuitTypeFilter',
|
||||
)
|
||||
|
||||
|
||||
@ -65,6 +66,12 @@ class ProviderNetworkFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualCircuitType, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualCircuitTypeFilterSet)
|
||||
class VirtualCircuitTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualCircuit, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualCircuitFilterSet)
|
||||
class VirtualCircuitFilter(BaseFilterMixin):
|
||||
|
@ -37,3 +37,6 @@ class CircuitsQuery:
|
||||
|
||||
virtual_circuit_termination: VirtualCircuitTerminationType = strawberry_django.field()
|
||||
virtual_circuit_termination_list: List[VirtualCircuitTerminationType] = strawberry_django.field()
|
||||
|
||||
virtual_circuit_type: VirtualCircuitTypeType = strawberry_django.field()
|
||||
virtual_circuit_type_list: List[VirtualCircuitTypeType] = strawberry_django.field()
|
||||
|
@ -21,6 +21,7 @@ __all__ = (
|
||||
'ProviderNetworkType',
|
||||
'VirtualCircuitTerminationType',
|
||||
'VirtualCircuitType',
|
||||
'VirtualCircuitTypeType',
|
||||
)
|
||||
|
||||
|
||||
@ -130,6 +131,17 @@ class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
|
||||
return self.member
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualCircuitType,
|
||||
fields='__all__',
|
||||
filters=VirtualCircuitTypeFilter
|
||||
)
|
||||
class VirtualCircuitTypeType(OrganizationalObjectType):
|
||||
color: str
|
||||
|
||||
virtual_circuits: List[Annotated["VirtualCircuitType", strawberry.lazy('circuits.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualCircuitTermination,
|
||||
fields='__all__',
|
||||
@ -154,6 +166,9 @@ class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
class VirtualCircuitType(NetBoxObjectType):
|
||||
provider_network: ProviderNetworkType = strawberry_django.field(select_related=["provider_network"])
|
||||
provider_account: ProviderAccountType | None
|
||||
type: Annotated["VirtualCircuitTypeType", strawberry.lazy('circuits.graphql.types')] = strawberry_django.field(
|
||||
select_related=["type"]
|
||||
)
|
||||
tenant: TenantType | None
|
||||
|
||||
terminations: List[VirtualCircuitTerminationType]
|
||||
|
@ -2,6 +2,7 @@ import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
import utilities.fields
|
||||
import utilities.json
|
||||
|
||||
|
||||
@ -14,6 +15,29 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VirtualCircuitType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
encoder=utilities.json.CustomFieldJSONEncoder
|
||||
)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('color', utilities.fields.ColorField(blank=True, max_length=6)),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'virtual circuit type',
|
||||
'verbose_name_plural': 'virtual circuit types',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VirtualCircuit',
|
||||
fields=[
|
||||
@ -47,6 +71,14 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
(
|
||||
'type',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='virtual_circuits',
|
||||
to='circuits.virtualcircuittype'
|
||||
)
|
||||
),
|
||||
(
|
||||
'tenant',
|
||||
models.ForeignKey(
|
||||
|
23
netbox/circuits/models/base.py
Normal file
23
netbox/circuits/models/base.py
Normal file
@ -0,0 +1,23 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.models import OrganizationalModel
|
||||
from utilities.fields import ColorField
|
||||
|
||||
__all__ = (
|
||||
'BaseCircuitType',
|
||||
)
|
||||
|
||||
|
||||
class BaseCircuitType(OrganizationalModel):
|
||||
"""
|
||||
Abstract base model to represent a type of physical or virtual circuit.
|
||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||
"Long Haul," "Metro," or "Out-of-Band".
|
||||
"""
|
||||
color = ColorField(
|
||||
verbose_name=_('color'),
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
@ -13,7 +13,7 @@ from netbox.models.mixins import DistanceMixin
|
||||
from netbox.models.features import (
|
||||
ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin,
|
||||
)
|
||||
from utilities.fields import ColorField
|
||||
from .base import BaseCircuitType
|
||||
|
||||
__all__ = (
|
||||
'Circuit',
|
||||
@ -24,16 +24,11 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CircuitType(OrganizationalModel):
|
||||
class CircuitType(BaseCircuitType):
|
||||
"""
|
||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||
"Long Haul," "Metro," or "Out-of-Band".
|
||||
"""
|
||||
color = ColorField(
|
||||
verbose_name=_('color'),
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
verbose_name = _('circuit type')
|
||||
@ -64,7 +59,7 @@ class Circuit(ContactsMixin, ImageAttachmentsMixin, DistanceMixin, PrimaryModel)
|
||||
null=True
|
||||
)
|
||||
type = models.ForeignKey(
|
||||
to='CircuitType',
|
||||
to='circuits.CircuitType',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='circuits'
|
||||
)
|
||||
|
@ -9,13 +9,26 @@ from django.utils.translation import gettext_lazy as _
|
||||
from circuits.choices import *
|
||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
|
||||
from .base import BaseCircuitType
|
||||
|
||||
__all__ = (
|
||||
'VirtualCircuit',
|
||||
'VirtualCircuitTermination',
|
||||
'VirtualCircuitType',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitType(BaseCircuitType):
|
||||
"""
|
||||
Like physical circuits, virtual circuits can be organized by their functional role. For example, a user might wish
|
||||
to categorize virtual circuits by their technological nature or by product name.
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
verbose_name = _('virtual circuit type')
|
||||
verbose_name_plural = _('virtual circuit types')
|
||||
|
||||
|
||||
class VirtualCircuit(PrimaryModel):
|
||||
"""
|
||||
A virtual connection between two or more endpoints, delivered across one or more physical circuits.
|
||||
@ -37,6 +50,11 @@ class VirtualCircuit(PrimaryModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
type = models.ForeignKey(
|
||||
to='circuits.VirtualCircuitType',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='virtual_circuits'
|
||||
)
|
||||
status = models.CharField(
|
||||
verbose_name=_('status'),
|
||||
max_length=50,
|
||||
@ -63,6 +81,7 @@ class VirtualCircuit(PrimaryModel):
|
||||
)
|
||||
prerequisite_models = (
|
||||
'circuits.ProviderNetwork',
|
||||
'circuits.VirtualCircuitType',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -100,3 +100,14 @@ class VirtualCircuitTerminationIndex(SearchIndex):
|
||||
('description', 500),
|
||||
)
|
||||
display_attrs = ('virtual_circuit', 'role', 'description')
|
||||
|
||||
|
||||
@register_search
|
||||
class VirtualCircuitTypeIndex(SearchIndex):
|
||||
model = models.VirtualCircuitType
|
||||
fields = (
|
||||
('name', 100),
|
||||
('slug', 110),
|
||||
('description', 500),
|
||||
)
|
||||
display_attrs = ('description',)
|
||||
|
@ -45,7 +45,7 @@ class CircuitTypeTable(NetBoxTable):
|
||||
'pk', 'id', 'name', 'circuit_count', 'color', 'description', 'slug', 'tags', 'created', 'last_updated',
|
||||
'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug')
|
||||
default_columns = ('pk', 'name', 'circuit_count', 'color', 'description')
|
||||
|
||||
|
||||
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
@ -61,6 +61,10 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
linkify=True,
|
||||
verbose_name=_('Account')
|
||||
)
|
||||
type = tables.Column(
|
||||
verbose_name=_('Type'),
|
||||
linkify=True
|
||||
)
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_a = columns.TemplateColumn(
|
||||
template_code=CIRCUITTERMINATION_LINK,
|
||||
|
@ -8,9 +8,34 @@ from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
__all__ = (
|
||||
'VirtualCircuitTable',
|
||||
'VirtualCircuitTerminationTable',
|
||||
'VirtualCircuitTypeTable',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTypeTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Name'),
|
||||
)
|
||||
color = columns.ColorColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:virtualcircuittype_list'
|
||||
)
|
||||
virtual_circuit_count = columns.LinkedCountColumn(
|
||||
viewname='circuits:virtualcircuit_list',
|
||||
url_params={'type_id': 'pk'},
|
||||
verbose_name=_('Circuits')
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualCircuitType
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'virtual_circuit_count', 'color', 'description', 'slug', 'tags', 'created',
|
||||
'last_updated', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'virtual_circuit_count', 'color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
cid = tables.Column(
|
||||
linkify=True,
|
||||
@ -29,6 +54,10 @@ class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
|
||||
linkify=True,
|
||||
verbose_name=_('Account')
|
||||
)
|
||||
type = tables.Column(
|
||||
verbose_name=_('Type'),
|
||||
linkify=True
|
||||
)
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_count = columns.LinkedCountColumn(
|
||||
viewname='circuits:virtualcircuittermination_list',
|
||||
@ -45,12 +74,12 @@ class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualCircuit
|
||||
fields = (
|
||||
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'tenant_group',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'type', 'status', 'tenant',
|
||||
'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'termination_count',
|
||||
'description',
|
||||
'pk', 'cid', 'provider', 'provider_account', 'provider_network', 'type', 'status', 'tenant',
|
||||
'termination_count', 'description',
|
||||
)
|
||||
|
||||
|
||||
|
@ -409,6 +409,38 @@ class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
|
||||
}
|
||||
|
||||
|
||||
class VirtualCircuitTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
model = VirtualCircuitType
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url', 'virtual_circuit_count']
|
||||
create_data = (
|
||||
{
|
||||
'name': 'Virtual Circuit Type 4',
|
||||
'slug': 'virtual-circuit-type-4',
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Circuit Type 5',
|
||||
'slug': 'virtual-circuit-type-5',
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Circuit Type 6',
|
||||
'slug': 'virtual-circuit-type-6',
|
||||
},
|
||||
)
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
virtual_circuit_types = (
|
||||
VirtualCircuitType(name='Virtual Circuit Type 1', slug='virtual-circuit-type-1'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 2', slug='virtual-circuit-type-2'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 3', slug='virtual-circuit-type-3'),
|
||||
)
|
||||
VirtualCircuitType.objects.bulk_create(virtual_circuit_types)
|
||||
|
||||
|
||||
class VirtualCircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
model = VirtualCircuit
|
||||
brief_fields = ['cid', 'description', 'display', 'id', 'provider_network', 'url']
|
||||
@ -421,21 +453,28 @@ class VirtualCircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
|
||||
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
|
||||
virtual_circuit_type = VirtualCircuitType.objects.create(
|
||||
name='Virtual Circuit Type 1',
|
||||
slug='virtual-circuit-type-1'
|
||||
)
|
||||
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
type=virtual_circuit_type,
|
||||
cid='Virtual Circuit 1'
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
type=virtual_circuit_type,
|
||||
cid='Virtual Circuit 2'
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
type=virtual_circuit_type,
|
||||
cid='Virtual Circuit 3'
|
||||
),
|
||||
)
|
||||
@ -446,18 +485,21 @@ class VirtualCircuitTest(APIViewTestCases.APIViewTestCase):
|
||||
'cid': 'Virtual Circuit 4',
|
||||
'provider_network': provider_network.pk,
|
||||
'provider_account': provider_account.pk,
|
||||
'type': virtual_circuit_type.pk,
|
||||
'status': CircuitStatusChoices.STATUS_PLANNED,
|
||||
},
|
||||
{
|
||||
'cid': 'Virtual Circuit 5',
|
||||
'provider_network': provider_network.pk,
|
||||
'provider_account': provider_account.pk,
|
||||
'type': virtual_circuit_type.pk,
|
||||
'status': CircuitStatusChoices.STATUS_PLANNED,
|
||||
},
|
||||
{
|
||||
'cid': 'Virtual Circuit 6',
|
||||
'provider_network': provider_network.pk,
|
||||
'provider_account': provider_account.pk,
|
||||
'type': virtual_circuit_type.pk,
|
||||
'status': CircuitStatusChoices.STATUS_PLANNED,
|
||||
},
|
||||
]
|
||||
@ -563,27 +605,35 @@ class VirtualCircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
|
||||
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
|
||||
virtual_circuit_type = VirtualCircuitType.objects.create(
|
||||
name='Virtual Circuit Type 1',
|
||||
slug='virtual-circuit-type-1'
|
||||
)
|
||||
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 1'
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 2'
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 3'
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 4'
|
||||
cid='Virtual Circuit 4',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
)
|
||||
VirtualCircuit.objects.bulk_create(virtual_circuits)
|
||||
|
@ -656,12 +656,12 @@ class CircuitGroupAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Provider(name='Provider 2', slug='provider-2'),
|
||||
Provider(name='Provider 3', slug='provider-3'),
|
||||
))
|
||||
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
|
||||
circuits = (
|
||||
Circuit(cid='Circuit 1', provider=providers[0], type=circuittype),
|
||||
Circuit(cid='Circuit 2', provider=providers[1], type=circuittype),
|
||||
Circuit(cid='Circuit 3', provider=providers[2], type=circuittype),
|
||||
Circuit(cid='Circuit 1', provider=providers[0], type=circuit_type),
|
||||
Circuit(cid='Circuit 2', provider=providers[1], type=circuit_type),
|
||||
Circuit(cid='Circuit 3', provider=providers[2], type=circuit_type),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
@ -672,18 +672,25 @@ class CircuitGroupAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
virtual_circuit_type = VirtualCircuitType.objects.create(
|
||||
name='Virtual Circuit Type 1',
|
||||
slug='virtual-circuit-type-1'
|
||||
)
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
cid='Virtual Circuit 1'
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[1],
|
||||
cid='Virtual Circuit 2'
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[2],
|
||||
cid='Virtual Circuit 3'
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
)
|
||||
VirtualCircuit.objects.bulk_create(virtual_circuits)
|
||||
@ -837,6 +844,36 @@ class ProviderAccountTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class VirtualCircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
filterset = VirtualCircuitTypeFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
VirtualCircuitType.objects.bulk_create((
|
||||
VirtualCircuitType(name='Virtual Circuit Type 1', slug='virtual-circuit-type-1', description='foobar1'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 2', slug='virtual-circuit-type-2', description='foobar2'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 3', slug='virtual-circuit-type-3'),
|
||||
))
|
||||
|
||||
def test_q(self):
|
||||
params = {'q': 'foobar1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Virtual Circuit Type 1']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['virtual-circuit-type-1']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['foobar1', 'foobar2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
filterset = VirtualCircuitFilterSet
|
||||
@ -880,12 +917,20 @@ class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
)
|
||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||
|
||||
virtual_circuit_types = (
|
||||
VirtualCircuitType(name='Virtual Circuit Type 1', slug='virtual-circuit-type-1'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 2', slug='virtual-circuit-type-2'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 3', slug='virtual-circuit-type-3'),
|
||||
)
|
||||
VirtualCircuitType.objects.bulk_create(virtual_circuit_types)
|
||||
|
||||
virutal_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
provider_account=provider_accounts[0],
|
||||
tenant=tenants[0],
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_types[0],
|
||||
status=CircuitStatusChoices.STATUS_PLANNED,
|
||||
description='virtualcircuit1',
|
||||
),
|
||||
@ -894,6 +939,7 @@ class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
provider_account=provider_accounts[1],
|
||||
tenant=tenants[1],
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_types[1],
|
||||
status=CircuitStatusChoices.STATUS_ACTIVE,
|
||||
description='virtualcircuit2',
|
||||
),
|
||||
@ -902,6 +948,7 @@ class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
provider_account=provider_accounts[2],
|
||||
tenant=tenants[2],
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_types[2],
|
||||
status=CircuitStatusChoices.STATUS_DEPROVISIONING,
|
||||
description='virtualcircuit3',
|
||||
),
|
||||
@ -933,6 +980,13 @@ class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_type(self):
|
||||
virtual_circuit_types = VirtualCircuitType.objects.all()[:2]
|
||||
params = {'type_id': [virtual_circuit_types[0].pk, virtual_circuit_types[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'type': [virtual_circuit_types[0].slug, virtual_circuit_types[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_status(self):
|
||||
params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
@ -1029,22 +1083,29 @@ class VirtualCircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
ProviderAccount(provider=providers[2], account='Provider Account 3'),
|
||||
)
|
||||
ProviderAccount.objects.bulk_create(provider_accounts)
|
||||
virtual_circuit_type = VirtualCircuitType.objects.create(
|
||||
name='Virtual Circuit Type 1',
|
||||
slug='virtual-circuit-type-1'
|
||||
)
|
||||
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
provider_account=provider_accounts[0],
|
||||
cid='Virtual Circuit 1'
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[1],
|
||||
provider_account=provider_accounts[1],
|
||||
cid='Virtual Circuit 2'
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[2],
|
||||
provider_account=provider_accounts[2],
|
||||
cid='Virtual Circuit 3'
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
)
|
||||
VirtualCircuit.objects.bulk_create(virtual_circuits)
|
||||
|
@ -543,6 +543,47 @@ class CircuitGroupAssignmentTestCase(
|
||||
}
|
||||
|
||||
|
||||
class VirtualCircuitTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = VirtualCircuitType
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
virtual_circuit_types = (
|
||||
VirtualCircuitType(name='Virtual Circuit Type 1', slug='circuit-type-1'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 2', slug='circuit-type-2'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 3', slug='circuit-type-3'),
|
||||
)
|
||||
VirtualCircuitType.objects.bulk_create(virtual_circuit_types)
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Virtual Circuit Type X',
|
||||
'slug': 'virtual-circuit-type-x',
|
||||
'description': 'A new virtual circuit type',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,slug",
|
||||
"Virtual Circuit Type 4,circuit-type-4",
|
||||
"Virtual Circuit Type 5,circuit-type-5",
|
||||
"Virtual Circuit Type 6,circuit-type-6",
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
"id,name,description",
|
||||
f"{virtual_circuit_types[0].pk},Virtual Circuit Type 7,New description7",
|
||||
f"{virtual_circuit_types[1].pk},Virtual Circuit Type 8,New description8",
|
||||
f"{virtual_circuit_types[2].pk},Virtual Circuit Type 9,New description9",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'Foo',
|
||||
}
|
||||
|
||||
|
||||
class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = VirtualCircuit
|
||||
|
||||
@ -566,22 +607,30 @@ class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
ProviderAccount(provider=provider, account='Provider Account 2'),
|
||||
)
|
||||
ProviderAccount.objects.bulk_create(provider_accounts)
|
||||
virtual_circuit_types = (
|
||||
VirtualCircuitType(name='Virtual Circuit Type 1', slug='virtual-circuit-type-1'),
|
||||
VirtualCircuitType(name='Virtual Circuit Type 2', slug='virtual-circuit-type-2'),
|
||||
)
|
||||
VirtualCircuitType.objects.bulk_create(virtual_circuit_types)
|
||||
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
provider_account=provider_accounts[0],
|
||||
cid='Virtual Circuit 1'
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_types[0]
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
provider_account=provider_accounts[0],
|
||||
cid='Virtual Circuit 2'
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_types[0]
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_networks[0],
|
||||
provider_account=provider_accounts[0],
|
||||
cid='Virtual Circuit 3'
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_types[0]
|
||||
),
|
||||
)
|
||||
VirtualCircuit.objects.bulk_create(virtual_circuits)
|
||||
@ -600,6 +649,7 @@ class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'cid': 'Virtual Circuit X',
|
||||
'provider_network': provider_networks[1].pk,
|
||||
'provider_account': provider_accounts[1].pk,
|
||||
'type': virtual_circuit_types[1].pk,
|
||||
'status': CircuitStatusChoices.STATUS_PLANNED,
|
||||
'description': 'A new virtual circuit',
|
||||
'comments': 'Some comments',
|
||||
@ -607,22 +657,41 @@ class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"cid,provider_network,provider_account,status",
|
||||
f"Virtual Circuit 4,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
|
||||
f"Virtual Circuit 5,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
|
||||
f"Virtual Circuit 6,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
|
||||
"cid,provider_network,provider_account,type,status",
|
||||
(
|
||||
f"Virtual Circuit 4,Provider Network 1,Provider Account 1,{virtual_circuit_types[0].name},"
|
||||
f"{CircuitStatusChoices.STATUS_PLANNED}"
|
||||
),
|
||||
(
|
||||
f"Virtual Circuit 5,Provider Network 1,Provider Account 1,{virtual_circuit_types[0].name},"
|
||||
f"{CircuitStatusChoices.STATUS_PLANNED}"
|
||||
),
|
||||
(
|
||||
f"Virtual Circuit 6,Provider Network 1,Provider Account 1,{virtual_circuit_types[0].name},"
|
||||
f"{CircuitStatusChoices.STATUS_PLANNED}"
|
||||
),
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
"id,cid,description,status",
|
||||
f"{virtual_circuits[0].pk},Virtual Circuit A,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
|
||||
f"{virtual_circuits[1].pk},Virtual Circuit B,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
|
||||
f"{virtual_circuits[2].pk},Virtual Circuit C,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
|
||||
"id,cid,description,type,status",
|
||||
(
|
||||
f"{virtual_circuits[0].pk},Virtual Circuit A,New description,{virtual_circuit_types[1].name},"
|
||||
f"{CircuitStatusChoices.STATUS_DECOMMISSIONED}"
|
||||
),
|
||||
(
|
||||
f"{virtual_circuits[1].pk},Virtual Circuit B,New description,{virtual_circuit_types[1].name},"
|
||||
f"{CircuitStatusChoices.STATUS_DECOMMISSIONED}"
|
||||
),
|
||||
(
|
||||
f"{virtual_circuits[2].pk},Virtual Circuit C,New description,{virtual_circuit_types[1].name},"
|
||||
f"{CircuitStatusChoices.STATUS_DECOMMISSIONED}"
|
||||
),
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'provider_network': provider_networks[1].pk,
|
||||
'provider_account': provider_accounts[1].pk,
|
||||
'type': virtual_circuit_types[1].pk,
|
||||
'status': CircuitStatusChoices.STATUS_DECOMMISSIONED,
|
||||
'description': 'New description',
|
||||
'comments': 'New comments',
|
||||
@ -636,6 +705,7 @@ class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
{{
|
||||
"cid": "Virtual Circuit 7",
|
||||
"provider_network": "Provider Network 1",
|
||||
"type": "Virtual Circuit Type 1",
|
||||
"status": "active",
|
||||
"terminations": [
|
||||
{{
|
||||
@ -774,27 +844,35 @@ class VirtualCircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase)
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
|
||||
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
|
||||
virtual_circuit_type = VirtualCircuitType.objects.create(
|
||||
name='Virtual Circuit Type 1',
|
||||
slug='virtual-circuit-type-1'
|
||||
)
|
||||
|
||||
virtual_circuits = (
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 1'
|
||||
cid='Virtual Circuit 1',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 2'
|
||||
cid='Virtual Circuit 2',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 3'
|
||||
cid='Virtual Circuit 3',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
VirtualCircuit(
|
||||
provider_network=provider_network,
|
||||
provider_account=provider_account,
|
||||
cid='Virtual Circuit 4'
|
||||
cid='Virtual Circuit 4',
|
||||
type=virtual_circuit_type
|
||||
),
|
||||
)
|
||||
VirtualCircuit.objects.bulk_create(virtual_circuits)
|
||||
|
@ -42,6 +42,9 @@ urlpatterns = [
|
||||
path('virtual-circuits/delete/', views.VirtualCircuitBulkDeleteView.as_view(), name='virtualcircuit_bulk_delete'),
|
||||
path('virtual-circuits/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuit'))),
|
||||
|
||||
path('virtual-circuit-types/', include(get_model_urls('circuits', 'virtualcircuittype', detail=False))),
|
||||
path('virtual-circuit-types/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuittype'))),
|
||||
|
||||
# Virtual circuit terminations
|
||||
path(
|
||||
'virtual-circuit-terminations/',
|
||||
|
@ -579,6 +579,67 @@ class CircuitGroupAssignmentBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.CircuitGroupAssignmentTable
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuit Types
|
||||
#
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'list', path='', detail=False)
|
||||
class VirtualCircuitTypeListView(generic.ObjectListView):
|
||||
queryset = VirtualCircuitType.objects.annotate(
|
||||
virtual_circuit_count=count_related(VirtualCircuit, 'type')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitTypeFilterSet
|
||||
filterset_form = forms.VirtualCircuitTypeFilterForm
|
||||
table = tables.VirtualCircuitTypeTable
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType)
|
||||
class VirtualCircuitTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'add', detail=False)
|
||||
@register_model_view(VirtualCircuitType, 'edit')
|
||||
class VirtualCircuitTypeEditView(generic.ObjectEditView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
form = forms.VirtualCircuitTypeForm
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'delete')
|
||||
class VirtualCircuitTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'bulk_import', detail=False)
|
||||
class VirtualCircuitTypeBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualCircuitType.objects.all()
|
||||
model_form = forms.VirtualCircuitTypeImportForm
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'bulk_edit', path='edit', detail=False)
|
||||
class VirtualCircuitTypeBulkEditView(generic.BulkEditView):
|
||||
queryset = VirtualCircuitType.objects.annotate(
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitTypeFilterSet
|
||||
table = tables.VirtualCircuitTypeTable
|
||||
form = forms.VirtualCircuitTypeBulkEditForm
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitType, 'bulk_delete', path='delete', detail=False)
|
||||
class VirtualCircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = VirtualCircuitType.objects.annotate(
|
||||
circuit_count=count_related(Circuit, 'type')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitTypeFilterSet
|
||||
table = tables.VirtualCircuitTypeTable
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuits
|
||||
#
|
||||
|
@ -1170,6 +1170,7 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
'virtualchassis',
|
||||
'virtualcircuit',
|
||||
'virtualcircuittermination',
|
||||
'virtualcircuittype',
|
||||
'virtualdevicecontext',
|
||||
'virtualdisk',
|
||||
'virtualmachine',
|
||||
|
@ -286,6 +286,7 @@ CIRCUITS_MENU = Menu(
|
||||
label=_('Virtual Circuits'),
|
||||
items=(
|
||||
get_model_item('circuits', 'virtualcircuit', _('Virtual Circuits')),
|
||||
get_model_item('circuits', 'virtualcircuittype', _('Virtual Circuit Types')),
|
||||
get_model_item('circuits', 'virtualcircuittermination', _('Virtual Circuit Terminations')),
|
||||
),
|
||||
),
|
||||
|
@ -35,6 +35,10 @@
|
||||
<th scope="row">{% trans "Circuit ID" %}</th>
|
||||
<td>{{ object.cid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Type" %}</th>
|
||||
<td>{{ object.type|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Status" %}</th>
|
||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||
|
55
netbox/templates/circuits/virtualcircuittype.html
Normal file
55
netbox/templates/circuits/virtualcircuittype.html
Normal file
@ -0,0 +1,55 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{% if perms.circuits.add_virtualcircuit %}
|
||||
<a href="{% url 'circuits:virtualcircuit_add' %}?type={{ object.pk }}" class="btn btn-primary">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Virtual Circuit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock extra_controls %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual Circuit Type" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Name" %}</th>
|
||||
<td>{{ object.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Description" %}</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Color" %}</th>
|
||||
<td>
|
||||
{% if object.color %}
|
||||
<span class="badge color-label" style="background-color: #{{ object.color }}"> </span>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/related_objects.html' %}
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user