Skip to content

Commit

Permalink
7025 circuit redundancy (netbox-community#16945)
Browse files Browse the repository at this point in the history
* 7025 CircuitRedundancyGroups

* 7025 CircuitRedundancyGroups api

* 7025 CircuitRedundancyGroups api

* 7025 CircuitRedundancyGroups tests

* 7025 CircuitRedundancyGroup -> CircuitGroup

* 7025 add tenancy

* 7025 linkify name

* 7025 missing file

* 7025 circuitgroupassignment

* 7025 base group assignment working

* 7025 assignments

* 7025 fix forms/tests for CircuitGroup

* 7025 fix api tests

* 7025 view tests

* 7025 CircuitGroupAssignment tests

* 7025 fix typo

* 7025 fix typo

* 7025 fix tests

* 7025 remove m2m

* 7025 add count to serializer

* 7025 fix test

* 7025 documentation

* 7025 review comments

* 7025 review comments

* 7025 add search index

* Make CircuitPriorityChoices extensible

* Add group assignment table to circuit view

* Misc cleanup

---------

Co-authored-by: Jeremy Stretch <[email protected]>
  • Loading branch information
arthanson and jeremystretch authored Jul 24, 2024
1 parent f7fdfdd commit 8237c6a
Show file tree
Hide file tree
Showing 29 changed files with 1,124 additions and 7 deletions.
13 changes: 13 additions & 0 deletions docs/models/circuits/circuitgroup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Circuit Groups

[Circuits](./circuit.md) can be arranged into administrative groups for organization. The assignment of a circuit to a group is optional.

## Fields

### Name

A unique human-friendly name.

### Slug

A unique URL-friendly identifier. (This value can be used for filtering.)
25 changes: 25 additions & 0 deletions docs/models/circuits/circuitgroupassignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Circuit Group Assignments

Circuits can be assigned to [circuit groups](./circuitgroup.md) for correlation purposes. For instance, three circuits, each belonging to a different provider, may each be assigned to the same circuit group. Each assignment may optionally include a priority designation.

## Fields

### Group

The [circuit group](./circuitgroup.md) being assigned.

### Circuit

The [circuit](./circuit.md) that is being assigned to the group.

### Priority

The circuit's operation priority relative to its peers within the group. The assignment of a priority is optional. Choices include:

* Primary
* Secondary
* Tertiary
* Inactive

!!! tip
Additional priority choices may be defined by setting `CircuitGroupAssignment.priority` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ nav:
- Data Model:
- Circuits:
- Circuit: 'models/circuits/circuit.md'
- CircuitGroup: 'models/circuits/circuitgroup.md'
- CircuitGroupAssignment: 'models/circuits/circuitgroupassignment.md'
- Circuit Termination: 'models/circuits/circuittermination.md'
- Circuit Type: 'models/circuits/circuittype.md'
- Provider: 'models/circuits/provider.md'
Expand Down
48 changes: 45 additions & 3 deletions netbox/circuits/api/serializers_/circuits.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers

from circuits.choices import CircuitStatusChoices
from circuits.models import Circuit, CircuitTermination, CircuitType
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
from dcim.api.serializers_.cables import CabledObjectSerializer
from dcim.api.serializers_.sites import SiteSerializer
from netbox.api.fields import ChoiceField, RelatedObjectCountField
Expand All @@ -12,6 +12,8 @@

__all__ = (
'CircuitSerializer',
'CircuitGroupAssignmentSerializer',
'CircuitGroupSerializer',
'CircuitTerminationSerializer',
'CircuitTypeSerializer',
)
Expand Down Expand Up @@ -43,6 +45,34 @@ class Meta:
]


class CircuitGroupSerializer(NetBoxModelSerializer):
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
circuit_count = RelatedObjectCountField('assignments')

class Meta:
model = CircuitGroup
fields = [
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tenant',
'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count'
]
brief_fields = ('id', 'url', 'display', 'name')


