mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-29 20:06:25 -06:00
WIP
This commit is contained in:
parent
03d413565f
commit
9aa4ae599e
@ -2,9 +2,13 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
|
||||
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
|
||||
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
|
||||
from circuits.models import (
|
||||
Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType, VirtualCircuit,
|
||||
VirtualCircuitTermination,
|
||||
)
|
||||
from dcim.api.serializers_.device_components import InterfaceSerializer
|
||||
from dcim.api.serializers_.cables import CabledObjectSerializer
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
||||
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
||||
@ -20,6 +24,8 @@ __all__ = (
|
||||
'CircuitGroupSerializer',
|
||||
'CircuitTerminationSerializer',
|
||||
'CircuitTypeSerializer',
|
||||
'VirtualCircuitSerializer',
|
||||
'VirtualCircuitTerminationSerializer',
|
||||
)
|
||||
|
||||
|
||||
@ -156,3 +162,32 @@ class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
|
||||
'id', 'url', 'display_url', 'display', 'group', 'circuit', 'priority', 'tags', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'group', 'circuit', 'priority')
|
||||
|
||||
|
||||
class VirtualCircuitSerializer(NetBoxModelSerializer):
|
||||
provider_network = ProviderNetworkSerializer(nested=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
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',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
virtual_circuit = VirtualCircuitSerializer(nested=True)
|
||||
role = ChoiceField(choices=VirtualCircuitTerminationRoleChoices, required=False)
|
||||
interface = InterfaceSerializer(nested=True)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitTermination
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'virtual_circuit', 'role', 'interface', 'description', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'virtual_circuit', 'role', 'interface')
|
||||
|
@ -17,5 +17,9 @@ router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
||||
router.register('circuit-groups', views.CircuitGroupViewSet)
|
||||
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)
|
||||
|
||||
# Virtual circuits
|
||||
router.register('virtual-circuits', views.VirtualCircuitViewSet)
|
||||
router.register('virtual-circuit-terminations', views.VirtualCircuitTerminationViewSet)
|
||||
|
||||
app_name = 'circuits-api'
|
||||
urlpatterns = router.urls
|
||||
|
@ -93,3 +93,23 @@ class ProviderNetworkViewSet(NetBoxModelViewSet):
|
||||
queryset = ProviderNetwork.objects.all()
|
||||
serializer_class = serializers.ProviderNetworkSerializer
|
||||
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuits
|
||||
#
|
||||
|
||||
class VirtualCircuitViewSet(NetBoxModelViewSet):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
serializer_class = serializers.VirtualCircuitSerializer
|
||||
filterset_class = filtersets.VirtualCircuitFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuit terminations
|
||||
#
|
||||
|
||||
class VirtualCircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
serializer_class = serializers.VirtualCircuitTerminationSerializer
|
||||
filterset_class = filtersets.VirtualCircuitTerminationFilterSet
|
||||
|
@ -92,3 +92,19 @@ class CircuitPriorityChoices(ChoiceSet):
|
||||
(PRIORITY_TERTIARY, _('Tertiary')),
|
||||
(PRIORITY_INACTIVE, _('Inactive')),
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuits
|
||||
#
|
||||
|
||||
class VirtualCircuitTerminationRoleChoices(ChoiceSet):
|
||||
ROLE_PEER = 'peer'
|
||||
ROLE_HUB = 'hub'
|
||||
ROLE_SPOKE = 'spoke'
|
||||
|
||||
CHOICES = [
|
||||
(ROLE_PEER, _('Peer'), 'green'),
|
||||
(ROLE_HUB, _('Hub'), 'blue'),
|
||||
(ROLE_SPOKE, _('Spoke'), 'orange'),
|
||||
]
|
||||
|
@ -404,3 +404,103 @@ class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
|
||||
Q(circuit__cid__icontains=value) |
|
||||
Q(group__name__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_network__provider',
|
||||
queryset=Provider.objects.all(),
|
||||
label=_('Provider (ID)'),
|
||||
)
|
||||
provider = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_network__provider__slug',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
label=_('Provider (slug)'),
|
||||
)
|
||||
provider_account_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_account',
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
label=_('Provider account (ID)'),
|
||||
)
|
||||
provider_account = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_account__account',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='account',
|
||||
label=_('Provider account (account)'),
|
||||
)
|
||||
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
label=_('Provider network (ID)'),
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=CircuitStatusChoices,
|
||||
null_value=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = ('id', 'cid', 'description')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(cid__icontains=value) |
|
||||
Q(description__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
|
||||
|
||||
class VirtualCircuitTerminationFilterSet(NetBoxModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label=_('Search'),
|
||||
)
|
||||
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=VirtualCircuit.objects.all(),
|
||||
label=_('Virtual circuit'),
|
||||
)
|
||||
role = django_filters.MultipleChoiceFilter(
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
null_value=None
|
||||
)
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit__provider_network__provider',
|
||||
queryset=Provider.objects.all(),
|
||||
label=_('Provider (ID)'),
|
||||
)
|
||||
provider = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit__provider_network__provider__slug',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
label=_('Provider (slug)'),
|
||||
)
|
||||
provider_account_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit__provider_account',
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
label=_('Provider account (ID)'),
|
||||
)
|
||||
provider_account = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit__provider_account__account',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='account',
|
||||
label=_('Provider account (account)'),
|
||||
)
|
||||
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
field_name='virtual_circuit__provider_network',
|
||||
label=_('Provider network (ID)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitTermination
|
||||
fields = ('id', 'interface_id')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(circuit__cid__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
).distinct()
|
||||
|
@ -3,7 +3,9 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices
|
||||
from circuits.choices import (
|
||||
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices,
|
||||
)
|
||||
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||
from circuits.models import *
|
||||
from dcim.models import Site
|
||||
@ -28,6 +30,8 @@ __all__ = (
|
||||
'ProviderBulkEditForm',
|
||||
'ProviderAccountBulkEditForm',
|
||||
'ProviderNetworkBulkEditForm',
|
||||
'VirtualCircuitBulkEditForm',
|
||||
'VirtualCircuitTerminationBulkEditForm',
|
||||
)
|
||||
|
||||
|
||||
@ -291,3 +295,62 @@ class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
||||
FieldSet('circuit', 'priority'),
|
||||
)
|
||||
nullable_fields = ('priority',)
|
||||
|
||||
|
||||
class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
required=False
|
||||
)
|
||||
provider_account = DynamicModelChoiceField(
|
||||
label=_('Provider account'),
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
required=False
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(CircuitStatusChoices),
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
label=_('Tenant'),
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = VirtualCircuit
|
||||
fieldsets = (
|
||||
FieldSet('provider_network', 'provider_account', 'status', 'description', name=_('Virtual circuit')),
|
||||
FieldSet('tenant', name=_('Tenancy')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'provider_account', 'tenant', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
role = forms.ChoiceField(
|
||||
label=_('Role'),
|
||||
choices=add_blank_choice(VirtualCircuitTerminationRoleChoices),
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = VirtualCircuitTermination
|
||||
fieldsets = (
|
||||
FieldSet('role', 'description'),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
|
@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from circuits.choices import *
|
||||
from circuits.constants import *
|
||||
from circuits.models import *
|
||||
from dcim.models import Interface
|
||||
from netbox.choices import DistanceUnitChoices
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
@ -20,6 +21,8 @@ __all__ = (
|
||||
'ProviderImportForm',
|
||||
'ProviderAccountImportForm',
|
||||
'ProviderNetworkImportForm',
|
||||
'VirtualCircuitImportForm',
|
||||
'VirtualCircuitTerminationImportForm',
|
||||
)
|
||||
|
||||
|
||||
@ -179,3 +182,61 @@ class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
|
||||
class Meta:
|
||||
model = CircuitGroupAssignment
|
||||
fields = ('circuit', 'group', 'priority')
|
||||
|
||||
|
||||
class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||
provider_network = CSVModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
to_field_name='cid',
|
||||
help_text=_('The network to which this virtual circuit belongs')
|
||||
)
|
||||
provider_account = CSVModelChoiceField(
|
||||
label=_('Provider account'),
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text=_('Assigned provider account (if any)'),
|
||||
required=False
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=CircuitStatusChoices,
|
||||
help_text=_('Operational status')
|
||||
)
|
||||
tenant = CSVModelChoiceField(
|
||||
label=_('Tenant'),
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
to_field_name='name',
|
||||
help_text=_('Assigned tenant')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'status', 'tenant','description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualCircuitTerminationImportForm(NetBoxModelImportForm):
|
||||
virtual_circuit = CSVModelChoiceField(
|
||||
label=_('Virtual circuit'),
|
||||
queryset=VirtualCircuit.objects.all(),
|
||||
to_field_name='cid',
|
||||
)
|
||||
role = CSVChoiceField(
|
||||
label=_('Role'),
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
help_text=_('Operational role')
|
||||
)
|
||||
interface = CSVModelChoiceField(
|
||||
label=_('Interface'),
|
||||
queryset=Interface.objects.all(),
|
||||
to_field_name='pk',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitTermination
|
||||
fields = [
|
||||
'virtual_circuit', 'description', 'tags'
|
||||
]
|
||||
|
@ -1,7 +1,10 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices
|
||||
from circuits.choices import (
|
||||
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices,
|
||||
VirtualCircuitTerminationRoleChoices,
|
||||
)
|
||||
from circuits.models import *
|
||||
from dcim.models import Location, Region, Site, SiteGroup
|
||||
from ipam.models import ASN
|
||||
@ -22,6 +25,8 @@ __all__ = (
|
||||
'ProviderFilterForm',
|
||||
'ProviderAccountFilterForm',
|
||||
'ProviderNetworkFilterForm',
|
||||
'VirtualCircuitFilterForm',
|
||||
'VirtualCircuitTerminationFilterForm',
|
||||
)
|
||||
|
||||
|
||||
@ -292,3 +297,74 @@ class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
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('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'provider_id', 'provider_network_id')
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
required=False,
|
||||
label=_('Provider')
|
||||
)
|
||||
provider_account_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'provider_id': '$provider_id'
|
||||
},
|
||||
label=_('Provider account')
|
||||
)
|
||||
provider_network_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'provider_id': '$provider_id'
|
||||
},
|
||||
label=_('Provider network')
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
label=_('Status'),
|
||||
choices=CircuitStatusChoices,
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualCircuitTerminationFilterForm(NetBoxModelFilterSetForm):
|
||||
model = VirtualCircuitTermination
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('virtual_circuit_id', 'role', name=_('Virtual circuit')),
|
||||
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||
)
|
||||
virtual_circuit_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VirtualCircuit.objects.all(),
|
||||
required=False,
|
||||
label=_('Virtual circuit')
|
||||
)
|
||||
role = forms.MultipleChoiceField(
|
||||
label=_('ROle'),
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
required=False
|
||||
)
|
||||
provider_network_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'provider_id': '$provider_id'
|
||||
},
|
||||
label=_('Provider network')
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
required=False,
|
||||
label=_('Provider')
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
@ -1,16 +1,21 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
|
||||
from circuits.choices import (
|
||||
CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices, VirtualCircuitTerminationRoleChoices,
|
||||
)
|
||||
from circuits.constants import *
|
||||
from circuits.models import *
|
||||
from dcim.models import Site
|
||||
from dcim.models import Interface, Site
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import get_field_value
|
||||
from utilities.forms.fields import CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
||||
from utilities.forms.fields import (
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields
|
||||
from utilities.forms.widgets import DatePicker, HTMXSelect, NumberWithOptions
|
||||
from utilities.templatetags.builtins.filters import bettertitle
|
||||
@ -24,6 +29,8 @@ __all__ = (
|
||||
'ProviderForm',
|
||||
'ProviderAccountForm',
|
||||
'ProviderNetworkForm',
|
||||
'VirtualCircuitForm',
|
||||
'VirtualCircuitTerminationForm',
|
||||
)
|
||||
|
||||
|
||||
@ -249,3 +256,62 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
||||
fields = [
|
||||
'group', 'circuit', 'priority', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
selector=True
|
||||
)
|
||||
provider_account = DynamicModelChoiceField(
|
||||
label=_('Provider account'),
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'provider_network', 'provider_account', 'cid', 'status', 'description', 'tags', name=_('Virtual circuit'),
|
||||
),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualCircuitTerminationForm(NetBoxModelForm):
|
||||
virtual_circuit = DynamicModelChoiceField(
|
||||
label=_('Virtual circuit'),
|
||||
queryset=VirtualCircuit.objects.all(),
|
||||
selector=True
|
||||
)
|
||||
role = forms.ChoiceField(
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
widget=HTMXSelect(),
|
||||
label=_('Role')
|
||||
)
|
||||
interface = DynamicModelChoiceField(
|
||||
label=_('Interface'),
|
||||
queryset=Interface.objects.all(),
|
||||
selector=True,
|
||||
query_params={
|
||||
'kind': 'virtual',
|
||||
}
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('virtual_circuit', 'role', 'description', 'tags', 'interface'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitTermination
|
||||
fields = [
|
||||
'virtual_circuit', 'role', 'interface', 'description', 'tags',
|
||||
]
|
||||
|
@ -4,14 +4,16 @@ from circuits import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationFilter',
|
||||
'CircuitFilter',
|
||||
'CircuitGroupAssignmentFilter',
|
||||
'CircuitGroupFilter',
|
||||
'CircuitTerminationFilter',
|
||||
'CircuitTypeFilter',
|
||||
'ProviderFilter',
|
||||
'ProviderAccountFilter',
|
||||
'ProviderNetworkFilter',
|
||||
'VirtualCircuitFilter',
|
||||
'VirtualCircuitTerminationFilter',
|
||||
)
|
||||
|
||||
|
||||
@ -61,3 +63,15 @@ class ProviderAccountFilter(BaseFilterMixin):
|
||||
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
|
||||
class ProviderNetworkFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualCircuit, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualCircuitFilterSet)
|
||||
class VirtualCircuitFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualCircuitTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualCircuitTerminationFilterSet)
|
||||
class VirtualCircuitTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
@ -31,3 +31,9 @@ class CircuitsQuery:
|
||||
|
||||
provider_network: ProviderNetworkType = strawberry_django.field()
|
||||
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
|
||||
|
||||
virtual_circuit: VirtualCircuitType = strawberry_django.field()
|
||||
virtual_circuit_list: List[VirtualCircuitType] = strawberry_django.field()
|
||||
|
||||
virtual_circuit_termination: VirtualCircuitTerminationType = strawberry_django.field()
|
||||
virtual_circuit_termination_list: List[VirtualCircuitTerminationType] = strawberry_django.field()
|
||||
|
@ -19,6 +19,8 @@ __all__ = (
|
||||
'ProviderType',
|
||||
'ProviderAccountType',
|
||||
'ProviderNetworkType',
|
||||
'VirtualCircuitTerminationType',
|
||||
'VirtualCircuitType',
|
||||
)
|
||||
|
||||
|
||||
@ -120,3 +122,26 @@ class CircuitGroupType(OrganizationalObjectType):
|
||||
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
|
||||
group: Annotated["CircuitGroupType", strawberry.lazy('circuits.graphql.types')]
|
||||
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualCircuitTermination,
|
||||
fields='__all__',
|
||||
filters=VirtualCircuitTerminationFilter
|
||||
)
|
||||
class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
virtual_circuit: Annotated["VirtualCircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||
interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualCircuit,
|
||||
fields='__all__',
|
||||
filters=VirtualCircuitFilter
|
||||
)
|
||||
class VirtualCircuitType(NetBoxObjectType):
|
||||
provider_network: ProviderNetworkType
|
||||
provider_account: ProviderAccountType | None
|
||||
tenant: TenantType | None
|
||||
|
||||
virtual_terminations: List[VirtualCircuitTerminationType]
|
||||
|
68
netbox/circuits/migrations/0049_virtual_circuits.py
Normal file
68
netbox/circuits/migrations/0049_virtual_circuits.py
Normal file
@ -0,0 +1,68 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 18:46
|
||||
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
import utilities.json
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0048_circuitterminations_cached_relations'),
|
||||
('dcim', '0196_qinq_svlan'),
|
||||
('extras', '0122_charfield_null_choices'),
|
||||
('tenancy', '0016_charfield_null_choices'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VirtualCircuit',
|
||||
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)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('cid', models.CharField(max_length=100)),
|
||||
('status', models.CharField(default='active', max_length=50)),
|
||||
('provider_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='circuits.provideraccount')),
|
||||
('provider_network', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='circuits.providernetwork')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_circuits', to='tenancy.tenant')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'circuit',
|
||||
'verbose_name_plural': 'circuits',
|
||||
'ordering': ['provider_network', 'provider_account', 'cid'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VirtualCircuitTermination',
|
||||
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)),
|
||||
('role', models.CharField(default='peer', max_length=50)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('interface', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='virtual_circuit_termination', to='dcim.interface')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
('virtual_circuit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.virtualcircuit')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'virtual circuit termination',
|
||||
'verbose_name_plural': 'virtual circuit terminations',
|
||||
'ordering': ['virtual_circuit', 'role', 'pk'],
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='virtualcircuit',
|
||||
constraint=models.UniqueConstraint(fields=('provider_network', 'cid'), name='circuits_virtualcircuit_unique_provider_network_cid'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='virtualcircuit',
|
||||
constraint=models.UniqueConstraint(fields=('provider_account', 'cid'), name='circuits_virtualcircuit_unique_provideraccount_cid'),
|
||||
),
|
||||
]
|
@ -1,2 +1,3 @@
|
||||
from .circuits import *
|
||||
from .providers import *
|
||||
from .virtual_circuits import *
|
||||
|
141
netbox/circuits/models/virtual_circuits.py
Normal file
141
netbox/circuits/models/virtual_circuits.py
Normal file
@ -0,0 +1,141 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
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
|
||||
|
||||
__all__ = (
|
||||
'VirtualCircuit',
|
||||
'VirtualCircuitTermination',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuit(PrimaryModel):
|
||||
"""
|
||||
A virtual connection between two or more endpoints, delivered across one or more physical circuits.
|
||||
"""
|
||||
cid = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name=_('circuit ID'),
|
||||
help_text=_('Unique circuit ID')
|
||||
)
|
||||
provider_network = models.ForeignKey(
|
||||
to='circuits.ProviderNetwork',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='virtual_circuits'
|
||||
)
|
||||
provider_account = models.ForeignKey(
|
||||
to='circuits.ProviderAccount',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='virtual_circuits',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
status = models.CharField(
|
||||
verbose_name=_('status'),
|
||||
max_length=50,
|
||||
choices=CircuitStatusChoices,
|
||||
default=CircuitStatusChoices.STATUS_ACTIVE
|
||||
)
|
||||
tenant = models.ForeignKey(
|
||||
to='tenancy.Tenant',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='virtual_circuits',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'provider_network', 'provider_account', 'status', 'tenant', 'description',
|
||||
)
|
||||
prerequisite_models = (
|
||||
'circuits.ProviderNetwork',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['provider_network', 'provider_account', 'cid']
|
||||
constraints = (
|
||||
models.UniqueConstraint(
|
||||
fields=('provider_network', 'cid'),
|
||||
name='%(app_label)s_%(class)s_unique_provider_network_cid'
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=('provider_account', 'cid'),
|
||||
name='%(app_label)s_%(class)s_unique_provideraccount_cid'
|
||||
),
|
||||
)
|
||||
verbose_name = _('virtual circuit')
|
||||
verbose_name_plural = _('virtual circuits')
|
||||
|
||||
def __str__(self):
|
||||
return self.cid
|
||||
|
||||
def get_status_color(self):
|
||||
return CircuitStatusChoices.colors.get(self.status)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
if self.provider_account and self.provider_network.provider != self.provider_account.provider:
|
||||
raise ValidationError({
|
||||
'provider_account': "The assigned account must belong to the provider of the assigned network."
|
||||
})
|
||||
|
||||
@property
|
||||
def provider(self):
|
||||
return self.provider_network.provider
|
||||
|
||||
|
||||
class VirtualCircuitTermination(
|
||||
CustomFieldsMixin,
|
||||
CustomLinksMixin,
|
||||
TagsMixin,
|
||||
ChangeLoggedModel
|
||||
):
|
||||
virtual_circuit = models.ForeignKey(
|
||||
to='circuits.VirtualCircuit',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='terminations'
|
||||
)
|
||||
role = models.CharField(
|
||||
verbose_name=_('role'),
|
||||
max_length=50,
|
||||
choices=VirtualCircuitTerminationRoleChoices,
|
||||
default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
|
||||
)
|
||||
interface = models.OneToOneField(
|
||||
to='dcim.Interface',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='virtual_circuit_termination'
|
||||
)
|
||||
description = models.CharField(
|
||||
verbose_name=_('description'),
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['virtual_circuit', 'role', 'pk']
|
||||
verbose_name = _('virtual circuit termination')
|
||||
verbose_name_plural = _('virtual circuit terminations')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.virtual_circuit}: {self.get_role_display()} termination'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('circuits:virtualcircuittermination', args=[self.pk])
|
||||
|
||||
def get_role_color(self):
|
||||
return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
|
||||
|
||||
def to_objectchange(self, action):
|
||||
objectchange = super().to_objectchange(action)
|
||||
objectchange.related_object = self.virtual_circuit
|
||||
return objectchange
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
return self.virtual_circuit
|
@ -80,3 +80,23 @@ class ProviderNetworkIndex(SearchIndex):
|
||||
('comments', 5000),
|
||||
)
|
||||
display_attrs = ('provider', 'service_id', 'description')
|
||||
|
||||
|
||||
@register_search
|
||||
class VirtualCircuitIndex(SearchIndex):
|
||||
model = models.VirtualCircuit
|
||||
fields = (
|
||||
('cid', 100),
|
||||
('description', 500),
|
||||
('comments', 5000),
|
||||
)
|
||||
display_attrs = ('provider', 'provider_network', 'provider_account', 'status', 'tenant', 'description')
|
||||
|
||||
|
||||
@register_search
|
||||
class VirtualCircuitTerminationIndex(SearchIndex):
|
||||
model = models.VirtualCircuitTermination
|
||||
fields = (
|
||||
('description', 500),
|
||||
)
|
||||
display_attrs = ('virtual_circuit', 'role', 'description')
|
||||
|
@ -1,3 +1,4 @@
|
||||
from .circuits import *
|
||||
from .columns import *
|
||||
from .providers import *
|
||||
from .virtual_circuits import *
|
||||
|
95
netbox/circuits/tables/virtual_circuits.py
Normal file
95
netbox/circuits/tables/virtual_circuits.py
Normal file
@ -0,0 +1,95 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from circuits.models import *
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
__all__ = (
|
||||
'VirtualCircuitTable',
|
||||
'VirtualCircuitTerminationTable',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
cid = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Circuit ID')
|
||||
)
|
||||
provider = tables.Column(
|
||||
accessor=tables.A('provider_network__provider'),
|
||||
verbose_name=_('Provider'),
|
||||
linkify=True
|
||||
)
|
||||
provider_network = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Provider network')
|
||||
)
|
||||
provider_account = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Account')
|
||||
)
|
||||
status = columns.ChoiceFieldColumn()
|
||||
termination_count = columns.LinkedCountColumn(
|
||||
viewname='circuits:virtualcircuittermination_list',
|
||||
url_params={'virtual_circuit_id': 'pk'},
|
||||
verbose_name=_('Terminations')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:virtualcircuit_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Circuit
|
||||
fields = (
|
||||
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'tenant_group',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'termination_count',
|
||||
'description',
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTerminationTable(NetBoxTable):
|
||||
virtual_circuit = tables.Column(
|
||||
verbose_name=_('Virtual circuit'),
|
||||
linkify=True
|
||||
)
|
||||
provider = tables.Column(
|
||||
accessor=tables.A('virtual_circuit__provider_network__provider'),
|
||||
verbose_name=_('Provider'),
|
||||
linkify=True
|
||||
)
|
||||
provider_network = tables.Column(
|
||||
accessor=tables.A('virtual_circuit__provider_network'),
|
||||
linkify=True,
|
||||
verbose_name=_('Provider network')
|
||||
)
|
||||
provider_account = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Account')
|
||||
)
|
||||
role = columns.ChoiceFieldColumn()
|
||||
device = tables.Column(
|
||||
accessor=tables.A('interface__device'),
|
||||
linkify=True,
|
||||
verbose_name=_('Device')
|
||||
)
|
||||
interface = tables.Column(
|
||||
verbose_name=_('Interface'),
|
||||
linkify=True
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualCircuitTermination
|
||||
fields = (
|
||||
'pk', 'id', 'virtual_circuit', 'provider', 'provider_network', 'provider_account', 'role', 'interfaces',
|
||||
'description', 'created', 'last_updated', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'virtual_circuit', 'role', 'device', 'interface', 'description',
|
||||
)
|
@ -70,4 +70,20 @@ urlpatterns = [
|
||||
path('circuit-group-assignments/edit/', views.CircuitGroupAssignmentBulkEditView.as_view(), name='circuitgroupassignment_bulk_edit'),
|
||||
path('circuit-group-assignments/delete/', views.CircuitGroupAssignmentBulkDeleteView.as_view(), name='circuitgroupassignment_bulk_delete'),
|
||||
path('circuit-group-assignments/<int:pk>/', include(get_model_urls('circuits', 'circuitgroupassignment'))),
|
||||
|
||||
# Virtual circuits
|
||||
path('virtual-circuits/', views.VirtualCircuitListView.as_view(), name='virtualcircuit_list'),
|
||||
path('virtual-circuits/add/', views.VirtualCircuitEditView.as_view(), name='virtualcircuit_add'),
|
||||
path('virtual-circuits/import/', views.VirtualCircuitBulkImportView.as_view(), name='virtualcircuit_import'),
|
||||
path('virtual-circuits/edit/', views.VirtualCircuitBulkEditView.as_view(), name='virtualcircuit_bulk_edit'),
|
||||
path('virtual-circuits/delete/', views.VirtualCircuitBulkDeleteView.as_view(), name='virtualcircuit_bulk_delete'),
|
||||
path('virtual-circuits/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuit'))),
|
||||
|
||||
# Virtual circuit terminations
|
||||
path('virtual-circuit-terminations/', views.VirtualCircuitTerminationListView.as_view(), name='virtualcircuittermination_list'),
|
||||
path('virtual-circuit-terminations/add/', views.VirtualCircuitTerminationEditView.as_view(), name='virtualcircuittermination_add'),
|
||||
path('virtual-circuit-terminations/import/', views.VirtualCircuitTerminationBulkImportView.as_view(), name='virtualcircuittermination_import'),
|
||||
path('virtual-circuit-terminations/edit/', views.VirtualCircuitTerminationBulkEditView.as_view(), name='virtualcircuittermination_bulk_edit'),
|
||||
path('virtual-circuit-terminations/delete/', views.VirtualCircuitTerminationBulkDeleteView.as_view(), name='virtualcircuittermination_bulk_delete'),
|
||||
path('virtual-circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuittermination'))),
|
||||
]
|
||||
|
@ -537,3 +537,109 @@ class CircuitGroupAssignmentBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = CircuitGroupAssignment.objects.all()
|
||||
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||
table = tables.CircuitGroupAssignmentTable
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuits
|
||||
#
|
||||
|
||||
class VirtualCircuitListView(generic.ObjectListView):
|
||||
queryset = VirtualCircuit.objects.annotate(
|
||||
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitFilterSet
|
||||
filterset_form = forms.VirtualCircuitFilterForm
|
||||
table = tables.VirtualCircuitTable
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuit)
|
||||
class VirtualCircuitView(generic.ObjectView):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuit, 'edit')
|
||||
class VirtualCircuitEditView(generic.ObjectEditView):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
form = forms.VirtualCircuitForm
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuit, 'delete')
|
||||
class VirtualCircuitDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
|
||||
|
||||
class VirtualCircuitBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualCircuit.objects.all()
|
||||
model_form = forms.VirtualCircuitImportForm
|
||||
# additional_permissions = [
|
||||
# 'circuits.add_circuittermination',
|
||||
# ]
|
||||
# related_object_forms = {
|
||||
# 'terminations': forms.VirtualCircuitTerminationImportRelatedForm,
|
||||
# }
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
data.update({'circuit': parent})
|
||||
return data
|
||||
|
||||
|
||||
class VirtualCircuitBulkEditView(generic.BulkEditView):
|
||||
queryset = VirtualCircuit.objects.annotate(
|
||||
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitFilterSet
|
||||
table = tables.VirtualCircuitTable
|
||||
form = forms.VirtualCircuitBulkEditForm
|
||||
|
||||
|
||||
class VirtualCircuitBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = VirtualCircuit.objects.annotate(
|
||||
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
|
||||
)
|
||||
filterset = filtersets.VirtualCircuitFilterSet
|
||||
table = tables.VirtualCircuitTable
|
||||
|
||||
|
||||
#
|
||||
# Virtual circuit terminations
|
||||
#
|
||||
|
||||
class VirtualCircuitTerminationListView(generic.ObjectListView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||
filterset_form = forms.VirtualCircuitTerminationFilterForm
|
||||
table = tables.VirtualCircuitTerminationTable
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitTermination)
|
||||
class VirtualCircuitTerminationView(generic.ObjectView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitTermination, 'edit')
|
||||
class VirtualCircuitTerminationEditView(generic.ObjectEditView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
form = forms.VirtualCircuitTerminationForm
|
||||
|
||||
|
||||
@register_model_view(VirtualCircuitTermination, 'delete')
|
||||
class VirtualCircuitTerminationDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
|
||||
|
||||
class VirtualCircuitTerminationBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
model_form = forms.VirtualCircuitTerminationImportForm
|
||||
|
||||
|
||||
class VirtualCircuitTerminationBulkEditView(generic.BulkEditView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||
table = tables.VirtualCircuitTerminationTable
|
||||
form = forms.VirtualCircuitTerminationBulkEditForm
|
||||
|
||||
|
||||
class VirtualCircuitTerminationBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = VirtualCircuitTermination.objects.all()
|
||||
filterset = filtersets.VirtualCircuitTerminationFilterSet
|
||||
table = tables.VirtualCircuitTerminationTable
|
||||
|
@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
|
||||
from circuits.models import CircuitTermination
|
||||
from circuits.models import CircuitTermination, VirtualCircuit, VirtualCircuitTermination
|
||||
from extras.filtersets import LocalConfigContextFilterSet
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.filtersets import PrimaryIPFilterSet
|
||||
@ -1747,6 +1747,16 @@ class InterfaceFilterSet(
|
||||
queryset=WirelessLink.objects.all(),
|
||||
label=_('Wireless link')
|
||||
)
|
||||
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit_termination__virtual_circuit',
|
||||
queryset=VirtualCircuit.objects.all(),
|
||||
label=_('Virtual circuit (ID)'),
|
||||
)
|
||||
virtual_circuit_termination_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_circuit_termination',
|
||||
queryset=VirtualCircuitTermination.objects.all(),
|
||||
label=_('Virtual circuit termination (ID)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
|
@ -1167,6 +1167,8 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
'tunnelgroup',
|
||||
'tunneltermination',
|
||||
'virtualchassis',
|
||||
'virtualcircuit',
|
||||
'virtualcircuittermination',
|
||||
'virtualdevicecontext',
|
||||
'virtualdisk',
|
||||
'virtualmachine',
|
||||
|
@ -278,6 +278,13 @@ CIRCUITS_MENU = Menu(
|
||||
get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
label=_('Virtual Circuits'),
|
||||
items=(
|
||||
get_model_item('circuits', 'virtualcircuit', _('Virtual Circuits')),
|
||||
get_model_item('circuits', 'virtualcircuittermination', _('Virtual Circuit Terminations')),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
label=_('Providers'),
|
||||
items=(
|
||||
|
@ -50,6 +50,19 @@
|
||||
<h2 class="card-header">{% trans "Circuits" %}</h2>
|
||||
{% htmx_table 'circuits:circuit_list' provider_network_id=object.pk %}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2 class="card-header">
|
||||
{% trans "Virtual Circuits" %}
|
||||
{% if perms.circuits.add_virtualcircuit %}
|
||||
<div class="card-actions">
|
||||
<a href="{% url 'circuits:virtualcircuit_add' %}?provider_network={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Add a Virtual Circuit" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% htmx_table 'circuits:virtualcircuit_list' provider_network_id=object.pk %}
|
||||
</div>
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
|
84
netbox/templates/circuits/virtualcircuit.html
Normal file
84
netbox/templates/circuits/virtualcircuit.html
Normal file
@ -0,0 +1,84 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_id={{ object.provider.pk }}">{{ object.provider }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_network_id={{ object.provider_network.pk }}">{{ object.provider_network }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Virtual circuit" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider" %}</th>
|
||||
<td>{{ object.provider|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider Network" %}</th>
|
||||
<td>{{ object.provider_network|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider account" %}</th>
|
||||
<td>{{ object.provider_account|linkify|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Circuit ID" %}</th>
|
||||
<td>{{ object.cid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Status" %}</th>
|
||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Tenant" %}</th>
|
||||
<td>
|
||||
{% if object.tenant.group %}
|
||||
{{ object.tenant.group|linkify }} /
|
||||
{% endif %}
|
||||
{{ object.tenant|linkify|placeholder }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Description" %}</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div class="card">
|
||||
<h2 class="card-header">
|
||||
{% trans "Terminations" %}
|
||||
{% if perms.circuits.add_virtualcircuittermination %}
|
||||
<div class="card-actions">
|
||||
<a href="{% url 'circuits:virtualcircuittermination_add' %}?virtual_circuit={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-ghost-primary btn-sm">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Termination" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% htmx_table 'circuits:virtualcircuittermination_list' virtual_circuit_id=object.pk %}
|
||||
</div>
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
59
netbox/templates/circuits/virtualcircuittermination.html
Normal file
59
netbox/templates/circuits/virtualcircuittermination.html
Normal file
@ -0,0 +1,59 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load helpers %}
|
||||
{% load plugins %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_id={{ object.virtual_circuit.provider.pk }}">{{ object.virtual_circuit.provider }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'circuits:virtualcircuit_list' %}?provider_network_id={{ object.virtual_circuit.provider_network.pk }}">{{ object.virtual_circuit.provider_network }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'circuits:virtualcircuittermination_list' %}?virtual_circuit_id={{ object.virtual_circuit.pk }}">{{ object.virtual_circuit }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider" %}</th>
|
||||
<td>{{ object.virtual_circuit.provider|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider Network" %}</th>
|
||||
<td>{{ object.virtual_circuit.provider_network|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Provider account" %}</th>
|
||||
<td>{{ object.virtual_circuit.provider_account|linkify|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Virtual circuit" %}</th>
|
||||
<td>{{ object.virtual_circuit|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Role" %}</th>
|
||||
<td>{% badge object.get_role_display bg_color=object.get_role_color %}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% plugin_right_page object %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user