diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 92d326e2c..fdc4b0a8a 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -34,7 +34,7 @@ class IPAMRootView(APIRootView): # class ASNRangeViewSet(NetBoxModelViewSet): - queryset = ASNRange.objects.prefetch_related('tenant').annotate( + queryset = ASNRange.objects.prefetch_related('tenant', 'rir').annotate( asn_count=count_related(ASN, 'range') ) serializer_class = serializers.ASNRangeSerializer @@ -251,8 +251,11 @@ class AvailableASNsView(ObjectValidationMixin, APIView): # Assign ASNs from the list of available IPs and copy VRF assignment from the parent for i, requested_asn in enumerate(requested_asns): - requested_asn['asn'] = available_asns[i] - requested_asn['range'] = asnrange.pk + requested_asn.update({ + 'rir': asnrange.rir.pk, + 'range': asnrange.pk, + 'asn': available_asns[i], + }) # Initialize the serializer with a list or a single object depending on what was requested context = {'request': request} diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 811ff7223..9695b9dc5 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -169,6 +169,16 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet): class ASNRangeFilterSet(OrganizationalModelFilterSet, TenancyFilterSet): + rir_id = django_filters.ModelMultipleChoiceFilter( + queryset=RIR.objects.all(), + label=_('RIR (ID)'), + ) + rir = django_filters.ModelMultipleChoiceFilter( + field_name='rir__slug', + queryset=RIR.objects.all(), + to_field_name='slug', + label=_('RIR (slug)'), + ) class Meta: model = ASNRange diff --git a/netbox/ipam/forms/bulk_edit.py b/netbox/ipam/forms/bulk_edit.py index 1ef6bf911..e74c6ea87 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -99,6 +99,11 @@ class RIRBulkEditForm(NetBoxModelBulkEditForm): class ASNRangeBulkEditForm(NetBoxModelBulkEditForm): + rir = DynamicModelChoiceField( + queryset=RIR.objects.all(), + required=False, + label=_('RIR') + ) tenant = DynamicModelChoiceField( queryset=Tenant.objects.all(), required=False @@ -110,9 +115,9 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm): model = ASNRange fieldsets = ( - (None, ('tenant', 'description')), + (None, ('rir', 'tenant', 'description')), ) - nullable_fields = ('date_added', 'description') + nullable_fields = ('description',) class ASNBulkEditForm(NetBoxModelBulkEditForm): diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index 1fc1bf962..216a937b8 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -89,6 +89,11 @@ class AggregateImportForm(NetBoxModelImportForm): class ASNRangeImportForm(NetBoxModelImportForm): + rir = CSVModelChoiceField( + queryset=RIR.objects.all(), + to_field_name='name', + help_text=_('Assigned RIR') + ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, @@ -98,7 +103,7 @@ class ASNRangeImportForm(NetBoxModelImportForm): class Meta: model = ASNRange - fields = ('name', 'slug', 'start', 'end', 'tenant', 'description', 'tags') + fields = ('name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', 'tags') class ASNImportForm(NetBoxModelImportForm): diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 491fd9be1..737f4d16e 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -119,9 +119,14 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = ASNRange fieldsets = ( (None, ('q', 'filter_id', 'tag')), - ('Range', ('start', 'end')), + ('Range', ('rir_id', 'start', 'end')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) + rir_id = DynamicModelMultipleChoiceField( + queryset=RIR.objects.all(), + required=False, + label=_('RIR') + ) start = forms.IntegerField( required=False ) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 44353f9fa..4c14b6bd4 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -130,16 +130,20 @@ class AggregateForm(TenancyForm, NetBoxModelForm): class ASNRangeForm(TenancyForm, NetBoxModelForm): + rir = DynamicModelChoiceField( + queryset=RIR.objects.all(), + label=_('RIR'), + ) slug = SlugField() fieldsets = ( - ('ASN Range', ('name', 'slug', 'start', 'end', 'description', 'tags')), + ('ASN Range', ('name', 'slug', 'rir', 'start', 'end', 'description', 'tags')), ('Tenancy', ('tenant_group', 'tenant')), ) class Meta: model = ASNRange fields = [ - 'name', 'slug', 'start', 'end', 'tenant_group', 'tenant', 'description', 'tags' + 'name', 'slug', 'rir', 'start', 'end', 'tenant_group', 'tenant', 'description', 'tags' ] diff --git a/netbox/ipam/migrations/0064_asnrange.py b/netbox/ipam/migrations/0064_asnrange.py index c6ba63e64..c15b7b504 100644 --- a/netbox/ipam/migrations/0064_asnrange.py +++ b/netbox/ipam/migrations/0064_asnrange.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.7 on 2023-02-25 19:49 +# Generated by Django 4.1.7 on 2023-02-25 21:18 from django.db import migrations, models import django.db.models.deletion @@ -28,6 +28,7 @@ class Migration(migrations.Migration): ('slug', models.SlugField(max_length=100, unique=True)), ('start', ipam.fields.ASNField()), ('end', ipam.fields.ASNField()), + ('rir', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='asn_ranges', to='ipam.rir')), ('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')), ], diff --git a/netbox/ipam/models/asns.py b/netbox/ipam/models/asns.py index 3ee33286d..76b7f8570 100644 --- a/netbox/ipam/models/asns.py +++ b/netbox/ipam/models/asns.py @@ -17,6 +17,12 @@ class ASN(PrimaryModel): 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. """ + rir = models.ForeignKey( + to='ipam.RIR', + on_delete=models.PROTECT, + related_name='asns', + verbose_name='RIR' + ) range = models.ForeignKey( to='ipam.ASNRange', on_delete=models.PROTECT, @@ -28,12 +34,6 @@ class ASN(PrimaryModel): verbose_name='ASN', help_text=_('32-bit autonomous system number') ) - rir = models.ForeignKey( - to='ipam.RIR', - on_delete=models.PROTECT, - related_name='asns', - verbose_name='RIR' - ) tenant = models.ForeignKey( to='tenancy.Tenant', on_delete=models.PROTECT, @@ -90,6 +90,12 @@ class ASNRange(OrganizationalModel): max_length=100, unique=True ) + rir = models.ForeignKey( + to='ipam.RIR', + on_delete=models.PROTECT, + related_name='asn_ranges', + verbose_name='RIR' + ) start = ASNField() end = ASNField() tenant = models.ForeignKey( diff --git a/netbox/ipam/tables/asn.py b/netbox/ipam/tables/asn.py index 9d860ad6b..2db3457af 100644 --- a/netbox/ipam/tables/asn.py +++ b/netbox/ipam/tables/asn.py @@ -15,6 +15,9 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable): name = tables.Column( linkify=True ) + rir = tables.Column( + linkify=True + ) tags = columns.TagColumn( url_name='ipam:asnrange_list' ) @@ -27,10 +30,10 @@ class ASNRangeTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = ASNRange fields = ( - 'pk', 'name', 'slug', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', 'tags', + 'pk', 'name', 'slug', 'rir', 'start', 'end', 'asn_count', 'tenant', 'tenant_group', 'description', 'tags', 'created', 'last_updated', 'actions', ) - default_columns = ('pk', 'name', 'start', 'end', 'tenant', 'asn_count', 'description') + default_columns = ('pk', 'name', 'rir', 'start', 'end', 'tenant', 'asn_count', 'description') class ASNTable(TenancyColumnsMixin, NetBoxTable): diff --git a/netbox/templates/ipam/asnrange.html b/netbox/templates/ipam/asnrange.html index a2194e96d..5a6b2ac49 100644 --- a/netbox/templates/ipam/asnrange.html +++ b/netbox/templates/ipam/asnrange.html @@ -15,6 +15,12 @@