Closes: #9047 - Add Provider Accounts (#12057)

* #9047 - ProviderAccount

* #9047 - Move to new selector types

* #9047 - Re-introduce provider FK to Circuit model

* #9047 - Fix broken tests

* Misc cleanup

* Revert errant change

* Fix tests

* Update circuit filter form

---------

Co-authored-by: jeremystretch <jstretch@netboxlabs.com>
This commit is contained in:
Daniel Sheppard
2023-03-29 07:27:11 -05:00
committed by GitHub
parent d2a694a878
commit 9d709c84e7
35 changed files with 792 additions and 98 deletions

View File

@@ -29,8 +29,8 @@ class CircuitType(OrganizationalModel):
class Circuit(PrimaryModel):
"""
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. Circuit port speed and commit rate are measured
in Kbps.
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,
@@ -42,6 +42,13 @@ class Circuit(PrimaryModel):
on_delete=models.PROTECT,
related_name='circuits'
)
provider_account = models.ForeignKey(
to='circuits.ProviderAccount',
on_delete=models.PROTECT,
related_name='circuits',
blank=True,
null=True
)
type = models.ForeignKey(
to='CircuitType',
on_delete=models.PROTECT,
@@ -103,7 +110,8 @@ class Circuit(PrimaryModel):
)
clone_fields = (
'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate',
'description',
)
prerequisite_models = (
'circuits.CircuitType',
@@ -111,12 +119,16 @@ class Circuit(PrimaryModel):
)
class Meta:
ordering = ['provider', 'cid']
ordering = ['provider', 'provider_account', 'cid']
constraints = (
models.UniqueConstraint(
fields=('provider', 'cid'),
name='%(app_label)s_%(class)s_unique_provider_cid'
),
models.UniqueConstraint(
fields=('provider_account', 'cid'),
name='%(app_label)s_%(class)s_unique_provideraccount_cid'
),
)
def __str__(self):
@@ -128,6 +140,12 @@ class Circuit(PrimaryModel):
def get_status_color(self):
return CircuitStatusChoices.colors.get(self.status)
def clean(self):
super().clean()
if self.provider_account and self.provider != self.provider_account.provider:
raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."})
class CircuitTermination(
CustomFieldsMixin,

View File

@@ -1,5 +1,6 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext as _
@@ -8,6 +9,7 @@ from netbox.models import PrimaryModel
__all__ = (
'ProviderNetwork',
'Provider',
'ProviderAccount',
)
@@ -30,20 +32,13 @@ class Provider(PrimaryModel):
related_name='providers',
blank=True
)
account = models.CharField(
max_length=30,
blank=True,
verbose_name='Account number'
)
# Generic relations
contacts = GenericRelation(
to='tenancy.ContactAssignment'
)
clone_fields = (
'account',
)
clone_fields = ()
class Meta:
ordering = ['name']
@@ -55,6 +50,54 @@ class Provider(PrimaryModel):
return reverse('circuits:provider', args=[self.pk])
class ProviderAccount(PrimaryModel):
"""
This is a discrete account within a provider. Each Circuit belongs to a Provider Account.
"""
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(
to='tenancy.ContactAssignment'
)
clone_fields = ('provider', )
class Meta:
ordering = ('provider', 'account')
constraints = (
models.UniqueConstraint(
fields=('provider', 'account'),
name='%(app_label)s_%(class)s_unique_provider_account'
),
models.UniqueConstraint(
fields=('provider', 'name'),
name='%(app_label)s_%(class)s_unique_provider_name',
condition=~Q(name="")
),
)
def __str__(self):
if self.name:
return f'{self.account} ({self.name})'
return f'{self.account}'
def get_absolute_url(self):
return reverse('circuits:provideraccount', args=[self.pk])
class ProviderNetwork(PrimaryModel):
"""
This represents a provider network which exists outside of NetBox, the details of which are unknown or