Misc cleanup

This commit is contained in:
jeremystretch 2023-03-28 21:02:35 -04:00
parent 8f3590fdd5
commit bee08050b6
24 changed files with 247 additions and 288 deletions

View File

@ -5,8 +5,8 @@ NetBox is ideal for managing your network's transit and peering providers and ci
```mermaid
flowchart TD
ASN --> Provider
Provider --> ProviderAccount --> Circuit
Provider --> ProviderNetwork & Circuit
Provider --> ProviderNetwork & ProviderAccount & Circuit
ProviderAccount --> Circuit
CircuitType --> Circuit
click ASN "../../models/circuits/asn/"
@ -27,7 +27,7 @@ Sometimes you'll need to model provider networks into which you don't have full
A circuit is a physical connection between two points, which is installed and maintained by an external provider. For example, an Internet connection delivered as a fiber optic cable would be modeled as a circuit in NetBox.
Each circuit is associated with a provider account and assigned a circuit ID, which must be unique to that provider. A circuit is also assigned a user-defined type, operational status, and various other operating characteristics.
Each circuit is associated with a provider and assigned a circuit ID, which must be unique to that provider. A circuit is also assigned a user-defined type, operational status, and various other operating characteristics. Provider accounts can also be employed to further categorize circuits belonging to a common provider: These may represent different business units or technologies.
Each circuit may have up to two terminations (A and Z) defined. Each termination can be associated with a particular site or provider network. In the case of the former, a cable can be connected between the circuit termination and a device component to map its physical connectivity.

View File

@ -56,7 +56,7 @@ Below is the (rough) recommended order in which NetBox objects should be created
4. Manufacturers, device types, and module types
5. Platforms and device roles
6. Devices and modules
7. Providers, provider accounts and provider networks
7. Providers, provider accounts, and provider networks
8. Circuit types and circuits
9. Wireless LAN groups and wireless LANs
10. Route targets and VRFs

View File

@ -4,9 +4,13 @@ A circuit represents a physical point-to-point data connection, typically used t
## Fields
### Provider
The [provider](./provider.md) to which this circuit belongs.
### Provider Account
The [provider account](./provideraccount.md) to which this circuit belongs.
Circuits may optionally be assigned to a specific [provider account](./provideraccount.md).
### Circuit ID

View File

@ -7,15 +7,13 @@ router.APIRootView = views.CircuitsRootView
# Providers
router.register('providers', views.ProviderViewSet)
router.register('provider-accounts', views.ProviderAccountViewSet)
router.register('provider-networks', views.ProviderNetworkViewSet)
# Circuits
router.register('circuit-types', views.CircuitTypeViewSet)
router.register('circuits', views.CircuitViewSet)
router.register('circuit-terminations', views.CircuitTerminationViewSet)
# Provider networks
router.register('provider-accounts', views.ProviderAccountViewSet)
router.register('provider-networks', views.ProviderNetworkViewSet)
app_name = 'circuits-api'
urlpatterns = router.urls

View File

@ -22,7 +22,7 @@ class CircuitsRootView(APIRootView):
class ProviderViewSet(NetBoxModelViewSet):
queryset = Provider.objects.prefetch_related('asns', 'tags').annotate(
circuit_count=count_related(Circuit, 'provider_account__provider')
circuit_count=count_related(Circuit, 'provider')
)
serializer_class = serializers.ProviderSerializer
filterset_class = filtersets.ProviderFilterSet
@ -46,7 +46,7 @@ class CircuitTypeViewSet(NetBoxModelViewSet):
class CircuitViewSet(NetBoxModelViewSet):
queryset = Circuit.objects.prefetch_related(
'type', 'tenant', 'provider_account', 'provider_account__provider', 'termination_a', 'termination_z'
'type', 'tenant', 'provider', 'provider_account', 'termination_a', 'termination_z'
).prefetch_related('tags')
serializer_class = serializers.CircuitSerializer
filterset_class = filtersets.CircuitFilterSet
@ -66,11 +66,11 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
#
# Provider networks
# Provider accounts
#
class ProviderAccountViewSet(NetBoxModelViewSet):
queryset = ProviderAccount.objects.prefetch_related('tags')
queryset = ProviderAccount.objects.prefetch_related('provider', 'tags')
serializer_class = serializers.ProviderAccountSerializer
filterset_class = filtersets.ProviderAccountFilterSet

View File

@ -24,37 +24,37 @@ __all__ = (
class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='accounts__circuits__terminations__site__region',
field_name='circuits__terminations__site__region',
lookup_expr='in',
label=_('Region (ID)'),
)
region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='accounts__circuits__terminations__site__region',
field_name='circuits__terminations__site__region',
lookup_expr='in',
to_field_name='slug',
label=_('Region (slug)'),
)
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='accounts__circuits__terminations__site__group',
field_name='circuits__terminations__site__group',
lookup_expr='in',
label=_('Site group (ID)'),
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='accounts__circuits__terminations__site__group',
field_name='circuits__terminations__site__group',
lookup_expr='in',
to_field_name='slug',
label=_('Site group (slug)'),
)
site_id = django_filters.ModelMultipleChoiceFilter(
field_name='accounts__circuits__terminations__site',
field_name='circuits__terminations__site',
queryset=Site.objects.all(),
label=_('Site'),
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='accounts__circuits__terminations__site__slug',
field_name='circuits__terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label=_('Site (slug)'),
@ -67,7 +67,7 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
class Meta:
model = Provider
fields = ['id', 'name', 'slug', ]
fields = ['id', 'name', 'slug']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -35,7 +35,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
model = Provider
fieldsets = (
(None, ('asns', )),
(None, ('asns', 'description')),
)
nullable_fields = (
'asns', 'description', 'comments',
@ -60,8 +60,7 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
(None, ('provider', 'description')),
)
nullable_fields = (
'description',
'comments',
'description', 'comments',
)

View File

@ -38,7 +38,7 @@ class ProviderAccountImportForm(NetBoxModelImportForm):
class Meta:
model = ProviderAccount
fields = (
'provider', 'name', 'account', 'comments', 'tags',
'provider', 'name', 'account', 'description', 'comments', 'tags',
)

View File

@ -69,7 +69,6 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
label=_('Provider')
)
account = forms.CharField(
max_length=100,
required=False
)
tag = TagFilterField(model)

View File

@ -41,8 +41,7 @@ class ProviderForm(NetBoxModelForm):
class ProviderAccountForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
selector=True
queryset=Provider.objects.all()
)
comments = CommentField()
@ -93,14 +92,10 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
)
provider_account = DynamicModelChoiceField(
queryset=ProviderAccount.objects.all(),
initial_params={
'circuits': '$circuit'
},
required=False,
query_params={
'provider_id': '$provider',
},
selector=True,
required=False
}
)
type = DynamicModelChoiceField(
queryset=CircuitType.objects.all()
@ -129,9 +124,6 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
class CircuitTerminationForm(NetBoxModelForm):
circuit = DynamicModelChoiceField(
queryset=Circuit.objects.all(),
query_params={
'provider_id': '$provider',
},
selector=True
)
site = DynamicModelChoiceField(

View File

@ -1,35 +1,34 @@
# Generated by Django 4.1.4 on 2023-03-14 16:02
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
import utilities.json
#
# Migrate Account in Provider model to separate account model
#
def create_provideraccounts_from_providers(apps, schema_editor):
"""
Migrate Account in Provider model to separate account model
"""
Provider = apps.get_model('circuits', 'Provider')
ProviderAccount = apps.get_model('circuits', 'ProviderAccount')
provider_accounts = []
for provider in Provider.objects.all():
if provider.account:
provideraccount = ProviderAccount.objects.create(
name=f'{provider.name} {provider.account}',
account=provider.account,
provider_accounts.append(ProviderAccount(
provider=provider,
)
account=provider.account
))
ProviderAccount.objects.bulk_create(provider_accounts, batch_size=100)
#
# Unmigrate ProviderAccount to Provider model
#
def revert_provideraccounts_from_providers(apps, schema_editor):
def restore_providers_from_provideraccounts(apps, schema_editor):
"""
Restore Provider account values from auto-generated ProviderAccounts
"""
ProviderAccount = apps.get_model('circuits', 'ProviderAccount')
provideraccounts = ProviderAccount.objects.all().order_by('pk')
for provideraccount in provideraccounts:
if provideraccounts.filter(provider=provideraccount.provider)[0] == provideraccount:
provider_accounts = ProviderAccount.objects.order_by('pk')
for provideraccount in provider_accounts:
if provider_accounts.filter(provider=provideraccount.provider)[0] == provideraccount:
provideraccount.provider.account = provideraccount.account
provideraccount.provider.save()
@ -51,7 +50,7 @@ class Migration(migrations.Migration):
('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)),
('account', models.CharField(max_length=30)),
('account', models.CharField(max_length=100)),
('name', models.CharField(blank=True, max_length=100)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='accounts', to='circuits.provider')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
@ -62,14 +61,14 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='provideraccount',
constraint=models.UniqueConstraint(condition=models.Q(('account', ''), _negated=True), fields=('provider', 'name'), name='circuits_provideraccount_unique_provider_name'),
constraint=models.UniqueConstraint(condition=models.Q(('name', ''), _negated=True), fields=('provider', 'name'), name='circuits_provideraccount_unique_provider_name'),
),
migrations.AddConstraint(
model_name='provideraccount',
constraint=models.UniqueConstraint(fields=('provider', 'account'), name='circuits_provideraccount_unique_provider_account'),
),
migrations.RunPython(
create_provideraccounts_from_providers, revert_provideraccounts_from_providers
create_provideraccounts_from_providers, restore_providers_from_provideraccounts
),
migrations.RemoveField(
model_name='provider',

View File

@ -28,9 +28,9 @@ class CircuitType(OrganizationalModel):
class Circuit(PrimaryModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider Account; ProviderAccounts may have
multiple circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are
measured in Kbps.
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
circuits. Each circuit is also assigned a CircuitType and a Site, and may optionally be assigned to a particular
ProviderAccount. Circuit port speed and commit rate are measured in Kbps.
"""
cid = models.CharField(
max_length=100,
@ -116,7 +116,6 @@ class Circuit(PrimaryModel):
prerequisite_models = (
'circuits.CircuitType',
'circuits.Provider',
'circuits.ProviderAccount',
)
class Meta:
@ -143,8 +142,9 @@ class Circuit(PrimaryModel):
def clean(self):
super().clean()
if self.provider_account and self.provider != self.provider_account.provider:
raise ValidationError("Provider must match ProviderAccount's provider")
raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."})
class CircuitTermination(

View File

@ -15,8 +15,8 @@ __all__ = (
class Provider(PrimaryModel):
"""
This is usually a telecommunications company or similar organization. This model stores information pertinent to
the user's relationship with the Provider.
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
stores information pertinent to the user's relationship with the Provider.
"""
name = models.CharField(
max_length=100,
@ -54,19 +54,19 @@ class ProviderAccount(PrimaryModel):
"""
This is a discrete account within a provider. Each Circuit belongs to a Provider Account.
"""
account = models.CharField(
max_length=30,
verbose_name='Account number'
)
name = models.CharField(
max_length=100,
blank=True
)
provider = models.ForeignKey(
to='circuits.Provider',
on_delete=models.PROTECT,
related_name='accounts'
)
account = models.CharField(
max_length=100,
verbose_name='Account ID'
)
name = models.CharField(
max_length=100,
blank=True
)
# Generic relations
contacts = GenericRelation(
@ -85,7 +85,7 @@ class ProviderAccount(PrimaryModel):
models.UniqueConstraint(
fields=('provider', 'name'),
name='%(app_label)s_%(class)s_unique_provider_name',
condition=~Q(account="")
condition=~Q(name="")
),
)

View File

@ -1,5 +1,4 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
from circuits.models import *
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
@ -53,7 +52,8 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
linkify=True
)
provider_account = tables.Column(
linkify=True
linkify=True,
verbose_name='Account'
)
status = columns.ChoiceFieldColumn()
termination_a = tables.TemplateColumn(

View File

@ -50,8 +50,8 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
class Meta(NetBoxTable.Meta):
model = Provider
fields = (
'pk', 'id', 'name', 'accounts', 'account_count', 'asns', 'asn_count', 'circuit_count', 'description', 'comments', 'contacts',
'tags', 'created', 'last_updated',
'pk', 'id', 'name', 'accounts', 'account_count', 'asns', 'asn_count', 'circuit_count', 'description',
'comments', 'contacts', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'account_count', 'circuit_count')
@ -64,6 +64,12 @@ class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
provider = tables.Column(
linkify=True
)
circuit_count = columns.LinkedCountColumn(
accessor=Accessor('count_circuits'),
viewname='circuits:circuit_list',
url_params={'provider_account_id': 'pk'},
verbose_name='Circuits'
)
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:provideraccount_list'
@ -72,7 +78,8 @@ class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
class Meta(NetBoxTable.Meta):
model = ProviderAccount
fields = (
'pk', 'id', 'account', 'name', 'provider', 'circuit_count', 'comments', 'contacts', 'tags', 'created', 'last_updated',
'pk', 'id', 'account', 'name', 'provider', 'circuit_count', 'comments', 'contacts', 'tags', 'created',
'last_updated',
)
default_columns = ('pk', 'account', 'name', 'provider', 'circuit_count')

View File

@ -158,11 +158,6 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
provider_account = ProviderAccount.objects.create(
name='Provider Account 2',
provider=provider,
account='2345'
)
sites = (
Site(name='Site 1', slug='site-1'),
@ -177,9 +172,9 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
ProviderNetwork.objects.bulk_create(provider_networks)
circuits = (
Circuit(cid='Circuit 1', provider=provider, provider_account=provider_account, type=circuit_type),
Circuit(cid='Circuit 2', provider=provider, provider_account=provider_account, type=circuit_type),
Circuit(cid='Circuit 3', provider=provider, provider_account=provider_account, type=circuit_type),
Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
Circuit(cid='Circuit 2', provider=provider, type=circuit_type),
Circuit(cid='Circuit 3', provider=provider, type=circuit_type),
)
Circuit.objects.bulk_create(circuits)

View File

@ -36,15 +36,6 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
providers[1].asns.set([asns[1]])
providers[2].asns.set([asns[2]])
provider_accounts = (
ProviderAccount(name='Account A', account='AAAA', provider=providers[0]),
ProviderAccount(name='Account B', account='BBBB', provider=providers[1]),
ProviderAccount(name='Account C', account='CCCC', provider=providers[2]),
ProviderAccount(name='Account D', account='DDDD', provider=providers[3]),
ProviderAccount(name='Account E', account='EEEE', provider=providers[4]),
)
ProviderAccount.objects.bulk_create(provider_accounts)
regions = (
Region(name='Test Region 1', slug='test-region-1'),
Region(name='Test Region 2', slug='test-region-2'),
@ -73,8 +64,8 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
CircuitType.objects.bulk_create(circuit_types)
circuits = (
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Test Circuit 1'),
Circuit(provider=providers[1], provider_account=provider_accounts[1], type=circuit_types[1], cid='Test Circuit 1'),
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 1'),
Circuit(provider=providers[1], type=circuit_types[1], cid='Circuit 2'),
)
Circuit.objects.bulk_create(circuits)
@ -202,8 +193,9 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
Provider.objects.bulk_create(providers)
provider_accounts = (
ProviderAccount(name='Provider Account 1', provider=providers[0], account='1234'),
ProviderAccount(name='Provider Account 2', provider=providers[1], account='2345'),
ProviderAccount(name='Provider Account 1', provider=providers[0], account='A'),
ProviderAccount(name='Provider Account 2', provider=providers[1], account='B'),
ProviderAccount(name='Provider Account 3', provider=providers[2], account='C'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
@ -217,10 +209,10 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
circuits = (
Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', termination_date='2021-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar1'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', termination_date='2021-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar2'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', termination_date='2021-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[0], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', termination_date='2021-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[1], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', termination_date='2021-01-04', commit_rate=4000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[1], provider_account=provider_accounts[1], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', termination_date='2021-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE),
Circuit(provider=providers[1], provider_account=provider_accounts[1], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', termination_date='2021-01-06', commit_rate=6000, status=CircuitStatusChoices.STATUS_OFFLINE),
Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', termination_date='2021-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE),
Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', termination_date='2021-01-06', commit_rate=6000, status=CircuitStatusChoices.STATUS_OFFLINE),
)
Circuit.objects.bulk_create(circuits)
@ -258,9 +250,9 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_provider_account(self):
provider_account = ProviderAccount.objects.first()
params = {'provider_account_id': [provider_account.pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
provider_accounts = ProviderAccount.objects.all()[:2]
params = {'provider_account_id': [provider_accounts[0].pk, provider_accounts[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_provider_network(self):
provider_networks = ProviderNetwork.objects.all()[:2]
@ -342,11 +334,6 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
)
Provider.objects.bulk_create(providers)
provider_accounts = (
ProviderAccount(name='Provider Account 1', provider=providers[0], account='1234'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
provider_networks = (
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
ProviderNetwork(name='Provider Network 2', provider=providers[0]),
@ -355,13 +342,13 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
ProviderNetwork.objects.bulk_create(provider_networks)
circuits = (
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 1'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 2'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 3'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 4'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 5'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 6'),
Circuit(provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0], cid='Circuit 7'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 1'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 2'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 3'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 4'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 5'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 6'),
Circuit(provider=providers[0], circuitstype=circuit_types[0], cid='Circuit 7'),
)
Circuit.objects.bulk_create(circuits)

View File

@ -202,7 +202,6 @@ class ProviderAccountTestCase(ViewTestCases.PrimaryObjectViewTestCase):
ProviderAccount(name='Provider Account 2', provider=providers[0], account='2345'),
ProviderAccount(name='Provider Account 3', provider=providers[0], account='3456'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
@ -305,12 +304,11 @@ class CircuitTerminationTestCase(
Site.objects.bulk_create(sites)
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
account = ProviderAccount.objects.create(name='Provider Account 1', provider=provider, account='1234')
circuits = (
Circuit(cid='Circuit 1', provider=provider, provider_account=account, type=circuittype),
Circuit(cid='Circuit 2', provider=provider, provider_account=account, type=circuittype),
Circuit(cid='Circuit 3', provider=provider, provider_account=account, type=circuittype),
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.objects.bulk_create(circuits)

View File

@ -14,7 +14,7 @@ urlpatterns = [
path('providers/delete/', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
path('providers/<int:pk>/', include(get_model_urls('circuits', 'provider'))),
# Provider networks
# Provider accounts
path('provider-accounts/', views.ProviderAccountListView.as_view(), name='provideraccount_list'),
path('provider-accounts/add/', views.ProviderAccountEditView.as_view(), name='provideraccount_add'),
path('provider-accounts/import/', views.ProviderAccountBulkImportView.as_view(), name='provideraccount_import'),

View File

@ -30,9 +30,8 @@ class CablePathTestCase(TestCase):
cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel')
provider = Provider.objects.create(name='Provider', slug='provider')
provider_account = ProviderAccount.objects.create(name='Account', account='AAAA1111', provider=provider)
circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type')
cls.circuit = Circuit.objects.create(provider=provider, provider_account=provider_account, type=circuit_type, cid='Circuit 1')
cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1')
def assertPathExists(self, nodes, **kwargs):
"""
@ -1309,7 +1308,7 @@ class CablePathTestCase(TestCase):
[IF1] --C1-- [CT1] [CT2] --> [PN1]
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider_account.provider)
providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider)
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, provider_network=providernetwork, term_side='Z')
@ -1437,7 +1436,7 @@ class CablePathTestCase(TestCase):
"""
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
circuit2 = Circuit.objects.create(provider=self.circuit.provider, provider_account=self.circuit.provider_account, type=self.circuit.type, cid='Circuit 2')
circuit2 = Circuit.objects.create(provider=self.circuit.provider, type=self.circuit.type, cid='Circuit 2')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z')
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, site=self.site, term_side='A')

View File

@ -504,11 +504,10 @@ class CableTestCase(TestCase):
device=patch_pannel, name='FP4', type='8p8c', rear_port=rear_port4, rear_port_position=1
)
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_account = ProviderAccount.objects.create(name='Provider Account 1', account='A1', provider=provider)
provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider)
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
circuit1 = Circuit.objects.create(provider=provider, provider_account=provider_account, type=circuittype, cid='1')
circuit2 = Circuit.objects.create(provider=provider, provider_account=provider_account, type=circuittype, cid='2')
circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2')
circuittermination1 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z')
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A')

View File

@ -1,6 +1,6 @@
from django.test import TransactionTestCase
from circuits.models import Provider, Circuit, CircuitType, ProviderAccount
from circuits.models import Provider, Circuit, CircuitType
from extras.choices import ChangeActionChoices
from extras.models import Branch, StagedChange, Tag
from ipam.models import ASN, RIR
@ -28,25 +28,18 @@ class StagingTestCase(TransactionTestCase):
)
Provider.objects.bulk_create(providers)
provider_accounts = (
ProviderAccount(name='Account A', provider=providers[0], account='AAAA'),
ProviderAccount(name='Account B', provider=providers[1], account='BBBB'),
ProviderAccount(name='Account C', provider=providers[2], account='CCCC'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
Circuit.objects.bulk_create((
Circuit(provider=providers[0], provider_account=provider_accounts[0], cid='Circuit A1', type=circuit_type),
Circuit(provider=providers[0], provider_account=provider_accounts[0], cid='Circuit A2', type=circuit_type),
Circuit(provider=providers[0], provider_account=provider_accounts[0], cid='Circuit A3', type=circuit_type),
Circuit(provider=providers[1], provider_account=provider_accounts[1], cid='Circuit B1', type=circuit_type),
Circuit(provider=providers[1], provider_account=provider_accounts[1], cid='Circuit B2', type=circuit_type),
Circuit(provider=providers[1], provider_account=provider_accounts[1], cid='Circuit B3', type=circuit_type),
Circuit(provider=providers[2], provider_account=provider_accounts[2], cid='Circuit C1', type=circuit_type),
Circuit(provider=providers[2], provider_account=provider_accounts[2], cid='Circuit C2', type=circuit_type),
Circuit(provider=providers[2], provider_account=provider_accounts[2], cid='Circuit C3', type=circuit_type),
Circuit(provider=providers[0], cid='Circuit A1', type=circuit_type),
Circuit(provider=providers[0], cid='Circuit A2', type=circuit_type),
Circuit(provider=providers[0], cid='Circuit A3', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B1', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B2', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B3', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C1', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C2', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C3', type=circuit_type),
))
def test_object_creation(self):
@ -57,8 +50,7 @@ class StagingTestCase(TransactionTestCase):
with checkout(branch):
provider = Provider.objects.create(name='Provider D', slug='provider-d')
provider.asns.set(asns)
provider_account = ProviderAccount.objects.create(name='Account D', provider=provider, account='DDDD')
circuit = Circuit.objects.create(provider=provider, provider_account=provider_account, cid='Circuit D1', type=CircuitType.objects.first())
circuit = Circuit.objects.create(provider=provider, cid='Circuit D1', type=CircuitType.objects.first())
circuit.tags.set(tags)
# Sanity-checking
@ -70,7 +62,7 @@ class StagingTestCase(TransactionTestCase):
# Verify that changes have been rolled back after exiting the context
self.assertEqual(Provider.objects.count(), 3)
self.assertEqual(Circuit.objects.count(), 9)
self.assertEqual(StagedChange.objects.count(), 6)
self.assertEqual(StagedChange.objects.count(), 5)
# Verify that changes are replayed upon entering the context
with checkout(branch):
@ -153,31 +145,25 @@ class StagingTestCase(TransactionTestCase):
with checkout(branch):
provider = Provider.objects.get(name='Provider A')
Circuit.objects.filter(provider_account__provider=provider).delete()
provider.accounts.all().delete()
provider.delete()
# Sanity-checking
self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6)
# Verify that changes have been rolled back after exiting the context
self.assertEqual(Provider.objects.count(), 3)
self.assertEqual(ProviderAccount.objects.count(), 3)
self.assertEqual(Circuit.objects.count(), 9)
self.assertEqual(StagedChange.objects.count(), 5)
self.assertEqual(StagedChange.objects.count(), 4)
# Verify that changes are replayed upon entering the context
with checkout(branch):
self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6)
# Verify that changes are applied and deleted upon branch merge
branch.merge()
self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6)
self.assertEqual(StagedChange.objects.count(), 0)

View File

@ -19,8 +19,8 @@
<td>{{ object.provider|linkify }}</td>
</tr>
<tr>
<th scope="row">Provider Account</th>
<td>{{ object.provider_account|linkify }}</td>
<th scope="row">Account</th>
<td>{{ object.provider_account|linkify|placeholder }}</td>
</tr>
<tr>
<th scope="row">Circuit ID</th>

View File

@ -13,9 +13,7 @@
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Provider Account
</h5>
<h5 class="card-header">Provider Account</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
@ -23,12 +21,12 @@
<td>{{ object.provider|linkify }}</td>
</tr>
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
<th scope="row">Account</th>
<td>{{ object.account }}</td>
</tr>
<tr>
<th scope="row">Account</th>
<td>{{ object.account|placeholder }}</td>
<th scope="row">Name</th>
<td>{{ object.name|placeholder }}</td>
</tr>
</table>
</div>
@ -43,12 +41,11 @@
{% include 'inc/panels/contacts.html' %}
{% plugin_right_page object %}
</div>
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Circuits</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'circuits:circuit_list' %}?provider_id={{ object.pk }}"
hx-get="{% url 'circuits:circuit_list' %}?provider_account_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>