class CircuitGroupAssignmentSerializer_(NetBoxModelSerializer):
"""
Base serializer for group assignments under CircuitSerializer.
"""
group = CircuitGroupSerializer(nested=True)
priority = ChoiceField(choices=CircuitPriorityChoices, allow_blank=True, required=False)

class Meta:
model = CircuitGroupAssignment
fields = [
'id', 'url', 'display_url', 'display', 'group', 'priority', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'group', 'priority')


class CircuitSerializer(NetBoxModelSerializer):
provider = ProviderSerializer(nested=True)
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
Expand All @@ -51,13 +81,14 @@ class CircuitSerializer(NetBoxModelSerializer):
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
termination_z = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
assignments = CircuitGroupAssignmentSerializer_(nested=True, many=True, required=False)

class Meta:
model = Circuit
fields = [
'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant',
'install_date', 'termination_date', 'commit_rate', 'description', 'termination_a', 'termination_z',
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'assignments',
]
brief_fields = ('id', 'url', 'display', 'cid', 'description')

Expand All @@ -75,3 +106,14 @@ class Meta:
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
]
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied')


class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
circuit = CircuitSerializer(nested=True)

class Meta:
model = CircuitGroupAssignment
fields = [
'id', 'url', 'display_url', 'display', 'group', 'circuit', 'priority', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'group', 'circuit', 'priority')
2 changes: 2 additions & 0 deletions netbox/circuits/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
router.register('circuit-types', views.CircuitTypeViewSet)
router.register('circuits', views.CircuitViewSet)
router.register('circuit-terminations', views.CircuitTerminationViewSet)
router.register('circuit-groups', views.CircuitGroupViewSet)
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)

app_name = 'circuits-api'
urlpatterns = router.urls
20 changes: 20 additions & 0 deletions netbox/circuits/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
filterset_class = filtersets.CircuitTerminationFilterSet


#
# Circuit Groups
#

class CircuitGroupViewSet(NetBoxModelViewSet):
queryset = CircuitGroup.objects.all()
serializer_class = serializers.CircuitGroupSerializer
filterset_class = filtersets.CircuitGroupFilterSet


#
# Circuit Group Assignments
#

class CircuitGroupAssignmentViewSet(NetBoxModelViewSet):
queryset = CircuitGroupAssignment.objects.all()
serializer_class = serializers.CircuitGroupAssignmentSerializer
filterset_class = filtersets.CircuitGroupAssignmentFilterSet


#
# Provider accounts
#
Expand Down
16 changes: 16 additions & 0 deletions netbox/circuits/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,19 @@ class CircuitTerminationPortSpeedChoices(ChoiceSet):
(1544, 'T1 (1.544 Mbps)'),
(2048, 'E1 (2.048 Mbps)'),
]


class CircuitPriorityChoices(ChoiceSet):
key = 'CircuitGroupAssignment.priority'

PRIORITY_PRIMARY = 'primary'
PRIORITY_SECONDARY = 'secondary'
PRIORITY_TERTIARY = 'tertiary'
PRIORITY_INACTIVE = 'inactive'

CHOICES = [
(PRIORITY_PRIMARY, _('Primary')),
(PRIORITY_SECONDARY, _('Secondary')),
(PRIORITY_TERTIARY, _('Tertiary')),
(PRIORITY_INACTIVE, _('Inactive')),
]
42 changes: 42 additions & 0 deletions netbox/circuits/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

__all__ = (
'CircuitFilterSet',
'CircuitGroupAssignmentFilterSet',
'CircuitGroupFilterSet',
'CircuitTerminationFilterSet',
'CircuitTypeFilterSet',
'ProviderNetworkFilterSet',
Expand Down Expand Up @@ -303,3 +305,43 @@ def search(self, queryset, name, value):
Q(pp_info__icontains=value) |
Q(description__icontains=value)
).distinct()


class CircuitGroupFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):

class Meta:
model = CircuitGroup
fields = ('id', 'name', 'slug', 'description')


