mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 17:26:10 -06:00
Introduce ASNRange model
This commit is contained in:
parent
1e1aac5c48
commit
7e7c9e981d
@ -7,6 +7,7 @@ from netbox.api.serializers import WritableNestedSerializer
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'NestedAggregateSerializer',
|
'NestedAggregateSerializer',
|
||||||
'NestedASNSerializer',
|
'NestedASNSerializer',
|
||||||
|
'NestedASNRangeSerializer',
|
||||||
'NestedFHRPGroupSerializer',
|
'NestedFHRPGroupSerializer',
|
||||||
'NestedFHRPGroupAssignmentSerializer',
|
'NestedFHRPGroupAssignmentSerializer',
|
||||||
'NestedIPAddressSerializer',
|
'NestedIPAddressSerializer',
|
||||||
@ -25,6 +26,18 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ASN ranges
|
||||||
|
#
|
||||||
|
|
||||||
|
class NestedASNRangeSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asnrange-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.ASNRange
|
||||||
|
fields = ['id', 'url', 'display', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ASNs
|
# ASNs
|
||||||
#
|
#
|
||||||
|
@ -15,12 +15,26 @@ from virtualization.api.nested_serializers import NestedVirtualMachineSerializer
|
|||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ASN ranges
|
||||||
|
#
|
||||||
|
|
||||||
|
class ASNRangeSerializer(NetBoxModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asnrange-detail')
|
||||||
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
|
asn_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ASNRange
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'name', 'slug', 'start', 'end', 'tenant', 'description', 'tags', 'custom_fields',
|
||||||
|
'created', 'last_updated', 'asn_count',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ASNs
|
# ASNs
|
||||||
#
|
#
|
||||||
from .nested_serializers import NestedL2VPNSerializer
|
|
||||||
from ..models.l2vpn import L2VPNTermination, L2VPN
|
|
||||||
|
|
||||||
|
|
||||||
class ASNSerializer(NetBoxModelSerializer):
|
class ASNSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asn-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:asn-detail')
|
||||||
|
@ -7,44 +7,22 @@ from . import views
|
|||||||
router = NetBoxRouter()
|
router = NetBoxRouter()
|
||||||
router.APIRootView = views.IPAMRootView
|
router.APIRootView = views.IPAMRootView
|
||||||
|
|
||||||
# ASNs
|
|
||||||
router.register('asns', views.ASNViewSet)
|
router.register('asns', views.ASNViewSet)
|
||||||
|
router.register('asn-ranges', views.ASNRangeViewSet)
|
||||||
# VRFs
|
|
||||||
router.register('vrfs', views.VRFViewSet)
|
router.register('vrfs', views.VRFViewSet)
|
||||||
|
|
||||||
# Route targets
|
|
||||||
router.register('route-targets', views.RouteTargetViewSet)
|
router.register('route-targets', views.RouteTargetViewSet)
|
||||||
|
|
||||||
# RIRs
|
|
||||||
router.register('rirs', views.RIRViewSet)
|
router.register('rirs', views.RIRViewSet)
|
||||||
|
|
||||||
# Aggregates
|
|
||||||
router.register('aggregates', views.AggregateViewSet)
|
router.register('aggregates', views.AggregateViewSet)
|
||||||
|
|
||||||
# Prefixes
|
|
||||||
router.register('roles', views.RoleViewSet)
|
router.register('roles', views.RoleViewSet)
|
||||||
router.register('prefixes', views.PrefixViewSet)
|
router.register('prefixes', views.PrefixViewSet)
|
||||||
|
|
||||||
# IP ranges
|
|
||||||
router.register('ip-ranges', views.IPRangeViewSet)
|
router.register('ip-ranges', views.IPRangeViewSet)
|
||||||
|
|
||||||
# IP addresses
|
|
||||||
router.register('ip-addresses', views.IPAddressViewSet)
|
router.register('ip-addresses', views.IPAddressViewSet)
|
||||||
|
|
||||||
# FHRP groups
|
|
||||||
router.register('fhrp-groups', views.FHRPGroupViewSet)
|
router.register('fhrp-groups', views.FHRPGroupViewSet)
|
||||||
router.register('fhrp-group-assignments', views.FHRPGroupAssignmentViewSet)
|
router.register('fhrp-group-assignments', views.FHRPGroupAssignmentViewSet)
|
||||||
|
|
||||||
# VLANs
|
|
||||||
router.register('vlan-groups', views.VLANGroupViewSet)
|
router.register('vlan-groups', views.VLANGroupViewSet)
|
||||||
router.register('vlans', views.VLANViewSet)
|
router.register('vlans', views.VLANViewSet)
|
||||||
|
|
||||||
# Services
|
|
||||||
router.register('service-templates', views.ServiceTemplateViewSet)
|
router.register('service-templates', views.ServiceTemplateViewSet)
|
||||||
router.register('services', views.ServiceViewSet)
|
router.register('services', views.ServiceViewSet)
|
||||||
|
|
||||||
# L2VPN
|
|
||||||
router.register('l2vpns', views.L2VPNViewSet)
|
router.register('l2vpns', views.L2VPNViewSet)
|
||||||
router.register('l2vpn-terminations', views.L2VPNTerminationViewSet)
|
router.register('l2vpn-terminations', views.L2VPNTerminationViewSet)
|
||||||
|
|
||||||
|
@ -33,6 +33,14 @@ class IPAMRootView(APIRootView):
|
|||||||
# Viewsets
|
# Viewsets
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class ASNRangeViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = ASNRange.objects.prefetch_related('tenant').annotate(
|
||||||
|
asn_count=count_related(ASN, 'range')
|
||||||
|
)
|
||||||
|
serializer_class = serializers.ASNRangeSerializer
|
||||||
|
filterset_class = filtersets.ASNRangeFilterSet
|
||||||
|
|
||||||
|
|
||||||
class ASNViewSet(NetBoxModelViewSet):
|
class ASNViewSet(NetBoxModelViewSet):
|
||||||
queryset = ASN.objects.prefetch_related('tenant', 'rir').annotate(
|
queryset = ASN.objects.prefetch_related('tenant', 'rir').annotate(
|
||||||
site_count=count_related(Site, 'asns'),
|
site_count=count_related(Site, 'asns'),
|
||||||
|
@ -20,6 +20,7 @@ from .models import *
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateFilterSet',
|
'AggregateFilterSet',
|
||||||
'ASNFilterSet',
|
'ASNFilterSet',
|
||||||
|
'ASNRangeFilterSet',
|
||||||
'FHRPGroupAssignmentFilterSet',
|
'FHRPGroupAssignmentFilterSet',
|
||||||
'FHRPGroupFilterSet',
|
'FHRPGroupFilterSet',
|
||||||
'IPAddressFilterSet',
|
'IPAddressFilterSet',
|
||||||
@ -167,6 +168,19 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ASNRange
|
||||||
|
fields = ['id', 'start', 'end', 'description']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = Q(description__icontains=value)
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
||||||
rir_id = django_filters.ModelMultipleChoiceFilter(
|
rir_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
|
@ -16,6 +16,7 @@ from utilities.forms import (
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateBulkEditForm',
|
'AggregateBulkEditForm',
|
||||||
'ASNBulkEditForm',
|
'ASNBulkEditForm',
|
||||||
|
'ASNRangeBulkEditForm',
|
||||||
'FHRPGroupBulkEditForm',
|
'FHRPGroupBulkEditForm',
|
||||||
'IPAddressBulkEditForm',
|
'IPAddressBulkEditForm',
|
||||||
'IPRangeBulkEditForm',
|
'IPRangeBulkEditForm',
|
||||||
@ -97,6 +98,23 @@ class RIRBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
nullable_fields = ('is_private', 'description')
|
nullable_fields = ('is_private', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
tenant = DynamicModelChoiceField(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
model = ASNRange
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('tenant', 'description')),
|
||||||
|
)
|
||||||
|
nullable_fields = ('date_added', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
sites = DynamicModelMultipleChoiceField(
|
sites = DynamicModelMultipleChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
|
@ -15,6 +15,7 @@ from virtualization.models import VirtualMachine, VMInterface
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateImportForm',
|
'AggregateImportForm',
|
||||||
'ASNImportForm',
|
'ASNImportForm',
|
||||||
|
'ASNRangeImportForm',
|
||||||
'FHRPGroupImportForm',
|
'FHRPGroupImportForm',
|
||||||
'IPAddressImportForm',
|
'IPAddressImportForm',
|
||||||
'IPRangeImportForm',
|
'IPRangeImportForm',
|
||||||
@ -87,6 +88,19 @@ class AggregateImportForm(NetBoxModelImportForm):
|
|||||||
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags')
|
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeImportForm(NetBoxModelImportForm):
|
||||||
|
tenant = CSVModelChoiceField(
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned tenant')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ASNRange
|
||||||
|
fields = ('name', 'slug', 'start', 'end', 'tenant', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ASNImportForm(NetBoxModelImportForm):
|
class ASNImportForm(NetBoxModelImportForm):
|
||||||
rir = CSVModelChoiceField(
|
rir = CSVModelChoiceField(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
|
@ -17,6 +17,7 @@ from virtualization.models import VirtualMachine
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateFilterForm',
|
'AggregateFilterForm',
|
||||||
'ASNFilterForm',
|
'ASNFilterForm',
|
||||||
|
'ASNRangeFilterForm',
|
||||||
'FHRPGroupFilterForm',
|
'FHRPGroupFilterForm',
|
||||||
'IPAddressFilterForm',
|
'IPAddressFilterForm',
|
||||||
'IPRangeFilterForm',
|
'IPRangeFilterForm',
|
||||||
@ -114,6 +115,22 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
|
model = ASNRange
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
|
('Range', ('start', 'end')),
|
||||||
|
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||||
|
)
|
||||||
|
start = forms.IntegerField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
end = forms.IntegerField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = ASN
|
model = ASN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
@ -20,6 +20,7 @@ from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInter
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateForm',
|
'AggregateForm',
|
||||||
'ASNForm',
|
'ASNForm',
|
||||||
|
'ASNRangeForm',
|
||||||
'FHRPGroupForm',
|
'FHRPGroupForm',
|
||||||
'FHRPGroupAssignmentForm',
|
'FHRPGroupAssignmentForm',
|
||||||
'IPAddressAssignForm',
|
'IPAddressAssignForm',
|
||||||
@ -128,6 +129,20 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeForm(TenancyForm, NetBoxModelForm):
|
||||||
|
slug = SlugField()
|
||||||
|
fieldsets = (
|
||||||
|
('ASN Range', ('name', 'slug', 'start', 'end', 'description', 'tags')),
|
||||||
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ASNRange
|
||||||
|
fields = [
|
||||||
|
'name', 'slug', 'start', 'end', 'tenant_group', 'tenant', 'description', 'tags'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ASNForm(TenancyForm, NetBoxModelForm):
|
class ASNForm(TenancyForm, NetBoxModelForm):
|
||||||
rir = DynamicModelChoiceField(
|
rir = DynamicModelChoiceField(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
|
@ -8,6 +8,9 @@ class IPAMQuery(graphene.ObjectType):
|
|||||||
asn = ObjectField(ASNType)
|
asn = ObjectField(ASNType)
|
||||||
asn_list = ObjectListField(ASNType)
|
asn_list = ObjectListField(ASNType)
|
||||||
|
|
||||||
|
asnrange = ObjectField(ASNRangeType)
|
||||||
|
asnrange_list = ObjectListField(ASNRangeType)
|
||||||
|
|
||||||
aggregate = ObjectField(AggregateType)
|
aggregate = ObjectField(AggregateType)
|
||||||
aggregate_list = ObjectListField(AggregateType)
|
aggregate_list = ObjectListField(AggregateType)
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from graphene_django import DjangoObjectType
|
|
||||||
from extras.graphql.mixins import ContactsMixin
|
from extras.graphql.mixins import ContactsMixin
|
||||||
from ipam import filtersets, models
|
from ipam import filtersets, models
|
||||||
from netbox.graphql.scalars import BigInt
|
from netbox.graphql.scalars import BigInt
|
||||||
@ -8,6 +7,7 @@ from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBo
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ASNType',
|
'ASNType',
|
||||||
|
'ASNRangeType',
|
||||||
'AggregateType',
|
'AggregateType',
|
||||||
'FHRPGroupType',
|
'FHRPGroupType',
|
||||||
'FHRPGroupAssignmentType',
|
'FHRPGroupAssignmentType',
|
||||||
@ -36,6 +36,14 @@ class ASNType(NetBoxObjectType):
|
|||||||
filterset_class = filtersets.ASNFilterSet
|
filterset_class = filtersets.ASNFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeType(NetBoxObjectType):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.ASNRange
|
||||||
|
fields = '__all__'
|
||||||
|
filterset_class = filtersets.ASNRangeFilterSet
|
||||||
|
|
||||||
|
|
||||||
class AggregateType(NetBoxObjectType):
|
class AggregateType(NetBoxObjectType):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
45
netbox/ipam/migrations/0064_asnrange.py
Normal file
45
netbox/ipam/migrations/0064_asnrange.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-02-25 19:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import ipam.fields
|
||||||
|
import taggit.managers
|
||||||
|
import utilities.json
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tenancy', '0009_standardize_description_comments'),
|
||||||
|
('extras', '0087_dashboard'),
|
||||||
|
('ipam', '0063_standardize_description_comments'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ASNRange',
|
||||||
|
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)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('slug', models.SlugField(max_length=100, unique=True)),
|
||||||
|
('start', ipam.fields.ASNField()),
|
||||||
|
('end', ipam.fields.ASNField()),
|
||||||
|
('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='asn_ranges', to='tenancy.tenant')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'ASN range',
|
||||||
|
'verbose_name_plural': 'ASN ranges',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asn',
|
||||||
|
name='range',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.asnrange'),
|
||||||
|
),
|
||||||
|
]
|
@ -9,6 +9,7 @@ from .vlans import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ASN',
|
'ASN',
|
||||||
|
'ASNRange',
|
||||||
'Aggregate',
|
'Aggregate',
|
||||||
'IPAddress',
|
'IPAddress',
|
||||||
'IPRange',
|
'IPRange',
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from ipam.fields import ASNField
|
from ipam.fields import ASNField
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ASN',
|
'ASN',
|
||||||
|
'ASNRange',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +17,12 @@ class ASN(PrimaryModel):
|
|||||||
An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
|
An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
|
||||||
one or more ASNs assigned to it.
|
one or more ASNs assigned to it.
|
||||||
"""
|
"""
|
||||||
|
range = models.ForeignKey(
|
||||||
|
to='ipam.ASNRange',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
asn = ASNField(
|
asn = ASNField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name='ASN',
|
verbose_name='ASN',
|
||||||
@ -68,3 +75,49 @@ class ASN(PrimaryModel):
|
|||||||
return f'{self.asn} ({self.asn // 65536}.{self.asn % 65536})'
|
return f'{self.asn} ({self.asn // 65536}.{self.asn % 65536})'
|
||||||
else:
|
else:
|
||||||
return self.asn
|
return self.asn
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.range and self.asn not in self.range.range:
|
||||||
|
raise ValidationError(f"ASN {self.asn} is outside of assigned range ({self.range})")
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRange(OrganizationalModel):
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
start = ASNField()
|
||||||
|
end = ASNField()
|
||||||
|
tenant = models.ForeignKey(
|
||||||
|
to='tenancy.Tenant',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='asn_ranges',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
verbose_name = 'ASN range'
|
||||||
|
verbose_name_plural = 'ASN ranges'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'ASNs {self.range_as_string()}'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('ipam:asnrange', args=[self.pk])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def range(self):
|
||||||
|
return range(self.start, self.end + 1)
|
||||||
|
|
||||||
|
def range_as_string(self):
|
||||||
|
return f'{self.start}-{self.end}'
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.end <= self.start:
|
||||||
|
raise ValidationError(f"Starting ASN ({self.start}) must be lower than ending ASN ({self.end}).")
|
||||||
|
@ -22,6 +22,14 @@ class ASNIndex(SearchIndex):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class ASNRangeIndex(SearchIndex):
|
||||||
|
model = models.ASNRange
|
||||||
|
fields = (
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
class FHRPGroupIndex(SearchIndex):
|
class FHRPGroupIndex(SearchIndex):
|
||||||
model = models.FHRPGroup
|
model = models.FHRPGroup
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from .asn import *
|
||||||
from .fhrp import *
|
from .fhrp import *
|
||||||
from .ip import *
|
from .ip import *
|
||||||
from .l2vpn import *
|
from .l2vpn import *
|
||||||
|
69
netbox/ipam/tables/asn.py
Normal file
69
netbox/ipam/tables/asn.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from ipam.models import *
|
||||||
|
from netbox.tables import NetBoxTable, columns
|
||||||
|
from tenancy.tables import TenancyColumnsMixin
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'ASNTable',
|
||||||
|
'ASNRangeTable',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='ipam:asnrange_list'
|
||||||
|
)
|
||||||
|
asn_count = columns.LinkedCountColumn(
|
||||||
|
viewname='ipam:asn_list',
|
||||||
|
url_params={'asn_id': 'pk'},
|
||||||
|
verbose_name=_('ASN Count')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = ASNRange
|
||||||
|
fields = (
|
||||||
|
'pk', 'name', 'slug', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', 'tags',
|
||||||
|
'created', 'last_updated', 'actions',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'name', 'start', 'end', 'tenant', 'asn_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
class ASNTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
|
asn = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
asn_asdot = tables.Column(
|
||||||
|
accessor=tables.A('asn_asdot'),
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('ASDOT')
|
||||||
|
)
|
||||||
|
site_count = columns.LinkedCountColumn(
|
||||||
|
viewname='dcim:site_list',
|
||||||
|
url_params={'asn_id': 'pk'},
|
||||||
|
verbose_name=_('Site Count')
|
||||||
|
)
|
||||||
|
provider_count = columns.LinkedCountColumn(
|
||||||
|
viewname='circuits:provider_list',
|
||||||
|
url_params={'asn_id': 'pk'},
|
||||||
|
verbose_name=_('Provider Count')
|
||||||
|
)
|
||||||
|
sites = columns.ManyToManyColumn(
|
||||||
|
linkify_item=True
|
||||||
|
)
|
||||||
|
comments = columns.MarkdownColumn()
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='ipam:asn_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = ASN
|
||||||
|
fields = (
|
||||||
|
'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description',
|
||||||
|
'comments', 'sites', 'tags', 'created', 'last_updated', 'actions',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant')
|
@ -8,7 +8,6 @@ from tenancy.tables import TenancyColumnsMixin, TenantColumn
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AggregateTable',
|
'AggregateTable',
|
||||||
'ASNTable',
|
|
||||||
'AssignedIPAddressesTable',
|
'AssignedIPAddressesTable',
|
||||||
'IPAddressAssignTable',
|
'IPAddressAssignTable',
|
||||||
'IPAddressTable',
|
'IPAddressTable',
|
||||||
@ -93,47 +92,6 @@ class RIRTable(NetBoxTable):
|
|||||||
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description')
|
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# ASNs
|
|
||||||
#
|
|
||||||
|
|
||||||
class ASNTable(TenancyColumnsMixin, NetBoxTable):
|
|
||||||
asn = tables.Column(
|
|
||||||
linkify=True
|
|
||||||
)
|
|
||||||
asn_asdot = tables.Column(
|
|
||||||
accessor=tables.A('asn_asdot'),
|
|
||||||
linkify=True,
|
|
||||||
verbose_name='ASDOT'
|
|
||||||
)
|
|
||||||
site_count = columns.LinkedCountColumn(
|
|
||||||
viewname='dcim:site_list',
|
|
||||||
url_params={'asn_id': 'pk'},
|
|
||||||
verbose_name='Site Count'
|
|
||||||
)
|
|
||||||
provider_count = columns.LinkedCountColumn(
|
|
||||||
viewname='circuits:provider_list',
|
|
||||||
url_params={'asn_id': 'pk'},
|
|
||||||
verbose_name='Provider Count'
|
|
||||||
)
|
|
||||||
sites = columns.ManyToManyColumn(
|
|
||||||
linkify_item=True,
|
|
||||||
verbose_name='Sites'
|
|
||||||
)
|
|
||||||
comments = columns.MarkdownColumn()
|
|
||||||
tags = columns.TagColumn(
|
|
||||||
url_name='ipam:asn_list'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
|
||||||
model = ASN
|
|
||||||
fields = (
|
|
||||||
'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description',
|
|
||||||
'comments', 'sites', 'tags', 'created', 'last_updated', 'actions',
|
|
||||||
)
|
|
||||||
default_columns = ('pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant')
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Aggregates
|
# Aggregates
|
||||||
#
|
#
|
||||||
|
@ -6,6 +6,14 @@ from . import views
|
|||||||
app_name = 'ipam'
|
app_name = 'ipam'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
|
# ASN ranges
|
||||||
|
path('asn-ranges/', views.ASNRangeListView.as_view(), name='asnrange_list'),
|
||||||
|
path('asn-ranges/add/', views.ASNRangeEditView.as_view(), name='asnrange_add'),
|
||||||
|
path('asn-ranges/import/', views.ASNRangeBulkImportView.as_view(), name='asnrange_import'),
|
||||||
|
path('asn-ranges/edit/', views.ASNRangeBulkEditView.as_view(), name='asnrange_bulk_edit'),
|
||||||
|
path('asn-ranges/delete/', views.ASNRangeBulkDeleteView.as_view(), name='asnrange_bulk_delete'),
|
||||||
|
path('asn-ranges/<int:pk>/', include(get_model_urls('ipam', 'asnrange'))),
|
||||||
|
|
||||||
# ASNs
|
# ASNs
|
||||||
path('asns/', views.ASNListView.as_view(), name='asn_list'),
|
path('asns/', views.ASNListView.as_view(), name='asn_list'),
|
||||||
path('asns/add/', views.ASNEditView.as_view(), name='asn_add'),
|
path('asns/add/', views.ASNEditView.as_view(), name='asn_add'),
|
||||||
|
@ -7,16 +7,15 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from circuits.models import Provider
|
from circuits.models import Provider
|
||||||
from dcim.filtersets import InterfaceFilterSet
|
from dcim.filtersets import InterfaceFilterSet
|
||||||
from dcim.models import Interface, Site, Device
|
from dcim.models import Interface, Site
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from utilities.views import ViewTab, register_model_view
|
from utilities.views import ViewTab, register_model_view
|
||||||
from virtualization.filtersets import VMInterfaceFilterSet
|
from virtualization.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VMInterface, VirtualMachine
|
from virtualization.models import VMInterface
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
from .models import ASN
|
|
||||||
from .tables.l2vpn import L2VPNTable, L2VPNTerminationTable
|
from .tables.l2vpn import L2VPNTable, L2VPNTerminationTable
|
||||||
from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans
|
from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans
|
||||||
|
|
||||||
@ -195,6 +194,67 @@ class RIRBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ASN ranges
|
||||||
|
#
|
||||||
|
|
||||||
|
class ASNRangeListView(generic.ObjectListView):
|
||||||
|
queryset = ASNRange.objects.annotate(
|
||||||
|
asn_count=count_related(ASN, 'range'),
|
||||||
|
)
|
||||||
|
filterset = filtersets.ASNRangeFilterSet
|
||||||
|
filterset_form = forms.ASNRangeFilterForm
|
||||||
|
table = tables.ASNRangeTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ASNRange)
|
||||||
|
class ASNRangeView(generic.ObjectView):
|
||||||
|
queryset = ASNRange.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
related_models = (
|
||||||
|
(ASN.objects.restrict(request.user, 'view').filter(range=instance), 'asn_id'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'related_models': related_models,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ASNRange, 'edit')
|
||||||
|
class ASNRangeEditView(generic.ObjectEditView):
|
||||||
|
queryset = ASNRange.objects.all()
|
||||||
|
form = forms.ASNRangeForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ASNRange, 'delete')
|
||||||
|
class ASNRangeDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = ASNRange.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = ASNRange.objects.all()
|
||||||
|
model_form = forms.ASNRangeImportForm
|
||||||
|
table = tables.ASNRangeTable
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = ASNRange.objects.annotate(
|
||||||
|
site_count=count_related(Site, 'asns')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ASNRangeFilterSet
|
||||||
|
table = tables.ASNRangeTable
|
||||||
|
form = forms.ASNRangeBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class ASNRangeBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = ASNRange.objects.annotate(
|
||||||
|
site_count=count_related(Site, 'asns')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ASNRangeFilterSet
|
||||||
|
table = tables.ASNRangeTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ASNs
|
# ASNs
|
||||||
#
|
#
|
||||||
|
@ -158,6 +158,7 @@ IPAM_MENU = Menu(
|
|||||||
MenuGroup(
|
MenuGroup(
|
||||||
label=_('ASNs'),
|
label=_('ASNs'),
|
||||||
items=(
|
items=(
|
||||||
|
get_model_item('ipam', 'asnrange', _('ASN Ranges')),
|
||||||
get_model_item('ipam', 'asn', _('ASNs')),
|
get_model_item('ipam', 'asn', _('ASNs')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
52
netbox/templates/ipam/asnrange.html
Normal file
52
netbox/templates/ipam/asnrange.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">ASN Range</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Range</td>
|
||||||
|
<td>{{ object.range_as_string }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tenant</td>
|
||||||
|
<td>
|
||||||
|
{% if object.tenant.group %}
|
||||||
|
{{ object.tenant.group|linkify }} /
|
||||||
|
{% endif %}
|
||||||
|
{{ object.tenant|linkify|placeholder }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user