Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes: #9047 - Add Provider Accounts #12057

Merged
merged 8 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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