diff --git a/netbox/circuits/choices.py b/netbox/circuits/choices.py index e2d345581..7aba50dba 100644 --- a/netbox/circuits/choices.py +++ b/netbox/circuits/choices.py @@ -76,3 +76,17 @@ class CircuitTerminationPortSpeedChoices(ChoiceSet): (1544, 'T1 (1.544 Mbps)'), (2048, 'E1 (2.048 Mbps)'), ] + + +class CircuitPriorityChoices(ChoiceSet): + 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')), + ) diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index e52673874..dffbaa41d 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -13,6 +13,7 @@ from .models import * __all__ = ( 'CircuitFilterSet', + 'CircuitRedundancyGroupFilterSet', 'CircuitTerminationFilterSet', 'CircuitTypeFilterSet', 'ProviderNetworkFilterSet', @@ -303,3 +304,18 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet): Q(pp_info__icontains=value) | Q(description__icontains=value) ).distinct() + + +class CircuitRedundancyGroupFilterSet(NetBoxModelFilterSet): + + class Meta: + model = CircuitRedundancyGroup + fields = ('id', 'name',) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(comments__icontains=value) + ).distinct() diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index 88fdd2c71..35e2e073e 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/netbox/circuits/forms/bulk_import.py @@ -11,6 +11,7 @@ from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugFiel __all__ = ( 'CircuitImportForm', + 'CircuitRedundancyGroupImportForm', 'CircuitTerminationImportForm', 'CircuitTerminationImportRelatedForm', 'CircuitTypeImportForm', @@ -150,3 +151,10 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination 'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags' ] + + +class CircuitRedundancyGroupImportForm(NetBoxModelImportForm): + + class Meta: + model = CircuitRedundancyGroup + fields = ('name', 'tags') diff --git a/netbox/circuits/forms/filtersets.py b/netbox/circuits/forms/filtersets.py index 6f6473c3d..6c7ef8234 100644 --- a/netbox/circuits/forms/filtersets.py +++ b/netbox/circuits/forms/filtersets.py @@ -13,6 +13,7 @@ from utilities.forms.widgets import DatePicker, NumberWithOptions __all__ = ( 'CircuitFilterForm', + 'CircuitRedundancyGroupFilterForm', 'CircuitTerminationFilterForm', 'CircuitTypeFilterForm', 'ProviderFilterForm', @@ -230,3 +231,12 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm): label=_('Provider') ) tag = TagFilterField(model) + + +class CircuitRedundancyGroupFilterForm(NetBoxModelFilterSetForm): + model = CircuitRedundancyGroup + fieldsets = ( + FieldSet('q', 'filter_id', 'tag'), + ) + selector_fields = ('filter_id', 'q', ) + tag = TagFilterField(model) diff --git a/netbox/circuits/forms/model_forms.py b/netbox/circuits/forms/model_forms.py index ee5e47ce7..5ee4e49a6 100644 --- a/netbox/circuits/forms/model_forms.py +++ b/netbox/circuits/forms/model_forms.py @@ -12,6 +12,7 @@ from utilities.forms.widgets import DatePicker, NumberWithOptions __all__ = ( 'CircuitForm', + 'CircuitRedundancyGroupForm', 'CircuitTerminationForm', 'CircuitTypeForm', 'ProviderForm', @@ -171,3 +172,14 @@ class CircuitTerminationForm(NetBoxModelForm): options=CircuitTerminationPortSpeedChoices ), } + + +class CircuitRedundancyGroupForm(TenancyForm, NetBoxModelForm): + comments = CommentField() + + class Meta: + model = CircuitRedundancyGroup + fields = [ + 'name', 'tenant_group', 'tenant', + 'comments', 'tags', + ] diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index fa21d7cd3..da86615c0 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -11,6 +11,8 @@ from utilities.fields import ColorField __all__ = ( 'Circuit', + 'CircuitGroupAssignment', + 'CircuitRedundancyGroup', 'CircuitTermination', 'CircuitType', ) @@ -151,6 +153,39 @@ class Circuit(ContactsMixin, ImageAttachmentsMixin, PrimaryModel): raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."}) +class CircuitRedundancyGroup(PrimaryModel): + """ + """ + name = models.CharField( + verbose_name=_('name'), + max_length=100 + ) + slug = models.SlugField( + verbose_name=_('slug'), + max_length=100 + ) + circuits = models.ManyToManyField(Circuit, through='CircuitGroupAssignment') + + class Meta: + ordering = ('name', 'pk') # Name may be non-unique + verbose_name = _('Circuit redundancy group') + verbose_name_plural = _('Circuit redundancy group') + + def get_absolute_url(self): + return reverse('circuits:circuitredundancygroup', args=[self.pk]) + + +class CircuitGroupAssignment(models.Model): + circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE) + group = models.ForeignKey(CircuitRedundancyGroup, on_delete=models.CASCADE) + priority = models.CharField( + verbose_name=_('priority'), + max_length=50, + choices=CircuitPriorityChoices, + blank=True + ) + + class CircuitTermination( CustomFieldsMixin, CustomLinksMixin, diff --git a/netbox/circuits/tables/circuits.py b/netbox/circuits/tables/circuits.py index e1b99ff42..6ca85910e 100644 --- a/netbox/circuits/tables/circuits.py +++ b/netbox/circuits/tables/circuits.py @@ -9,6 +9,7 @@ from netbox.tables import NetBoxTable, columns from .columns import CommitRateColumn __all__ = ( + 'CircuitRedundancyGroupTable', 'CircuitTable', 'CircuitTerminationTable', 'CircuitTypeTable', @@ -119,3 +120,13 @@ class CircuitTerminationTable(NetBoxTable): 'xconnect_id', 'pp_info', 'description', 'created', 'last_updated', 'actions', ) default_columns = ('pk', 'id', 'circuit', 'provider', 'term_side', 'description') + + +class CircuitRedundancyGroupTable(NetBoxTable): + + class Meta(NetBoxTable.Meta): + model = CircuitRedundancyGroup + fields = ( + 'pk', 'name', 'created', 'last_updated', 'actions', + ) + default_columns = ('pk', 'name',) diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index 5c0ab99ee..1393cb984 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -55,4 +55,11 @@ urlpatterns = [ path('circuit-terminations/delete/', views.CircuitTerminationBulkDeleteView.as_view(), name='circuittermination_bulk_delete'), path('circuit-terminations//', include(get_model_urls('circuits', 'circuittermination'))), + # Circuit Redundacy Groups + path('circuit-redundancy-groups/', views.CircuitRedundancyGroupListView.as_view(), name='circuitredundancygroup_list'), + path('circuit-redundancy-groups/add/', views.CircuitRedundancyGroupEditView.as_view(), name='circuitredundancygroup_add'), + path('circuit-redundancy-groups/import/', views.CircuitRedundancyGroupBulkImportView.as_view(), name='circuitredundancygroup_import'), + # path('circuit-redundancy-groups/edit/', views.CircuitRedundancyGroupBulkEditView.as_view(), name='circuitredundancygroup_bulk_edit'), + path('circuit-redundancy-groups/delete/', views.CircuitRedundancyGroupBulkDeleteView.as_view(), name='circuitredundancygroup_bulk_delete'), + path('circuit-redundancy-groups//', include(get_model_urls('circuits', 'circuitredundancygroup'))), ] diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index b10b83b23..48c13f7c2 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -440,3 +440,41 @@ class CircuitTerminationBulkDeleteView(generic.BulkDeleteView): # Trace view register_model_view(CircuitTermination, 'trace', kwargs={'model': CircuitTermination})(PathTraceView) + + +# +# Circuit Redundacy Groups +# + +class CircuitRedundancyGroupListView(generic.ObjectListView): + queryset = CircuitRedundancyGroup.objects.all() + filterset = filtersets.CircuitRedundancyGroupFilterSet + filterset_form = forms.CircuitRedundancyGroupFilterForm + table = tables.CircuitRedundancyGroupTable + + +@register_model_view(CircuitRedundancyGroup) +class CircuitRedundancyGroupView(generic.ObjectView): + queryset = CircuitRedundancyGroup.objects.all() + + +@register_model_view(CircuitRedundancyGroup, 'edit') +class CircuitRedundancyGroupEditView(generic.ObjectEditView): + queryset = CircuitRedundancyGroup.objects.all() + form = forms.CircuitRedundancyGroupForm + + +@register_model_view(CircuitRedundancyGroup, 'delete') +class CircuitRedundancyGroupDeleteView(generic.ObjectDeleteView): + queryset = CircuitRedundancyGroup.objects.all() + + +class CircuitRedundancyGroupBulkImportView(generic.BulkImportView): + queryset = CircuitRedundancyGroup.objects.all() + model_form = forms.CircuitRedundancyGroupImportForm + + +class CircuitRedundancyGroupBulkDeleteView(generic.BulkDeleteView): + queryset = CircuitRedundancyGroup.objects.all() + filterset = filtersets.CircuitRedundancyGroupFilterSet + table = tables.CircuitRedundancyGroupTable diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index d9edab36b..8ec51405b 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -259,6 +259,7 @@ CIRCUITS_MENU = Menu( get_model_item('circuits', 'circuit', _('Circuits')), get_model_item('circuits', 'circuittype', _('Circuit Types')), get_model_item('circuits', 'circuittermination', _('Circuit Terminations')), + get_model_item('circuits', 'circuitredundancygroup', _('Circuit Redundancy Groups')), ), ), MenuGroup(