mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
7025 circuit redundancy (#16945)
* 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 <jstretch@netboxlabs.com>
This commit is contained in:
parent
f7fdfdd925
commit
8237c6accc
13
docs/models/circuits/circuitgroup.md
Normal file
13
docs/models/circuits/circuitgroup.md
Normal file
@ -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
docs/models/circuits/circuitgroupassignment.md
Normal file
25
docs/models/circuits/circuitgroupassignment.md
Normal file
@ -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.
|
@ -164,6 +164,8 @@ nav:
|
|||||||
- Data Model:
|
- Data Model:
|
||||||
- Circuits:
|
- Circuits:
|
||||||
- Circuit: 'models/circuits/circuit.md'
|
- Circuit: 'models/circuits/circuit.md'
|
||||||
|
- CircuitGroup: 'models/circuits/circuitgroup.md'
|
||||||
|
- CircuitGroupAssignment: 'models/circuits/circuitgroupassignment.md'
|
||||||
- Circuit Termination: 'models/circuits/circuittermination.md'
|
- Circuit Termination: 'models/circuits/circuittermination.md'
|
||||||
- Circuit Type: 'models/circuits/circuittype.md'
|
- Circuit Type: 'models/circuits/circuittype.md'
|
||||||
- Provider: 'models/circuits/provider.md'
|
- Provider: 'models/circuits/provider.md'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from circuits.choices import CircuitStatusChoices
|
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType
|
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
|
||||||
from dcim.api.serializers_.cables import CabledObjectSerializer
|
from dcim.api.serializers_.cables import CabledObjectSerializer
|
||||||
from dcim.api.serializers_.sites import SiteSerializer
|
from dcim.api.serializers_.sites import SiteSerializer
|
||||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
||||||
@ -12,6 +12,8 @@ from .providers import ProviderAccountSerializer, ProviderNetworkSerializer, Pro
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitSerializer',
|
'CircuitSerializer',
|
||||||
|
'CircuitGroupAssignmentSerializer',
|
||||||
|
'CircuitGroupSerializer',
|
||||||
'CircuitTerminationSerializer',
|
'CircuitTerminationSerializer',
|
||||||
'CircuitTypeSerializer',
|
'CircuitTypeSerializer',
|
||||||
)
|
)
|
||||||
@ -43,6 +45,34 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
class CircuitSerializer(NetBoxModelSerializer):
|
||||||
provider = ProviderSerializer(nested=True)
|
provider = ProviderSerializer(nested=True)
|
||||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||||
@ -51,13 +81,14 @@ class CircuitSerializer(NetBoxModelSerializer):
|
|||||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||||
termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
|
termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True)
|
||||||
termination_z = 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:
|
class Meta:
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant',
|
'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant',
|
||||||
'install_date', 'termination_date', 'commit_rate', 'description', 'termination_a', 'termination_z',
|
'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')
|
brief_fields = ('id', 'url', 'display', 'cid', 'description')
|
||||||
|
|
||||||
@ -75,3 +106,14 @@ class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer
|
|||||||
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_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')
|
||||||
|
@ -14,6 +14,8 @@ router.register('provider-networks', views.ProviderNetworkViewSet)
|
|||||||
router.register('circuit-types', views.CircuitTypeViewSet)
|
router.register('circuit-types', views.CircuitTypeViewSet)
|
||||||
router.register('circuits', views.CircuitViewSet)
|
router.register('circuits', views.CircuitViewSet)
|
||||||
router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
router.register('circuit-terminations', views.CircuitTerminationViewSet)
|
||||||
|
router.register('circuit-groups', views.CircuitGroupViewSet)
|
||||||
|
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)
|
||||||
|
|
||||||
app_name = 'circuits-api'
|
app_name = 'circuits-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -55,6 +55,26 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
|
|||||||
filterset_class = filtersets.CircuitTerminationFilterSet
|
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
|
# Provider accounts
|
||||||
#
|
#
|
||||||
|
@ -76,3 +76,19 @@ class CircuitTerminationPortSpeedChoices(ChoiceSet):
|
|||||||
(1544, 'T1 (1.544 Mbps)'),
|
(1544, 'T1 (1.544 Mbps)'),
|
||||||
(2048, 'E1 (2.048 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')),
|
||||||
|
]
|
||||||
|
@ -13,6 +13,8 @@ from .models import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitFilterSet',
|
'CircuitFilterSet',
|
||||||
|
'CircuitGroupAssignmentFilterSet',
|
||||||
|
'CircuitGroupFilterSet',
|
||||||
'CircuitTerminationFilterSet',
|
'CircuitTerminationFilterSet',
|
||||||
'CircuitTypeFilterSet',
|
'CircuitTypeFilterSet',
|
||||||
'ProviderNetworkFilterSet',
|
'ProviderNetworkFilterSet',
|
||||||
@ -303,3 +305,43 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
|
|||||||
Q(pp_info__icontains=value) |
|
Q(pp_info__icontains=value) |
|
||||||
Q(description__icontains=value)
|
Q(description__icontains=value)
|
||||||
).distinct()
|
).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)
|
||||||
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
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 circuits.models import *
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam.models import ASN
|
from ipam.models import ASN
|
||||||
@ -14,6 +14,8 @@ from utilities.forms.widgets import BulkEditNullBooleanSelect, DatePicker, Numbe
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitBulkEditForm',
|
'CircuitBulkEditForm',
|
||||||
|
'CircuitGroupAssignmentBulkEditForm',
|
||||||
|
'CircuitGroupBulkEditForm',
|
||||||
'CircuitTerminationBulkEditForm',
|
'CircuitTerminationBulkEditForm',
|
||||||
'CircuitTypeBulkEditForm',
|
'CircuitTypeBulkEditForm',
|
||||||
'ProviderBulkEditForm',
|
'ProviderBulkEditForm',
|
||||||
@ -219,3 +221,40 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')),
|
FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description')
|
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',)
|
||||||
|
@ -11,6 +11,8 @@ from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugFiel
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitImportForm',
|
'CircuitImportForm',
|
||||||
|
'CircuitGroupAssignmentImportForm',
|
||||||
|
'CircuitGroupImportForm',
|
||||||
'CircuitTerminationImportForm',
|
'CircuitTerminationImportForm',
|
||||||
'CircuitTerminationImportRelatedForm',
|
'CircuitTerminationImportRelatedForm',
|
||||||
'CircuitTypeImportForm',
|
'CircuitTypeImportForm',
|
||||||
@ -150,3 +152,24 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination
|
|||||||
'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id',
|
'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id',
|
||||||
'pp_info', 'description', 'tags'
|
'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')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
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 circuits.models import *
|
||||||
from dcim.models import Region, Site, SiteGroup
|
from dcim.models import Region, Site, SiteGroup
|
||||||
from ipam.models import ASN
|
from ipam.models import ASN
|
||||||
@ -13,6 +13,8 @@ from utilities.forms.widgets import DatePicker, NumberWithOptions
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitFilterForm',
|
'CircuitFilterForm',
|
||||||
|
'CircuitGroupAssignmentFilterForm',
|
||||||
|
'CircuitGroupFilterForm',
|
||||||
'CircuitTerminationFilterForm',
|
'CircuitTerminationFilterForm',
|
||||||
'CircuitTypeFilterForm',
|
'CircuitTypeFilterForm',
|
||||||
'ProviderFilterForm',
|
'ProviderFilterForm',
|
||||||
@ -230,3 +232,36 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm):
|
|||||||
label=_('Provider')
|
label=_('Provider')
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
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)
|
||||||
|
@ -12,6 +12,8 @@ from utilities.forms.widgets import DatePicker, NumberWithOptions
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitForm',
|
'CircuitForm',
|
||||||
|
'CircuitGroupAssignmentForm',
|
||||||
|
'CircuitGroupForm',
|
||||||
'CircuitTerminationForm',
|
'CircuitTerminationForm',
|
||||||
'CircuitTypeForm',
|
'CircuitTypeForm',
|
||||||
'ProviderForm',
|
'ProviderForm',
|
||||||
@ -171,3 +173,35 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||||||
options=CircuitTerminationPortSpeedChoices
|
options=CircuitTerminationPortSpeedChoices
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupForm(TenancyForm, NetBoxModelForm):
|
||||||
|
slug = SlugField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('name', 'slug', 'description', 'tags', name=_('Circuit Group')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CircuitGroup
|
||||||
|
fields = [
|
||||||
|
'name', 'slug', 'description', 'tenant_group', 'tenant', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentForm(NetBoxModelForm):
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
label=_('Group'),
|
||||||
|
queryset=CircuitGroup.objects.all(),
|
||||||
|
)
|
||||||
|
circuit = DynamicModelChoiceField(
|
||||||
|
label=_('Circuit'),
|
||||||
|
queryset=Circuit.objects.all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CircuitGroupAssignment
|
||||||
|
fields = [
|
||||||
|
'group', 'circuit', 'priority', 'tags',
|
||||||
|
]
|
||||||
|
@ -7,6 +7,8 @@ from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitTerminationFilter',
|
'CircuitTerminationFilter',
|
||||||
'CircuitFilter',
|
'CircuitFilter',
|
||||||
|
'CircuitGroupAssignmentFilter',
|
||||||
|
'CircuitGroupFilter',
|
||||||
'CircuitTypeFilter',
|
'CircuitTypeFilter',
|
||||||
'ProviderFilter',
|
'ProviderFilter',
|
||||||
'ProviderAccountFilter',
|
'ProviderAccountFilter',
|
||||||
@ -32,6 +34,18 @@ class CircuitTypeFilter(BaseFilterMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter(models.CircuitGroup, lookups=True)
|
||||||
|
@autotype_decorator(filtersets.CircuitGroupFilterSet)
|
||||||
|
class CircuitGroupFilter(BaseFilterMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter(models.CircuitGroupAssignment, lookups=True)
|
||||||
|
@autotype_decorator(filtersets.CircuitGroupAssignmentFilterSet)
|
||||||
|
class CircuitGroupAssignmentFilter(BaseFilterMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.filter(models.Provider, lookups=True)
|
@strawberry_django.filter(models.Provider, lookups=True)
|
||||||
@autotype_decorator(filtersets.ProviderFilterSet)
|
@autotype_decorator(filtersets.ProviderFilterSet)
|
||||||
class ProviderFilter(BaseFilterMixin):
|
class ProviderFilter(BaseFilterMixin):
|
||||||
|
@ -24,6 +24,16 @@ class CircuitsQuery:
|
|||||||
return models.CircuitType.objects.get(pk=id)
|
return models.CircuitType.objects.get(pk=id)
|
||||||
circuit_type_list: List[CircuitTypeType] = strawberry_django.field()
|
circuit_type_list: List[CircuitTypeType] = strawberry_django.field()
|
||||||
|
|
||||||
|
@strawberry.field
|
||||||
|
def circuit_group(self, id: int) -> CircuitGroupType:
|
||||||
|
return models.CircuitGroup.objects.get(pk=id)
|
||||||
|
circuit_group_list: List[CircuitGroupType] = strawberry_django.field()
|
||||||
|
|
||||||
|
@strawberry.field
|
||||||
|
def circuit_group_assignment(self, id: int) -> CircuitGroupAssignmentType:
|
||||||
|
return models.CircuitGroupAssignment.objects.get(pk=id)
|
||||||
|
circuit_group_assignment_list: List[CircuitGroupAssignmentType] = strawberry_django.field()
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def provider(self, id: int) -> ProviderType:
|
def provider(self, id: int) -> ProviderType:
|
||||||
return models.Provider.objects.get(pk=id)
|
return models.Provider.objects.get(pk=id)
|
||||||
|
@ -6,13 +6,15 @@ import strawberry_django
|
|||||||
from circuits import models
|
from circuits import models
|
||||||
from dcim.graphql.mixins import CabledObjectMixin
|
from dcim.graphql.mixins import CabledObjectMixin
|
||||||
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||||
from netbox.graphql.types import NetBoxObjectType, ObjectType, OrganizationalObjectType
|
from netbox.graphql.types import BaseObjectType, NetBoxObjectType, ObjectType, OrganizationalObjectType
|
||||||
from tenancy.graphql.types import TenantType
|
from tenancy.graphql.types import TenantType
|
||||||
from .filters import *
|
from .filters import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CircuitTerminationType',
|
'CircuitTerminationType',
|
||||||
'CircuitType',
|
'CircuitType',
|
||||||
|
'CircuitGroupAssignmentType',
|
||||||
|
'CircuitGroupType',
|
||||||
'CircuitTypeType',
|
'CircuitTypeType',
|
||||||
'ProviderType',
|
'ProviderType',
|
||||||
'ProviderAccountType',
|
'ProviderAccountType',
|
||||||
@ -91,3 +93,22 @@ class CircuitType(NetBoxObjectType, ContactsMixin):
|
|||||||
tenant: TenantType | None
|
tenant: TenantType | None
|
||||||
|
|
||||||
terminations: List[CircuitTerminationType]
|
terminations: List[CircuitTerminationType]
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
models.CircuitGroup,
|
||||||
|
fields='__all__',
|
||||||
|
filters=CircuitGroupFilter
|
||||||
|
)
|
||||||
|
class CircuitGroupType(OrganizationalObjectType):
|
||||||
|
tenant: TenantType | None
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
models.CircuitGroupAssignment,
|
||||||
|
fields='__all__',
|
||||||
|
filters=CircuitGroupAssignmentFilter
|
||||||
|
)
|
||||||
|
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
|
||||||
|
group: Annotated["CircuitGroupType", strawberry.lazy('circuits.graphql.types')]
|
||||||
|
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
# Generated by Django 5.0.7 on 2024-07-22 06:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
import utilities.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('circuits', '0043_circuittype_color'),
|
||||||
|
('extras', '0118_notifications'),
|
||||||
|
('tenancy', '0015_contactassignment_rename_content_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CircuitGroup',
|
||||||
|
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)),
|
||||||
|
('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='circuit_groups',
|
||||||
|
to='tenancy.tenant',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Circuit group',
|
||||||
|
'verbose_name_plural': 'Circuit group',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CircuitGroupAssignment',
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
('priority', models.CharField(blank=True, max_length=50)),
|
||||||
|
(
|
||||||
|
'circuit',
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='assignments',
|
||||||
|
to='circuits.circuit',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'group',
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='assignments',
|
||||||
|
to='circuits.circuitgroup',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Circuit group assignment',
|
||||||
|
'verbose_name_plural': 'Circuit group assignments',
|
||||||
|
'ordering': ('circuit', 'priority', 'pk'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='circuitgroupassignment',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=('circuit', 'group'), name='circuits_circuitgroupassignment_unique_circuit_group'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -6,11 +6,13 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from dcim.models import CabledObjectModel
|
from dcim.models import CabledObjectModel
|
||||||
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
||||||
from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ImageAttachmentsMixin, TagsMixin
|
from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Circuit',
|
'Circuit',
|
||||||
|
'CircuitGroup',
|
||||||
|
'CircuitGroupAssignment',
|
||||||
'CircuitTermination',
|
'CircuitTermination',
|
||||||
'CircuitType',
|
'CircuitType',
|
||||||
)
|
)
|
||||||
@ -151,6 +153,75 @@ class Circuit(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
|
|||||||
raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."})
|
raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."})
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroup(OrganizationalModel):
|
||||||
|
"""
|
||||||
|
An administrative grouping of Circuits.
|
||||||
|
"""
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='circuit_groups',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
verbose_name = _('circuit group')
|
||||||
|
verbose_name_plural = _('circuit groups')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('circuits:circuitgroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
||||||
|
"""
|
||||||
|
Assignment of a Circuit to a CircuitGroup with an optional priority.
|
||||||
|
"""
|
||||||
|
circuit = models.ForeignKey(
|
||||||
|
Circuit,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='assignments'
|
||||||
|
)
|
||||||
|
group = models.ForeignKey(
|
||||||
|
CircuitGroup,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='assignments'
|
||||||
|
)
|
||||||
|
priority = models.CharField(
|
||||||
|
verbose_name=_('priority'),
|
||||||
|
max_length=50,
|
||||||
|
choices=CircuitPriorityChoices,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
prerequisite_models = (
|
||||||
|
'circuits.Circuit',
|
||||||
|
'circuits.CircuitGroup',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('circuit', 'priority', 'pk')
|
||||||
|
constraints = (
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=('circuit', 'group'),
|
||||||
|
name='%(app_label)s_%(class)s_unique_circuit_group'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
verbose_name = _('Circuit group assignment')
|
||||||
|
verbose_name_plural = _('Circuit group assignments')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.priority:
|
||||||
|
return f"{self.group} ({self.get_priority_display()})"
|
||||||
|
return str(self.group)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('circuits:circuitgroupassignment', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
class CircuitTermination(
|
class CircuitTermination(
|
||||||
CustomFieldsMixin,
|
CustomFieldsMixin,
|
||||||
CustomLinksMixin,
|
CustomLinksMixin,
|
||||||
|
@ -13,6 +13,17 @@ class CircuitIndex(SearchIndex):
|
|||||||
display_attrs = ('provider', 'provider_account', 'type', 'status', 'tenant', 'description')
|
display_attrs = ('provider', 'provider_account', 'type', 'status', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class CircuitGroupIndex(SearchIndex):
|
||||||
|
model = models.CircuitGroup
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 110),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
class CircuitTerminationIndex(SearchIndex):
|
class CircuitTerminationIndex(SearchIndex):
|
||||||
model = models.CircuitTermination
|
model = models.CircuitTermination
|
||||||
|
@ -9,6 +9,8 @@ from netbox.tables import NetBoxTable, columns
|
|||||||
from .columns import CommitRateColumn
|
from .columns import CommitRateColumn
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'CircuitGroupAssignmentTable',
|
||||||
|
'CircuitGroupTable',
|
||||||
'CircuitTable',
|
'CircuitTable',
|
||||||
'CircuitTerminationTable',
|
'CircuitTerminationTable',
|
||||||
'CircuitTypeTable',
|
'CircuitTypeTable',
|
||||||
@ -119,3 +121,50 @@ class CircuitTerminationTable(NetBoxTable):
|
|||||||
'xconnect_id', 'pp_info', 'description', 'created', 'last_updated', 'actions',
|
'xconnect_id', 'pp_info', 'description', 'created', 'last_updated', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'id', 'circuit', 'provider', 'term_side', 'description')
|
default_columns = ('pk', 'id', 'circuit', 'provider', 'term_side', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupTable(NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
circuit_group_assignment_count = columns.LinkedCountColumn(
|
||||||
|
viewname='circuits:circuitgroupassignment_list',
|
||||||
|
url_params={'group_id': 'pk'},
|
||||||
|
verbose_name=_('Circuits')
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='circuits:circuitgroup_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = CircuitGroup
|
||||||
|
fields = (
|
||||||
|
'pk', 'name', 'description', 'circuit_group_assignment_count', 'tags',
|
||||||
|
'created', 'last_updated', 'actions',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'name', 'description', 'circuit_group_assignment_count')
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentTable(NetBoxTable):
|
||||||
|
group = tables.Column(
|
||||||
|
verbose_name=_('Group'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
circuit = tables.Column(
|
||||||
|
verbose_name=_('Circuit'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
priority = tables.Column(
|
||||||
|
verbose_name=_('Priority'),
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='circuits:circuitgroupassignment_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = CircuitGroupAssignment
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'group', 'circuit', 'priority', 'created', 'last_updated', 'actions', 'tags',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'group', 'circuit', 'priority')
|
||||||
|
@ -206,6 +206,38 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = CircuitGroup
|
||||||
|
brief_fields = ['display', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
circuit_groups = (
|
||||||
|
CircuitGroup(name="Circuit Group 1", slug='circuit-group-1'),
|
||||||
|
CircuitGroup(name="Circuit Group 2", slug='circuit-group-2'),
|
||||||
|
CircuitGroup(name="Circuit Group 3", slug='circuit-group-3'),
|
||||||
|
)
|
||||||
|
CircuitGroup.objects.bulk_create(circuit_groups)
|
||||||
|
|
||||||
|
cls.create_data = [
|
||||||
|
{
|
||||||
|
'name': 'Circuit Group 4',
|
||||||
|
'slug': 'circuit-group-4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Circuit Group 5',
|
||||||
|
'slug': 'circuit-group-5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Circuit Group 6',
|
||||||
|
'slug': 'circuit-group-6',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
|
class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
brief_fields = ['account', 'description', 'display', 'id', 'name', 'url']
|
brief_fields = ['account', 'description', 'display', 'id', 'name', 'url']
|
||||||
@ -249,6 +281,77 @@ class ProviderAccountTest(APIViewTestCases.APIViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = CircuitGroupAssignment
|
||||||
|
brief_fields = ['circuit', 'display', 'group', 'id', 'priority', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
circuit_groups = (
|
||||||
|
CircuitGroup(name='Circuit Group 1', slug='circuit-group-1'),
|
||||||
|
CircuitGroup(name='Circuit Group 2', slug='circuit-group-2'),
|
||||||
|
CircuitGroup(name='Circuit Group 3', slug='circuit-group-3'),
|
||||||
|
CircuitGroup(name='Circuit Group 4', slug='circuit-group-4'),
|
||||||
|
CircuitGroup(name='Circuit Group 5', slug='circuit-group-5'),
|
||||||
|
CircuitGroup(name='Circuit Group 6', slug='circuit-group-6'),
|
||||||
|
)
|
||||||
|
CircuitGroup.objects.bulk_create(circuit_groups)
|
||||||
|
|
||||||
|
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||||
|
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||||
|
|
||||||
|
circuits = (
|
||||||
|
Circuit(cid='Circuit 1', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 2', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 3', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 4', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 5', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 6', provider=provider, type=circuittype),
|
||||||
|
)
|
||||||
|
Circuit.objects.bulk_create(circuits)
|
||||||
|
|
||||||
|
assignments = (
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[0],
|
||||||
|
circuit=circuits[0],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_PRIMARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[1],
|
||||||
|
circuit=circuits[1],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_SECONDARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[2],
|
||||||
|
circuit=circuits[2],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_TERTIARY
|
||||||
|
),
|
||||||
|
)
|
||||||
|
CircuitGroupAssignment.objects.bulk_create(assignments)
|
||||||
|
|
||||||
|
cls.create_data = [
|
||||||
|
{
|
||||||
|
'group': circuit_groups[3].pk,
|
||||||
|
'circuit': circuits[3].pk,
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_PRIMARY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'group': circuit_groups[4].pk,
|
||||||
|
'circuit': circuits[4].pk,
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_SECONDARY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'group': circuit_groups[5].pk,
|
||||||
|
'circuit': circuits[5].pk,
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_TERTIARY,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
|
class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||||
|
@ -451,6 +451,122 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7)
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
filterset = CircuitGroupFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
tenant_groups = (
|
||||||
|
TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
|
||||||
|
TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
|
||||||
|
TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
|
||||||
|
)
|
||||||
|
for tenantgroup in tenant_groups:
|
||||||
|
tenantgroup.save()
|
||||||
|
|
||||||
|
tenants = (
|
||||||
|
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
|
||||||
|
Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]),
|
||||||
|
Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]),
|
||||||
|
)
|
||||||
|
Tenant.objects.bulk_create(tenants)
|
||||||
|
|
||||||
|
CircuitGroup.objects.bulk_create((
|
||||||
|
CircuitGroup(name='Circuit Group 1', slug='circuit-group-1', description='foobar1', tenant=tenants[0]),
|
||||||
|
CircuitGroup(name='Circuit Group 2', slug='circuit-group-2', description='foobar2', tenant=tenants[1]),
|
||||||
|
CircuitGroup(name='Circuit Group 3', slug='circuit-group-3', tenant=tenants[1]),
|
||||||
|
))
|
||||||
|
|
||||||
|
def test_q(self):
|
||||||
|
params = {'q': 'foobar1'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
params = {'name': ['Circuit Group 1']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_slug(self):
|
||||||
|
params = {'slug': ['circuit-group-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)
|
||||||
|
|
||||||
|
def test_tenant(self):
|
||||||
|
tenants = Tenant.objects.all()[:2]
|
||||||
|
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'tenant': [tenants[0].slug, tenants[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
def test_tenant_group(self):
|
||||||
|
tenant_groups = TenantGroup.objects.all()[:2]
|
||||||
|
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
filterset = CircuitGroupAssignmentFilterSet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
circuit_groups = (
|
||||||
|
CircuitGroup(name='Circuit Group 1', slug='circuit-group-1'),
|
||||||
|
CircuitGroup(name='Circuit Group 2', slug='circuit-group-2'),
|
||||||
|
CircuitGroup(name='Circuit Group 3', slug='circuit-group-3'),
|
||||||
|
CircuitGroup(name='Circuit Group 4', slug='circuit-group-4'),
|
||||||
|
)
|
||||||
|
CircuitGroup.objects.bulk_create(circuit_groups)
|
||||||
|
|
||||||
|
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||||
|
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||||
|
|
||||||
|
circuits = (
|
||||||
|
Circuit(cid='Circuit 1', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 2', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 3', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 4', provider=provider, type=circuittype),
|
||||||
|
)
|
||||||
|
Circuit.objects.bulk_create(circuits)
|
||||||
|
|
||||||
|
assignments = (
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[0],
|
||||||
|
circuit=circuits[0],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_PRIMARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[1],
|
||||||
|
circuit=circuits[1],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_SECONDARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[2],
|
||||||
|
circuit=circuits[2],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_TERTIARY
|
||||||
|
),
|
||||||
|
)
|
||||||
|
CircuitGroupAssignment.objects.bulk_create(assignments)
|
||||||
|
|
||||||
|
def test_group_id(self):
|
||||||
|
groups = CircuitGroup.objects.filter(name__in=['Circuit Group 1', 'Circuit Group 2'])
|
||||||
|
params = {'group_id': [groups[0].pk, groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'group': [groups[0].slug, groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_circuit_id(self):
|
||||||
|
circuits = Circuit.objects.filter(cid__in=['Circuit 1', 'Circuit 2'])
|
||||||
|
params = {'circuit_id': [circuits[0].pk, circuits[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
filterset = ProviderNetworkFilterSet
|
filterset = ProviderNetworkFilterSet
|
||||||
|
@ -404,3 +404,109 @@ class CircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
response = self.client.get(reverse('circuits:circuittermination_trace', kwargs={'pk': circuittermination.pk}))
|
response = self.client.get(reverse('circuits:circuittermination_trace', kwargs={'pk': circuittermination.pk}))
|
||||||
self.assertHttpStatus(response, 200)
|
self.assertHttpStatus(response, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||||
|
model = CircuitGroup
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
circuit_groups = (
|
||||||
|
CircuitGroup(name='Circuit Group 1', slug='circuit-group-1'),
|
||||||
|
CircuitGroup(name='Circuit Group 2', slug='circuit-group-2'),
|
||||||
|
CircuitGroup(name='Circuit Group 3', slug='circuit-group-3'),
|
||||||
|
)
|
||||||
|
CircuitGroup.objects.bulk_create(circuit_groups)
|
||||||
|
|
||||||
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'name': 'Circuit Group X',
|
||||||
|
'slug': 'circuit-group-x',
|
||||||
|
'description': 'A new Circuit Group',
|
||||||
|
'tags': [t.pk for t in tags],
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.csv_data = (
|
||||||
|
"name,slug",
|
||||||
|
"Circuit Group 4,circuit-group-4",
|
||||||
|
"Circuit Group 5,circuit-group-5",
|
||||||
|
"Circuit Group 6,circuit-group-6",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.csv_update_data = (
|
||||||
|
"id,name,description",
|
||||||
|
f"{circuit_groups[0].pk},Circuit Group 7,New description7",
|
||||||
|
f"{circuit_groups[1].pk},Circuit Group 8,New description8",
|
||||||
|
f"{circuit_groups[2].pk},Circuit Group 9,New description9",
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'description': 'Foo',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentTestCase(
|
||||||
|
ViewTestCases.CreateObjectViewTestCase,
|
||||||
|
ViewTestCases.EditObjectViewTestCase,
|
||||||
|
ViewTestCases.DeleteObjectViewTestCase,
|
||||||
|
ViewTestCases.ListObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkEditObjectsViewTestCase,
|
||||||
|
ViewTestCases.BulkDeleteObjectsViewTestCase
|
||||||
|
):
|
||||||
|
model = CircuitGroupAssignment
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
circuit_groups = (
|
||||||
|
CircuitGroup(name='Circuit Group 1', slug='circuit-group-1'),
|
||||||
|
CircuitGroup(name='Circuit Group 2', slug='circuit-group-2'),
|
||||||
|
CircuitGroup(name='Circuit Group 3', slug='circuit-group-3'),
|
||||||
|
CircuitGroup(name='Circuit Group 4', slug='circuit-group-4'),
|
||||||
|
)
|
||||||
|
CircuitGroup.objects.bulk_create(circuit_groups)
|
||||||
|
|
||||||
|
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
|
||||||
|
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||||
|
|
||||||
|
circuits = (
|
||||||
|
Circuit(cid='Circuit 1', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 2', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 3', provider=provider, type=circuittype),
|
||||||
|
Circuit(cid='Circuit 4', provider=provider, type=circuittype),
|
||||||
|
)
|
||||||
|
Circuit.objects.bulk_create(circuits)
|
||||||
|
|
||||||
|
assignments = (
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[0],
|
||||||
|
circuit=circuits[0],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_PRIMARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[1],
|
||||||
|
circuit=circuits[1],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_SECONDARY
|
||||||
|
),
|
||||||
|
CircuitGroupAssignment(
|
||||||
|
group=circuit_groups[2],
|
||||||
|
circuit=circuits[2],
|
||||||
|
priority=CircuitPriorityChoices.PRIORITY_TERTIARY
|
||||||
|
),
|
||||||
|
)
|
||||||
|
CircuitGroupAssignment.objects.bulk_create(assignments)
|
||||||
|
|
||||||
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
|
cls.form_data = {
|
||||||
|
'group': circuit_groups[3].pk,
|
||||||
|
'circuit': circuits[3].pk,
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
|
||||||
|
'tags': [t.pk for t in tags],
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.bulk_edit_data = {
|
||||||
|
'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
|
||||||
|
}
|
||||||
|
@ -55,4 +55,19 @@ urlpatterns = [
|
|||||||
path('circuit-terminations/delete/', views.CircuitTerminationBulkDeleteView.as_view(), name='circuittermination_bulk_delete'),
|
path('circuit-terminations/delete/', views.CircuitTerminationBulkDeleteView.as_view(), name='circuittermination_bulk_delete'),
|
||||||
path('circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'circuittermination'))),
|
path('circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'circuittermination'))),
|
||||||
|
|
||||||
|
# Circuit Groups
|
||||||
|
path('circuit-groups/', views.CircuitGroupListView.as_view(), name='circuitgroup_list'),
|
||||||
|
path('circuit-groups/add/', views.CircuitGroupEditView.as_view(), name='circuitgroup_add'),
|
||||||
|
path('circuit-groups/import/', views.CircuitGroupBulkImportView.as_view(), name='circuitgroup_import'),
|
||||||
|
path('circuit-groups/edit/', views.CircuitGroupBulkEditView.as_view(), name='circuitgroup_bulk_edit'),
|
||||||
|
path('circuit-groups/delete/', views.CircuitGroupBulkDeleteView.as_view(), name='circuitgroup_bulk_delete'),
|
||||||
|
path('circuit-groups/<int:pk>/', include(get_model_urls('circuits', 'circuitgroup'))),
|
||||||
|
|
||||||
|
# Circuit Group Assignments
|
||||||
|
path('circuit-group-assignments/', views.CircuitGroupAssignmentListView.as_view(), name='circuitgroupassignment_list'),
|
||||||
|
path('circuit-group-assignments/add/', views.CircuitGroupAssignmentEditView.as_view(), name='circuitgroupassignment_add'),
|
||||||
|
path('circuit-group-assignments/import/', views.CircuitGroupAssignmentBulkImportView.as_view(), name='circuitgroupassignment_import'),
|
||||||
|
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'))),
|
||||||
]
|
]
|
||||||
|
@ -440,3 +440,100 @@ class CircuitTerminationBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
# Trace view
|
# Trace view
|
||||||
register_model_view(CircuitTermination, 'trace', kwargs={'model': CircuitTermination})(PathTraceView)
|
register_model_view(CircuitTermination, 'trace', kwargs={'model': CircuitTermination})(PathTraceView)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Circuit Groups
|
||||||
|
#
|
||||||
|
|
||||||
|
class CircuitGroupListView(generic.ObjectListView):
|
||||||
|
queryset = CircuitGroup.objects.annotate(
|
||||||
|
circuit_group_assignment_count=count_related(CircuitGroupAssignment, 'group')
|
||||||
|
)
|
||||||
|
filterset = filtersets.CircuitGroupFilterSet
|
||||||
|
filterset_form = forms.CircuitGroupFilterForm
|
||||||
|
table = tables.CircuitGroupTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroup)
|
||||||
|
class CircuitGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'related_models': self.get_related_models(request, instance),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroup, 'edit')
|
||||||
|
class CircuitGroupEditView(generic.ObjectEditView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
form = forms.CircuitGroupForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroup, 'delete')
|
||||||
|
class CircuitGroupDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
model_form = forms.CircuitGroupImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
filterset = filtersets.CircuitGroupFilterSet
|
||||||
|
table = tables.CircuitGroupTable
|
||||||
|
form = forms.CircuitGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = CircuitGroup.objects.all()
|
||||||
|
filterset = filtersets.CircuitGroupFilterSet
|
||||||
|
table = tables.CircuitGroupTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Circuit Groups
|
||||||
|
#
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentListView(generic.ObjectListView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||||
|
filterset_form = forms.CircuitGroupAssignmentFilterForm
|
||||||
|
table = tables.CircuitGroupAssignmentTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroupAssignment)
|
||||||
|
class CircuitGroupAssignmentView(generic.ObjectView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroupAssignment, 'edit')
|
||||||
|
class CircuitGroupAssignmentEditView(generic.ObjectEditView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
form = forms.CircuitGroupAssignmentForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(CircuitGroupAssignment, 'delete')
|
||||||
|
class CircuitGroupAssignmentDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
model_form = forms.CircuitGroupAssignmentImportForm
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||||
|
table = tables.CircuitGroupAssignmentTable
|
||||||
|
form = forms.CircuitGroupAssignmentBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitGroupAssignmentBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = CircuitGroupAssignment.objects.all()
|
||||||
|
filterset = filtersets.CircuitGroupAssignmentFilterSet
|
||||||
|
table = tables.CircuitGroupAssignmentTable
|
||||||
|
@ -1098,6 +1098,8 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
'asnrange',
|
'asnrange',
|
||||||
'cable',
|
'cable',
|
||||||
'circuit',
|
'circuit',
|
||||||
|
'circuitgroup',
|
||||||
|
'circuitgroupassignment',
|
||||||
'circuittermination',
|
'circuittermination',
|
||||||
'circuittype',
|
'circuittype',
|
||||||
'cluster',
|
'cluster',
|
||||||
|
@ -259,6 +259,8 @@ CIRCUITS_MENU = Menu(
|
|||||||
items=(
|
items=(
|
||||||
get_model_item('circuits', 'circuit', _('Circuits')),
|
get_model_item('circuits', 'circuit', _('Circuits')),
|
||||||
get_model_item('circuits', 'circuittype', _('Circuit Types')),
|
get_model_item('circuits', 'circuittype', _('Circuit Types')),
|
||||||
|
get_model_item('circuits', 'circuitgroup', _('Circuit Groups')),
|
||||||
|
get_model_item('circuits', 'circuitgroupassignment', _('Group Assignments')),
|
||||||
get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
|
get_model_item('circuits', 'circuittermination', _('Circuit Terminations')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -61,6 +61,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">
|
||||||
|
{% trans "Group Assignments" %}
|
||||||
|
{% if perms.circuits.add_circuitgroupassignment %}
|
||||||
|
<div class="card-actions">
|
||||||
|
<a href="{% url 'circuits:circuitgroupassignment_add' %}?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 "Assign Group" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
{% htmx_table 'circuits:circuitgroupassignment_list' circuit_id=object.pk %}
|
||||||
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
|
56
netbox/templates/circuits/circuitgroup.html
Normal file
56
netbox/templates/circuits/circuitgroup.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'circuits:circuitgroup_list' %}?circuitgroup_id={{ object.id }}">{{ object.name }}</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% if perms.circuit.add_circuitgroupassignment %}
|
||||||
|
<a href="{% url 'circuits:circuitgroupassignment_add' %}?group={{ object.pk }}" class="btn btn-primary">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Assign Circuit" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock extra_controls %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">{% trans "Circuit Group" %}</h5>
|
||||||
|
<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 "Tenant" %}</th>
|
||||||
|
<td>
|
||||||
|
{% if object.tenant.group %}
|
||||||
|
{{ object.tenant.group|linkify }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ object.tenant|linkify|placeholder }}
|
||||||
|
</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/comments.html' %}
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
48
netbox/templates/circuits/circuitgroupassignment.html
Normal file
48
netbox/templates/circuits/circuitgroupassignment.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'circuits:circuitgroupassignment_list' %}?group_id={{ object.group_id }}">{{ object.group }}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">{% trans "Circuit Group Assignment" %}</h5>
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Group" %}</th>
|
||||||
|
<td>{{ object.group }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Circuit" %}</th>
|
||||||
|
<td>{{ object.circuit }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Priority" %}</th>
|
||||||
|
<td>{{ object.priority }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% 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