Skip to content

Commit

Permalink
Closes: #9047 - Add Provider Accounts (#12057)
Browse files Browse the repository at this point in the history
* #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>
  • Loading branch information
DanSheps and jeremystretch authored Mar 29, 2023
1 parent d2a694a commit 9d709c8
Show file tree
Hide file tree
Showing 35 changed files with 792 additions and 98 deletions.
1 change: 1 addition & 0 deletions docs/development/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ These are considered the "core" application models which are used to model netwo

* [circuits.Circuit](../models/circuits/circuit.md)
* [circuits.Provider](../models/circuits/provider.md)
* [circuits.ProviderAccount](../models/circuits/provideracount.md)
* [circuits.ProviderNetwork](../models/circuits/providernetwork.md)
* [core.DataSource](../models/core/datasource.md)
* [dcim.Cable](../models/dcim/cable.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/development/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A SearchIndex subclass defines both its model and a list of two-tuples specifyin
| 60 | Unique serialized attribute (per related object) | Device.serial |
| 100 | Primary human identifier | Device.name, Circuit.cid, Cable.label |
| 110 | Slug | Site.slug |
| 200 | Secondary identifier | Provider.account, DeviceType.part_number |
| 200 | Secondary identifier | ProviderAccount.account, DeviceType.part_number |
| 300 | Highly unique descriptive attribute | CircuitTermination.xconnect_id, IPAddress.dns_name |
| 500 | Description | Site.description |
| 1000 | Custom field default | - |
Expand Down
6 changes: 4 additions & 2 deletions docs/features/circuits.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ NetBox is ideal for managing your network's transit and peering providers and ci
```mermaid
flowchart TD
ASN --> Provider
Provider --> ProviderNetwork & Circuit
Provider --> ProviderNetwork & ProviderAccount & Circuit
ProviderAccount --> Circuit
CircuitType --> Circuit
click ASN "../../models/circuits/asn/"
click Circuit "../../models/circuits/circuit/"
click CircuitType "../../models/circuits/circuittype/"
click Provider "../../models/circuits/provider/"
click ProviderAccount "../../models/circuits/provideraccount/"
click ProviderNetwork "../../models/circuits/providernetwork/"
```

Expand All @@ -25,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 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.

Expand Down
1 change: 1 addition & 0 deletions docs/features/contacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The following models support the assignment of contacts:

* circuits.Circuit
* circuits.Provider
* circuits.ProviderAccount
* dcim.Device
* dcim.Location
* dcim.Manufacturer
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/planning.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
Expand Down
4 changes: 4 additions & 0 deletions docs/models/circuits/circuit.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ A circuit represents a physical point-to-point data connection, typically used t

The [provider](./provider.md) to which this circuit belongs.

### Provider Account

Circuits may optionally be assigned to a specific [provider account](./provideraccount.md).

### Circuit ID

An identifier for this circuit. This must be unique to the assigned provider. (Circuits assigned to different providers may have the same circuit ID.)
Expand Down
4 changes: 0 additions & 4 deletions docs/models/circuits/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ A unique URL-friendly identifier. (This value can be used for filtering.)

The [AS numbers](../ipam/asn.md) assigned to this provider (optional).

### Account Number

The administrative account identifier tied to this provider for your organization.

### Portal URL

The URL for the provider's customer service portal.
Expand Down
17 changes: 17 additions & 0 deletions docs/models/circuits/provideraccount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Provider Accounts

This model can be used to represent individual accounts associated with a provider.

## Fields

### Provider

The [provider](./provider.md) the account belongs to.

### Name

A human-friendly name, unique to the provider.

### Account Number

The administrative account identifier tied to this provider for your organization.
13 changes: 13 additions & 0 deletions netbox/circuits/api/nested_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
'NestedCircuitTypeSerializer',
'NestedProviderNetworkSerializer',
'NestedProviderSerializer',
'NestedProviderAccountSerializer',
]


Expand Down Expand Up @@ -37,6 +38,18 @@ class Meta:
fields = ['id', 'url', 'display', 'name', 'slug', 'circuit_count']


#
# Provider Accounts
#

class NestedProviderAccountSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')

class Meta:
model = ProviderAccount
fields = ['id', 'url', 'display', 'name', 'account']


#
# Circuits
#
Expand Down
31 changes: 27 additions & 4 deletions netbox/circuits/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

class ProviderSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
accounts = SerializedPKRelatedField(
queryset=ProviderAccount.objects.all(),
serializer=NestedProviderAccountSerializer,
required=False,
many=True
)
asns = SerializedPKRelatedField(
queryset=ASN.objects.all(),
serializer=NestedASNSerializer,
Expand All @@ -31,11 +37,27 @@ class ProviderSerializer(NetBoxModelSerializer):
class Meta:
model = Provider
fields = [
'id', 'url', 'display', 'name', 'slug', 'account', 'description', 'comments', 'asns', 'tags',
'id', 'url', 'display', 'name', 'slug', 'accounts', 'description', 'comments', 'asns', 'tags',
'custom_fields', 'created', 'last_updated', 'circuit_count',
]


#
# Provider Accounts
#

class ProviderAccountSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provideraccount-detail')
provider = NestedProviderSerializer()

class Meta:
model = ProviderAccount
fields = [
'id', 'url', 'display', 'provider', 'name', 'account', 'description', 'comments', 'tags', 'custom_fields',
'created', 'last_updated',
]


#
# Provider networks
#
Expand Down Expand Up @@ -84,6 +106,7 @@ class Meta:
class CircuitSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
provider = NestedProviderSerializer()
provider_account = NestedProviderAccountSerializer()
status = ChoiceField(choices=CircuitStatusChoices, required=False)
type = NestedCircuitTypeSerializer()
tenant = NestedTenantSerializer(required=False, allow_null=True)
Expand All @@ -93,9 +116,9 @@ class CircuitSerializer(NetBoxModelSerializer):
class Meta:
model = Circuit
fields = [
'id', 'url', 'display', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date',
'commit_rate', 'description', 'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields',
'created', 'last_updated',
'id', 'url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date',
'termination_date', 'commit_rate', 'description', 'termination_a', 'termination_z', 'comments', 'tags',
'custom_fields', 'created', 'last_updated',
]


Expand Down
5 changes: 2 additions & 3 deletions netbox/circuits/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@

# 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-networks', views.ProviderNetworkViewSet)

app_name = 'circuits-api'
urlpatterns = router.urls
12 changes: 11 additions & 1 deletion netbox/circuits/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class CircuitTypeViewSet(NetBoxModelViewSet):

class CircuitViewSet(NetBoxModelViewSet):
queryset = Circuit.objects.prefetch_related(
'type', 'tenant', '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
Expand All @@ -65,6 +65,16 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
brief_prefetch_fields = ['circuit']


#
# Provider accounts
#

class ProviderAccountViewSet(NetBoxModelViewSet):
queryset = ProviderAccount.objects.prefetch_related('provider', 'tags')
serializer_class = serializers.ProviderAccountSerializer
filterset_class = filtersets.ProviderAccountFilterSet


#
# Provider networks
#
Expand Down
37 changes: 35 additions & 2 deletions netbox/circuits/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'CircuitTerminationFilterSet',
'CircuitTypeFilterSet',
'ProviderNetworkFilterSet',
'ProviderAccountFilterSet',
'ProviderFilterSet',
)

Expand Down Expand Up @@ -66,18 +67,45 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):

class Meta:
model = Provider
fields = ['id', 'name', 'slug', 'account']
fields = ['id', 'name', 'slug']

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(account__icontains=value) |
Q(accounts__account__icontains=value) |
Q(accounts__name__icontains=value) |
Q(comments__icontains=value)
)


class ProviderAccountFilterSet(NetBoxModelFilterSet):
provider_id = django_filters.ModelMultipleChoiceFilter(
queryset=Provider.objects.all(),
label=_('Provider (ID)'),
)
provider = django_filters.ModelMultipleChoiceFilter(
field_name='provider__slug',
queryset=Provider.objects.all(),
to_field_name='slug',
label=_('Provider (slug)'),
)

class Meta:
model = ProviderAccount
fields = ['id', 'name', 'account', 'description']

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(account__icontains=value) |
Q(comments__icontains=value)
).distinct()


class ProviderNetworkFilterSet(NetBoxModelFilterSet):
provider_id = django_filters.ModelMultipleChoiceFilter(
queryset=Provider.objects.all(),
Expand Down Expand Up @@ -123,6 +151,11 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
to_field_name='slug',
label=_('Provider (slug)'),
)
provider_account_id = django_filters.ModelMultipleChoiceFilter(
field_name='provider_account',
queryset=ProviderAccount.objects.all(),
label=_('ProviderAccount (ID)'),
)
provider_network_id = django_filters.ModelMultipleChoiceFilter(
field_name='terminations__provider_network',
queryset=ProviderNetwork.objects.all(),
Expand Down
41 changes: 33 additions & 8 deletions netbox/circuits/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
'CircuitBulkEditForm',
'CircuitTypeBulkEditForm',
'ProviderBulkEditForm',
'ProviderAccountBulkEditForm',
'ProviderNetworkBulkEditForm',
)

Expand All @@ -24,11 +25,6 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
label=_('ASNs'),
required=False
)
account = forms.CharField(
max_length=30,
required=False,
label=_('Account number')
)
description = forms.CharField(
max_length=200,
required=False
Expand All @@ -39,10 +35,32 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):

model = Provider
fieldsets = (
(None, ('asns', 'account', )),
(None, ('asns', 'description')),
)
nullable_fields = (
'asns', 'description', 'comments',
)


class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
required=False
)
description = forms.CharField(
max_length=200,
required=False
)
comments = CommentField(
label=_('Comments')
)

model = ProviderAccount
fieldsets = (
(None, ('provider', 'description')),
)
nullable_fields = (
'asns', 'account', 'description', 'comments',
'description', 'comments',
)


Expand Down Expand Up @@ -95,6 +113,13 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
queryset=Provider.objects.all(),
required=False
)
provider_account = DynamicModelChoiceField(
queryset=ProviderAccount.objects.all(),
required=False,
query_params={
'provider': '$provider'
}
)
status = forms.ChoiceField(
choices=add_blank_choice(CircuitStatusChoices),
required=False,
Expand Down Expand Up @@ -127,7 +152,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
model = Circuit
fieldsets = (
('Circuit', ('provider', 'type', 'status', 'description')),
('Service Parameters', ('install_date', 'termination_date', 'commit_rate')),
('Service Parameters', ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
('Tenancy', ('tenant',)),
)
nullable_fields = (
Expand Down
Loading

0 comments on commit 9d709c8

Please sign in to comment.