class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
)
circuit_id = django_filters.ModelMultipleChoiceFilter(
queryset=Circuit.objects.all(),
label=_('Circuit'),
)
group_id = django_filters.ModelMultipleChoiceFilter(
queryset=CircuitGroup.objects.all(),
label=_('Circuit group (ID)'),
)
group = django_filters.ModelMultipleChoiceFilter(
field_name='group__slug',
queryset=CircuitGroup.objects.all(),
to_field_name='slug',
label=_('Circuit group (slug)'),
)

class Meta:
model = CircuitGroupAssignment
fields = ('id', 'circuit', 'group', 'priority')

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(circuit__cid__icontains=value) |
Q(group__name__icontains=value)
)
41 changes: 40 additions & 1 deletion netbox/circuits/forms/bulk_edit.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django import forms
from django.utils.translation import gettext_lazy as _

from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices
from circuits.models import *
from dcim.models import Site
from ipam.models import ASN
Expand All @@ -14,6 +14,8 @@

__all__ = (
'CircuitBulkEditForm',
'CircuitGroupAssignmentBulkEditForm',
'CircuitGroupBulkEditForm',
'CircuitTerminationBulkEditForm',
'CircuitTypeBulkEditForm',
'ProviderBulkEditForm',
Expand Down Expand Up @@ -219,3 +221,40 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')),
)
nullable_fields = ('description')


class CircuitGroupBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)

model = CircuitGroup
nullable_fields = (
'description', 'tenant',
)


class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
circuit = DynamicModelChoiceField(
label=_('Circuit'),
queryset=Circuit.objects.all(),
required=False
)
priority = forms.ChoiceField(
label=_('Priority'),
choices=add_blank_choice(CircuitPriorityChoices),
required=False
)

model = CircuitGroupAssignment
fieldsets = (
FieldSet('circuit', 'priority'),
)
nullable_fields = ('priority',)
23 changes: 23 additions & 0 deletions netbox/circuits/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

__all__ = (
'CircuitImportForm',
'CircuitGroupAssignmentImportForm',
'CircuitGroupImportForm',
'CircuitTerminationImportForm',
'CircuitTerminationImportRelatedForm',
'CircuitTypeImportForm',
Expand Down Expand Up @@ -150,3 +152,24 @@ class Meta:
'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info', 'description', 'tags'
]


class CircuitGroupImportForm(NetBoxModelImportForm):
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned tenant')
)

class Meta:
model = CircuitGroup
fields = ('name', 'slug', 'description', 'tenant', 'tags')


class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):

class Meta:
model = CircuitGroupAssignment
fields = ('circuit', 'group', 'priority')
37 changes: 36 additions & 1 deletion netbox/circuits/forms/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django import forms
from django.utils.translation import gettext as _

from circuits.choices import CircuitCommitRateChoices, CircuitStatusChoices, CircuitTerminationSideChoices
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices
from circuits.models import *
from dcim.models import Region, Site, SiteGroup
from ipam.models import ASN
Expand All @@ -13,6 +13,8 @@

__all__ = (
'CircuitFilterForm',
'CircuitGroupAssignmentFilterForm',
'CircuitGroupFilterForm',
'CircuitTerminationFilterForm',
'CircuitTypeFilterForm',
'ProviderFilterForm',
Expand Down Expand Up @@ -230,3 +232,36 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm):
label=_('Provider')
)
tag = TagFilterField(model)


class CircuitGroupFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = CircuitGroup
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
)
tag = TagFilterField(model)


class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
model = CircuitGroupAssignment
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('circuit_id', 'group_id', 'priority', name=_('Assignment')),
)
circuit_id = DynamicModelMultipleChoiceField(
queryset=Circuit.objects.all(),
required=False,
label=_('Circuit')
)
group_id = DynamicModelMultipleChoiceField(
queryset=CircuitGroup.objects.all(),
required=False,
label=_('Group')
)
priority = forms.MultipleChoiceField(
label=_('Priority'),
choices=CircuitPriorityChoices,
required=False
)
tag = TagFilterField(model)
Loading

0 comments on commit 8237c6a

Please sign in to comment.