diff --git a/Makefile b/Makefile
index 71cd4d0..0b22891 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
PYTHON_VER?=3.8
-NETBOX_VER?=v3.2.0
+NETBOX_VER?=v3.2.4
NAME=netbox-bgp
diff --git a/README.md b/README.md
index a3d0e60..70e4e69 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,14 @@
# NetBox BGP Plugin
[Netbox](https://github.com/netbox-community/netbox) plugin for BGP related objects documentation.
+## Features
+This plugin provide following Models:
+* AS Numbers (will be removed in 0.8.0)
+* BGP Communities
+* BGP Sessions
+* Routing Policy
+* Prefix Lists (new in 0.7.0)
+
## Compatibility
| | |
diff --git a/netbox_bgp/api/serializers.py b/netbox_bgp/api/serializers.py
index 495c678..9cc7a0a 100644
--- a/netbox_bgp/api/serializers.py
+++ b/netbox_bgp/api/serializers.py
@@ -11,7 +11,7 @@
from netbox_bgp.models import (
ASN, ASNStatusChoices, BGPSession, SessionStatusChoices, RoutingPolicy, BGPPeerGroup,
- Community, RoutingPolicyRule
+ Community, RoutingPolicyRule, PrefixList, PrefixListRule
)
@@ -161,6 +161,7 @@ def to_representation(self, instance):
)
return ret
+
class NestedBGPSessionSerializer(WritableNestedSerializer):
url = HyperlinkedIdentityField(view_name='plugins:netbox_bgp:bgpsession')
@@ -169,6 +170,7 @@ class Meta:
fields = ['id', 'url', 'name', 'description']
validators = []
+
class CommunitySerializer(NetBoxModelSerializer):
status = ChoiceField(choices=ASNStatusChoices, required=False)
tenant = NestedTenantSerializer(required=False, allow_null=True)
@@ -183,3 +185,15 @@ class RoutingPolicyRuleSerializer(NetBoxModelSerializer):
class Meta:
model = RoutingPolicyRule
fields = '__all__'
+
+
+class PrefixListSerializer(NetBoxModelSerializer):
+ class Meta:
+ model = PrefixList
+ fields = '__all__'
+
+
+class PrefixListRuleSerializer(NetBoxModelSerializer):
+ class Meta:
+ model = PrefixListRule
+ fields = '__all__'
diff --git a/netbox_bgp/api/urls.py b/netbox_bgp/api/urls.py
index 4aea08a..0861652 100644
--- a/netbox_bgp/api/urls.py
+++ b/netbox_bgp/api/urls.py
@@ -2,7 +2,7 @@
from .views import (
ASNViewSet, BGPSessionViewSet, RoutingPolicyViewSet, BGPPeerGroupViewSet,
- CommunityViewSet
+ CommunityViewSet, PrefixListViewSet
)
router = routers.DefaultRouter()
@@ -13,6 +13,7 @@
router.register('peer-group', BGPPeerGroupViewSet, 'peergroup')
router.register('bgppeergroup', BGPPeerGroupViewSet, 'bgppeergroup')
router.register('community', CommunityViewSet)
+router.register('prefix-list', PrefixListViewSet)
urlpatterns = router.urls
diff --git a/netbox_bgp/api/views.py b/netbox_bgp/api/views.py
index 7619f2a..58ff3dc 100644
--- a/netbox_bgp/api/views.py
+++ b/netbox_bgp/api/views.py
@@ -2,12 +2,12 @@
from .serializers import (
ASNSerializer, BGPSessionSerializer, RoutingPolicySerializer, BGPPeerGroupSerializer,
- CommunitySerializer
+ CommunitySerializer, PrefixListSerializer
)
-from netbox_bgp.models import ASN, BGPSession, RoutingPolicy, BGPPeerGroup, Community
+from netbox_bgp.models import ASN, BGPSession, RoutingPolicy, BGPPeerGroup, Community, PrefixList
from netbox_bgp.filters import (
ASNFilterSet, BGPSessionFilterSet, RoutingPolicyFilterSet, BGPPeerGroupFilterSet,
- CommunityFilterSet
+ CommunityFilterSet, PrefixListFilterSet
)
@@ -39,3 +39,9 @@ class CommunityViewSet(ModelViewSet):
queryset = Community.objects.all()
serializer_class = CommunitySerializer
filterset_class = CommunityFilterSet
+
+
+class PrefixListViewSet(ModelViewSet):
+ queryset = PrefixList.objects.all()
+ serializer_class = PrefixListSerializer
+ filterset_class = PrefixListFilterSet
diff --git a/netbox_bgp/choices.py b/netbox_bgp/choices.py
new file mode 100644
index 0000000..fcafab4
--- /dev/null
+++ b/netbox_bgp/choices.py
@@ -0,0 +1,73 @@
+from utilities.choices import ChoiceSet
+
+
+class ASNStatusChoices(ChoiceSet):
+
+ STATUS_ACTIVE = 'active'
+ STATUS_RESERVED = 'reserved'
+ STATUS_DEPRECATED = 'deprecated'
+
+ CHOICES = (
+ (STATUS_ACTIVE, 'Active', 'blue'),
+ (STATUS_RESERVED, 'Reserved', 'cyan'),
+ (STATUS_DEPRECATED, 'Deprecated', 'red'),
+ )
+
+
+class SessionStatusChoices(ChoiceSet):
+
+ STATUS_OFFLINE = 'offline'
+ STATUS_ACTIVE = 'active'
+ STATUS_PLANNED = 'planned'
+ STATUS_FAILED = 'failed'
+
+ CHOICES = (
+ (STATUS_OFFLINE, 'Offline', 'orange'),
+ (STATUS_ACTIVE, 'Active', 'green'),
+ (STATUS_PLANNED, 'Planned', 'cyan'),
+ (STATUS_FAILED, 'Failed', 'red'),
+ )
+
+
+class ActionChoices(ChoiceSet):
+
+ CHOICES = [
+ ('permit', 'Permit', 'green'),
+ ('deny', 'Deny', 'red'),
+ ]
+
+
+class AFISAFIChoices(ChoiceSet):
+ AFISAFI_IPV4_UNICAST = 'ipv4-unicast'
+ AFISAFI_IPV4_MULTICAST = 'ipv4-multicast'
+ AFISAFI_IPV4_FLOWSPEC = 'ipv4-flowspec'
+
+ AFISAFI_IPV6_UNICAST = 'ipv6-unicast'
+ AFISAFI_IPV6_MULTICAST = 'ipv6-multicast'
+ AFISAFI_IPV6_FLOWSPEC = 'ipv6-flowspec'
+
+ AFISAFI_L2VPN_VPLS = 'l2vpn-vpls'
+ AFISAFI_L2VPN_EVPN = 'l2vpn-evpn'
+
+ AFISAFI_VPNV4_UNICAST = 'vpnv4-unicast'
+ AFISAFI_VPNV4_MULTICAST = 'vpnv4-multicast'
+ AFISAFI_VPNV4_FLOWSPEC = 'vpnv4-flowspec'
+
+ AFISAFI_VPNV6_UNICAST = 'vpnv6-unicast'
+ AFISAFI_VPNV6_MULTICAST = 'vpnv6-multicast'
+ AFISAFI_VPNV6_FLOWSPEC = 'vpnv6-flowspec'
+
+ CHOICES = (
+
+ )
+
+
+class IPAddressFamilyChoices(ChoiceSet):
+
+ FAMILY_4 = 4
+ FAMILY_6 = 6
+
+ CHOICES = (
+ (FAMILY_4, 'IPv4'),
+ (FAMILY_6, 'IPv6'),
+ )
diff --git a/netbox_bgp/filters.py b/netbox_bgp/filters.py
index f58ff43..3a71238 100644
--- a/netbox_bgp/filters.py
+++ b/netbox_bgp/filters.py
@@ -4,7 +4,7 @@
from netaddr.core import AddrFormatError
from extras.filters import TagFilter
-from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup
+from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, PrefixList
from ipam.models import IPAddress
from dcim.models import Device
@@ -217,3 +217,25 @@ def search(self, queryset, name, value):
| Q(description__icontains=value)
)
return queryset.filter(qs_filter)
+
+
+class PrefixListFilterSet(django_filters.FilterSet):
+ q = django_filters.CharFilter(
+ method='search',
+ label='Search',
+ )
+ tag = TagFilter()
+
+ class Meta:
+ model = PrefixList
+ fields = ['name', 'description']
+
+ def search(self, queryset, name, value):
+ """Perform the filtered search."""
+ if not value.strip():
+ return queryset
+ qs_filter = (
+ Q(name__icontains=value)
+ | Q(description__icontains=value)
+ )
+ return queryset.filter(qs_filter)
diff --git a/netbox_bgp/forms.py b/netbox_bgp/forms.py
index 9f8ca94..ea55c84 100644
--- a/netbox_bgp/forms.py
+++ b/netbox_bgp/forms.py
@@ -19,7 +19,9 @@
from .models import (
ASN, ASNStatusChoices, Community, BGPSession,
- SessionStatusChoices, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule
+ SessionStatusChoices, RoutingPolicy, BGPPeerGroup,
+ RoutingPolicyRule, PrefixList, PrefixListRule
+
)
@@ -421,7 +423,7 @@ class RoutingPolicyForm(NetBoxModelForm):
class Meta:
model = RoutingPolicy
- fields = ['name', 'description']
+ fields = ['name', 'description', 'tags']
class BGPPeerGroupFilterForm(NetBoxModelFilterSetForm):
@@ -460,19 +462,24 @@ class Meta:
class RoutingPolicyRuleForm(NetBoxModelForm):
+ continue_entry = forms.IntegerField(
+ required=False,
+ label='Continue',
+ help_text='Null for disable, 0 to next entry, or any sequence number'
+ )
match_community = DynamicModelMultipleChoiceField(
queryset=Community.objects.all(),
required=False,
)
- match_ip = DynamicModelMultipleChoiceField(
- queryset=Prefix.objects.all(),
+ match_ip_address = DynamicModelMultipleChoiceField(
+ queryset=PrefixList.objects.all(),
required=False,
- label='Match Prefix',
+ label='Match IP address Prefix lists',
)
- match_ip_cond = forms.JSONField(
- label='Match filtered prefixes',
- help_text='Filter for Prefixes, e.g., {"site__name": "site1", "tenant__name": "tenant1"}',
+ match_ipv6_address = DynamicModelMultipleChoiceField(
+ queryset=PrefixList.objects.all(),
required=False,
+ label='Match IPv6 address Prefix lists',
)
match_custom = forms.JSONField(
label='Custom Match',
@@ -488,7 +495,57 @@ class RoutingPolicyRuleForm(NetBoxModelForm):
class Meta:
model = RoutingPolicyRule
fields = [
- 'routing_policy', 'index', 'action', 'match_community',
- 'match_ip', 'match_ip_cond', 'match_custom',
+ 'routing_policy', 'index', 'action', 'continue_entry', 'match_community',
+ 'match_ip_address', 'match_ipv6_address', 'match_custom',
'set_actions', 'description',
]
+
+
+class PrefixListFilterForm(NetBoxModelFilterSetForm):
+ model = PrefixList
+ q = forms.CharField(
+ required=False,
+ label='Search'
+ )
+
+ tag = TagFilterField(model)
+
+
+class PrefixListForm(NetBoxModelForm):
+ tags = DynamicModelMultipleChoiceField(
+ queryset=Tag.objects.all(),
+ required=False
+ )
+
+ class Meta:
+ model = PrefixList
+ fields = ['name', 'description', 'tags']
+
+
+class PrefixListRuleForm(NetBoxModelForm):
+ prefix = DynamicModelChoiceField(
+ queryset=Prefix.objects.all(),
+ required=False,
+ help_text='NetBox Prefix Object',
+ )
+ prefix_custom = IPNetworkFormField(
+ required=False,
+ label='Prefix',
+ help_text='Just IP field for define special prefix like 0.0.0.0/0',
+ )
+ ge = forms.IntegerField(
+ label='Greater than or equal to',
+ required=False,
+ )
+ le = forms.IntegerField(
+ label='Less than or equal to',
+ required=False,
+ )
+
+ class Meta:
+ model = PrefixListRule
+ fields = [
+ 'prefix_list', 'index',
+ 'action', 'prefix', 'prefix_custom',
+ 'ge', 'le'
+ ]
diff --git a/netbox_bgp/migrations/0023_netbox_bgp.py b/netbox_bgp/migrations/0023_netbox_bgp.py
new file mode 100644
index 0000000..ff9e063
--- /dev/null
+++ b/netbox_bgp/migrations/0023_netbox_bgp.py
@@ -0,0 +1,58 @@
+# Generated by Django 4.0.4 on 2022-08-18 13:47
+
+import django.core.serializers.json
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import ipam.fields
+import taggit.managers
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('extras', '0073_journalentry_tags_custom_fields'),
+ ('ipam', '0057_created_datetimefield'),
+ ('netbox_bgp', '0022_netbox_bgp'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PrefixList',
+ 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=django.core.serializers.json.DjangoJSONEncoder)),
+ ('name', models.CharField(max_length=100)),
+ ('description', models.CharField(blank=True, max_length=200)),
+ ('family', models.CharField(max_length=10)),
+ ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+ ],
+ options={
+ 'verbose_name_plural': 'Prefix Lists',
+ 'unique_together': {('name', 'description', 'family')},
+ },
+ ),
+ migrations.CreateModel(
+ name='PrefixListRule',
+ 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=django.core.serializers.json.DjangoJSONEncoder)),
+ ('index', models.PositiveIntegerField()),
+ ('action', models.CharField(max_length=30)),
+ ('prefix_custom', ipam.fields.IPNetworkField(blank=True, null=True)),
+ ('ge', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
+ ('le', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
+ ('prefix', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='ipam.prefix')),
+ ('prefix_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prefrules', to='netbox_bgp.prefixlist')),
+ ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+ ],
+ options={
+ 'ordering': ('prefix_list', 'index'),
+ 'unique_together': {('prefix_list', 'index')},
+ },
+ ),
+ ]
diff --git a/netbox_bgp/migrations/0024_netbox_bgp.py b/netbox_bgp/migrations/0024_netbox_bgp.py
new file mode 100644
index 0000000..42ef68e
--- /dev/null
+++ b/netbox_bgp/migrations/0024_netbox_bgp.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.0.4 on 2022-08-19 06:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('netbox_bgp', '0023_netbox_bgp'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='routingpolicyrule',
+ name='match_ip',
+ ),
+ migrations.RemoveField(
+ model_name='routingpolicyrule',
+ name='match_ip_cond',
+ ),
+ migrations.AddField(
+ model_name='routingpolicyrule',
+ name='continue_entry',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='routingpolicyrule',
+ name='match_ip_address',
+ field=models.ManyToManyField(blank=True, related_name='+', to='netbox_bgp.prefixlist'),
+ ),
+ migrations.AddField(
+ model_name='routingpolicyrule',
+ name='match_ipv6_address',
+ field=models.ManyToManyField(blank=True, related_name='+', to='netbox_bgp.prefixlist'),
+ ),
+ ]
diff --git a/netbox_bgp/migrations/0025_netbox_bgp.py b/netbox_bgp/migrations/0025_netbox_bgp.py
new file mode 100644
index 0000000..9f07a15
--- /dev/null
+++ b/netbox_bgp/migrations/0025_netbox_bgp.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.0.4 on 2022-08-19 11:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('netbox_bgp', '0024_netbox_bgp'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='routingpolicyrule',
+ name='match_ip_address',
+ field=models.ManyToManyField(blank=True, related_name='plrules', to='netbox_bgp.prefixlist'),
+ ),
+ migrations.AlterField(
+ model_name='routingpolicyrule',
+ name='match_ipv6_address',
+ field=models.ManyToManyField(blank=True, related_name='plrules6', to='netbox_bgp.prefixlist'),
+ ),
+ ]
diff --git a/netbox_bgp/models.py b/netbox_bgp/models.py
index cc592e4..a512502 100644
--- a/netbox_bgp/models.py
+++ b/netbox_bgp/models.py
@@ -1,51 +1,17 @@
from django.urls import reverse
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
-from django.core.exceptions import FieldError
+from django.core.exceptions import FieldError, ValidationError
from django.conf import settings
from taggit.managers import TaggableManager
-from utilities.choices import ChoiceSet
from netbox.models import NetBoxModel
from netbox.models.features import ChangeLoggingMixin
+from ipam.fields import IPNetworkField
from ipam.models import Prefix
-
-class ASNStatusChoices(ChoiceSet):
-
- STATUS_ACTIVE = 'active'
- STATUS_RESERVED = 'reserved'
- STATUS_DEPRECATED = 'deprecated'
-
- CHOICES = (
- (STATUS_ACTIVE, 'Active', 'blue'),
- (STATUS_RESERVED, 'Reserved', 'cyan'),
- (STATUS_DEPRECATED, 'Deprecated', 'red'),
- )
-
-
-class SessionStatusChoices(ChoiceSet):
-
- STATUS_OFFLINE = 'offline'
- STATUS_ACTIVE = 'active'
- STATUS_PLANNED = 'planned'
- STATUS_FAILED = 'failed'
-
- CHOICES = (
- (STATUS_OFFLINE, 'Offline', 'orange'),
- (STATUS_ACTIVE, 'Active', 'green'),
- (STATUS_PLANNED, 'Planned', 'cyan'),
- (STATUS_FAILED, 'Failed', 'red'),
- )
-
-
-class ActionChoices(ChoiceSet):
-
- CHOICES = [
- ('permit', 'Permit', 'green'),
- ('deny', 'Deny', 'red'),
- ]
+from .choices import IPAddressFamilyChoices, ASNStatusChoices, SessionStatusChoices, ActionChoices
class ASNGroup(ChangeLoggingMixin, models.Model):
@@ -318,6 +284,98 @@ def get_absolute_url(self):
return reverse('plugins:netbox_bgp:bgpsession', args=[self.pk])
+class PrefixList(NetBoxModel):
+ """
+ """
+ name = models.CharField(
+ max_length=100
+ )
+ description = models.CharField(
+ max_length=200,
+ blank=True
+ )
+ family = models.CharField(
+ max_length=10,
+ choices=IPAddressFamilyChoices
+ )
+
+ class Meta:
+ verbose_name_plural = 'Prefix Lists'
+ unique_together = ['name', 'description', 'family']
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse('plugins:netbox_bgp:prefixlist', args=[self.pk])
+
+
+class PrefixListRule(NetBoxModel):
+ """
+ """
+ prefix_list = models.ForeignKey(
+ to=PrefixList,
+ on_delete=models.CASCADE,
+ related_name='prefrules'
+ )
+ index = models.PositiveIntegerField()
+ action = models.CharField(
+ max_length=30,
+ choices=ActionChoices
+ )
+ prefix = models.ForeignKey(
+ to='ipam.Prefix',
+ blank=True,
+ null=True,
+ related_name='+',
+ on_delete=models.CASCADE,
+ )
+ prefix_custom = IPNetworkField(
+ blank=True,
+ null=True,
+ )
+ ge = models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[MinValueValidator(0), MaxValueValidator(128)]
+ )
+ le = models.PositiveSmallIntegerField(
+ blank=True,
+ null=True,
+ validators=[MinValueValidator(0), MaxValueValidator(128)]
+ )
+
+ class Meta:
+ ordering = ('prefix_list', 'index')
+ unique_together = ('prefix_list', 'index')
+
+ @property
+ def network(self):
+ return self.prefix_custom or self.prefix
+
+ def __str__(self):
+ return f'{self.prefix_list}: Rule {self.index}'
+
+ def get_absolute_url(self):
+ return reverse('plugins:netbox_bgp:prefixlistrule', args=[self.pk])
+
+ def get_action_color(self):
+ return ActionChoices.colors.get(self.action)
+
+ def clean(self):
+ super().clean()
+ # make sure that only one field is setted
+ if self.prefix and self.prefix_custom:
+ raise ValidationError(
+ {'prefix': 'Cannot set both fields'}
+ )
+ # at least one fields must be setted
+ if self.prefix is None and self.prefix_custom is None:
+ raise ValidationError(
+ {'prefix': 'Cannot set both fields to Null'}
+ )
+
+
class RoutingPolicyRule(NetBoxModel):
routing_policy = models.ForeignKey(
to=RoutingPolicy,
@@ -333,19 +391,24 @@ class RoutingPolicyRule(NetBoxModel):
max_length=500,
blank=True
)
+ continue_entry = models.PositiveIntegerField(
+ blank=True,
+ null=True
+ )
match_community = models.ManyToManyField(
to=Community,
blank=True,
related_name='+'
)
- match_ip = models.ManyToManyField(
- to='ipam.Prefix',
+ match_ip_address = models.ManyToManyField(
+ to=PrefixList,
blank=True,
- related_name='+',
+ related_name='plrules',
)
- match_ip_cond = models.JSONField(
+ match_ipv6_address = models.ManyToManyField(
+ to=PrefixList,
blank=True,
- null=True,
+ related_name='plrules6',
)
match_custom = models.JSONField(
blank=True,
@@ -367,16 +430,7 @@ def get_absolute_url(self):
return reverse('plugins:netbox_bgp:routingpolicyrule', args=[self.pk])
def get_action_color(self):
- return ActionChoices.colors.get(self.action)
-
- def get_ip_conditions(self):
- queryset = Prefix.objects.none()
- if self.match_ip_cond and self.match_ip_cond != {}:
- try:
- queryset = Prefix.objects.filter(**self.match_ip_cond)
- except FieldError:
- pass
- return queryset
+ return ActionChoices.colors.get(self.action)
def get_match_custom(self):
# some kind of ckeck?
@@ -393,14 +447,17 @@ def match_statements(self):
{'community': list(self.match_community.all().values_list('value', flat=True))}
)
result.update(
- {'ip address': [str(prefix) for prefix in self.match_ip.all().values_list('prefix', flat=True)]}
+ {'ip address': [str(prefix_list) for prefix_list in self.match_ip_address.all().values_list('name', flat=True)]}
+ )
+ result.update(
+ {'ipv6 address': [str(prefix_list) for prefix_list in self.match_ipv6_address.all().values_list('name', flat=True)]}
)
- matched_ip = self.get_ip_conditions()
- result['ip address'].extend([str(prefix) for prefix in matched_ip.values_list('prefix', flat=True)])
+
custom_match = self.get_match_custom()
# update community from custom
result['community'].extend(custom_match.get('community', []))
result['ip address'].extend(custom_match.get('ip address', []))
+ result['ipv6 address'].extend(custom_match.get('ipv6 address', []))
# remove empty matches
result = {k: v for k, v in result.items() if v}
return result
diff --git a/netbox_bgp/navigation.py b/netbox_bgp/navigation.py
index 712e2bf..f1839bd 100644
--- a/netbox_bgp/navigation.py
+++ b/netbox_bgp/navigation.py
@@ -58,6 +58,20 @@
),
),
),
+ PluginMenuItem(
+ link='plugins:netbox_bgp:prefixlist_list',
+ link_text='Prefix Lists',
+ permissions=['netbox_bgp.view_prefixlist'],
+ buttons=(
+ PluginMenuButton(
+ link='plugins:netbox_bgp:prefixlist_add',
+ title='Prefix Lists',
+ icon_class='mdi mdi-plus-thick',
+ color=ButtonColorChoices.GREEN,
+ permissions=['netbox_bgp.add_prefixlist'],
+ ),
+ ),
+ ),
PluginMenuItem(
link='plugins:netbox_bgp:bgppeergroup_list',
link_text='Peer Groups',
diff --git a/netbox_bgp/tables.py b/netbox_bgp/tables.py
index 509c4ee..6fe3c93 100644
--- a/netbox_bgp/tables.py
+++ b/netbox_bgp/tables.py
@@ -5,7 +5,7 @@
from netbox.tables import NetBoxTable
from netbox.tables.columns import ChoiceFieldColumn, TagColumn
-from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule
+from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule, PrefixList, PrefixListRule
AVAILABLE_LABEL = mark_safe('Available')
COL_TENANT = """
@@ -138,3 +138,32 @@ class Meta(NetBoxTable.Meta):
'pk', 'routing_policy', 'index', 'match_statements',
'set_statements', 'action', 'description'
)
+
+
+class PrefixListTable(NetBoxTable):
+ name = tables.LinkColumn()
+
+ class Meta(NetBoxTable.Meta):
+ model = PrefixList
+ fields = ('pk', 'name', 'description')
+
+
+class PrefixListRuleTable(NetBoxTable):
+ prefix_list = tables.Column(
+ linkify=True
+ )
+ index = tables.Column(
+ linkify=True
+ )
+ action = ChoiceFieldColumn()
+ network = tables.Column(
+ verbose_name='Prefix',
+ linkify=True,
+ )
+
+ class Meta(NetBoxTable.Meta):
+ model = PrefixListRule
+ fields = (
+ 'pk', 'prefix_list', 'index',
+ 'action', 'network', 'ge', 'le'
+ )
diff --git a/netbox_bgp/templates/netbox_bgp/asn_list.html b/netbox_bgp/templates/netbox_bgp/asn_list.html
new file mode 100644
index 0000000..98154c2
--- /dev/null
+++ b/netbox_bgp/templates/netbox_bgp/asn_list.html
@@ -0,0 +1,17 @@
+{% extends 'generic/object_list.html' %}
+
+{% block subtitle %}
+
+
Warning
+ ASN model will be removed in next release. Please migrate plugin ASN objects to NetBox native ASN objects.
+
+{% endblock %}
+{% block extra_controls %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/netbox_bgp/templates/netbox_bgp/asn_migrate.html b/netbox_bgp/templates/netbox_bgp/asn_migrate.html
new file mode 100644
index 0000000..2c511d4
--- /dev/null
+++ b/netbox_bgp/templates/netbox_bgp/asn_migrate.html
@@ -0,0 +1,35 @@
+{% extends 'base/layout.html' %}
+{% load helpers %}
+{% load render_table from django_tables2 %}
+
+
+{% block title %}Migrate {{ table.rows|length }} ASNs?{% endblock %}
+
+{% block content %}
+
+
+
Confirm Migration
+
+ Warning: The following operation will create {{ table.rows|length }}
+ NetBox ASNs.
+ {% block message_extra %}{% endblock %}
+
+
+
+
+ {% render_table table 'inc/table.html' %}
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/netbox_bgp/templates/netbox_bgp/bgpsession_list.html b/netbox_bgp/templates/netbox_bgp/bgpsession_list.html
new file mode 100644
index 0000000..98154c2
--- /dev/null
+++ b/netbox_bgp/templates/netbox_bgp/bgpsession_list.html
@@ -0,0 +1,17 @@
+{% extends 'generic/object_list.html' %}
+
+{% block subtitle %}
+
+
Warning
+ ASN model will be removed in next release. Please migrate plugin ASN objects to NetBox native ASN objects.
+
+{% endblock %}
+{% block extra_controls %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/netbox_bgp/templates/netbox_bgp/community.html b/netbox_bgp/templates/netbox_bgp/community.html
index 38806a7..57eb450 100644
--- a/netbox_bgp/templates/netbox_bgp/community.html
+++ b/netbox_bgp/templates/netbox_bgp/community.html
@@ -5,7 +5,7 @@
{% load plugins %}
{% block breadcrumbs %}
-AS Communities
+Communities
{% endblock %}
{% block controls %}
diff --git a/netbox_bgp/templates/netbox_bgp/prefixlist.html b/netbox_bgp/templates/netbox_bgp/prefixlist.html
new file mode 100644
index 0000000..03598d5
--- /dev/null
+++ b/netbox_bgp/templates/netbox_bgp/prefixlist.html
@@ -0,0 +1,94 @@
+{% extends 'generic/object.html' %}
+{% load buttons %}
+{% load custom_links %}
+{% load helpers %}
+{% load plugins %}
+{% load render_table from django_tables2 %}
+
+{% block breadcrumbs %}
+Prefix Lists
+{% endblock %}
+{% block controls %}
+
+ {% if perms.netbox_bgp.change_prefixlist %}
+
+ Rule
+
+ {% endif %}
+ {% if perms.netbox_bgp.change_policy %}
+
+ Edit
+
+ {% endif %}
+ {% if perms.netbox_bgp.delete_policy %}
+
+ Delete
+
+ {% endif %}
+
+{% endblock controls %}
+{% block tabs %}
+
+{% endblock tabs %}
+
+{% block content %}
+
+
+
+
+
+
+
+ name |
+ {{ object.name }} |
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_left_page object %}
+
+
+
+
+
+ {% render_table rprules_table 'inc/table.html' %}
+
+ {% plugin_right_page object %}
+
+
+
+
+
+
+
+
+ {% render_table rules_table 'inc/table.html' %}
+
+ {% plugin_full_width_page object %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/netbox_bgp/templates/netbox_bgp/prefixlistrule.html b/netbox_bgp/templates/netbox_bgp/prefixlistrule.html
new file mode 100644
index 0000000..ca47b67
--- /dev/null
+++ b/netbox_bgp/templates/netbox_bgp/prefixlistrule.html
@@ -0,0 +1,55 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load static %}
+
+{% block content %}
+
+
+
+
+
+
+
+ Prefix List |
+
+ {{ object.prefix_list }}
+ |
+
+
+ Index |
+ {{ object.index }} |
+
+
+ Action |
+ {% badge object.get_action_display bg_color=object.get_action_color %} |
+
+
+ Prefix |
+ {% if object.network.get_absolute_url %}
+
+ {{ object.network }}
+ |
+ {% else %}
+ {{ object.network|placeholder }} |
+ {% endif %}
+
+ {% if object.ge %}
+
+ Greater than or equal to |
+ {{ object.ge|placeholder }} |
+
+ {% endif %}
+ {% if object.le %}
+
+ Less than or equal to |
+ {{ object.le|placeholder }} |
+
+ {% endif %}
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% include 'inc/panels/tags.html' %}
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/netbox_bgp/urls.py b/netbox_bgp/urls.py
index b32ac79..d05e2ec 100644
--- a/netbox_bgp/urls.py
+++ b/netbox_bgp/urls.py
@@ -1,68 +1,72 @@
from django.urls import path
from netbox.views.generic import ObjectChangeLogView
-from .models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule
+from .models import ASN, BGPSession, Community, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule, PrefixList, PrefixListRule
-from .views import (
- ASNListView, ASNView, ASNBulkDeleteView, ASNEditView, ASNBulkEditView,
- ASNDeleteView, CommunityListView, CommunityEditView, CommunityView,
- CommunityBulkEditView, CommunityBulkDeleteView, CommunityDeleteView,
- BGPSessionListView, BGPSessionEditView, BGPSessionBulkDeleteView,
- BGPSessionView, BGPSessionDeleteView, BGPSessionAddView,
- RoutingPolicyListView, RoutingPolicyEditView, RoutingPolicyBulkDeleteView,
- RoutingPolicyView, RoutingPolicyDeleteView, BGPPeerGroupListView,
- BGPPeerGroupEditView, BGPPeerGroupBulkDeleteView, BGPPeerGroupView,
- BGPPeerGroupDeleteView, RoutingPolicyRuleEditView, RoutingPolicyRuleDeleteView,
- RoutingPolicyRuleView, RoutingPolicyRuleListView
-)
+from . import views
urlpatterns = [
- path('asn/', ASNListView.as_view(), name='asn_list'),
- path('asn/add/', ASNEditView.as_view(), name='asn_add'),
- path('asn/edit/', ASNBulkEditView.as_view(), name='asn_bulk_edit'),
- path('asn/delete/', ASNBulkDeleteView.as_view(), name='asn_bulk_delete'),
- path('asn//', ASNView.as_view(), name='asn'),
- path('asn//edit/', ASNEditView.as_view(), name='asn_edit'),
- path('asn//delete/', ASNDeleteView.as_view(), name='asn_delete'),
+ path('asn/', views.ASNListView.as_view(), name='asn_list'),
+ path('asn/add/', views.ASNEditView.as_view(), name='asn_add'),
+ path('asn/edit/', views.ASNBulkEditView.as_view(), name='asn_bulk_edit'),
+ path('asn/delete/', views.ASNBulkDeleteView.as_view(), name='asn_bulk_delete'),
+ path('asn//', views.ASNView.as_view(), name='asn'),
+ path('asn//edit/', views.ASNEditView.as_view(), name='asn_edit'),
+ path('asn//delete/', views.ASNDeleteView.as_view(), name='asn_delete'),
path('asn//changelog/', ObjectChangeLogView.as_view(), name='asn_changelog', kwargs={'model': ASN}),
+ path('asn/migrate/', views.ASNMigrateView.as_view(), name='asn_migrate'),
# Community
- path('community/', CommunityListView.as_view(), name='community_list'),
- path('community/add/', CommunityEditView.as_view(), name='community_add'),
- path('community/edit/', CommunityBulkEditView.as_view(), name='community_bulk_edit'),
- path('community/delete/', CommunityBulkDeleteView.as_view(), name='community_bulk_delete'),
- path('community//', CommunityView.as_view(), name='community'),
- path('community//edit/', CommunityEditView.as_view(), name='community_edit'),
- path('community//delete/', CommunityDeleteView.as_view(), name='community_delete'),
+ path('community/', views.CommunityListView.as_view(), name='community_list'),
+ path('community/add/', views.CommunityEditView.as_view(), name='community_add'),
+ path('community/edit/', views.CommunityBulkEditView.as_view(), name='community_bulk_edit'),
+ path('community/delete/', views.CommunityBulkDeleteView.as_view(), name='community_bulk_delete'),
+ path('community//', views.CommunityView.as_view(), name='community'),
+ path('community//edit/', views.CommunityEditView.as_view(), name='community_edit'),
+ path('community//delete/', views.CommunityDeleteView.as_view(), name='community_delete'),
path('community//changelog/', ObjectChangeLogView.as_view(), name='community_changelog', kwargs={'model': Community}),
# Sessions
- path('session/', BGPSessionListView.as_view(), name='bgpsession_list'),
- path('session/add/', BGPSessionAddView.as_view(), name='bgpsession_add'),
- path('session/delete/', BGPSessionBulkDeleteView.as_view(), name='bgpsession_bulk_delete'),
- path('session//', BGPSessionView.as_view(), name='bgpsession'),
- path('session//edit/', BGPSessionEditView.as_view(), name='bgpsession_edit'),
- path('session//delete/', BGPSessionDeleteView.as_view(), name='bgpsession_delete'),
+ path('session/', views.BGPSessionListView.as_view(), name='bgpsession_list'),
+ path('session/add/', views.BGPSessionAddView.as_view(), name='bgpsession_add'),
+ path('session/delete/', views.BGPSessionBulkDeleteView.as_view(), name='bgpsession_bulk_delete'),
+ path('session//', views.BGPSessionView.as_view(), name='bgpsession'),
+ path('session//edit/', views.BGPSessionEditView.as_view(), name='bgpsession_edit'),
+ path('session//delete/', views.BGPSessionDeleteView.as_view(), name='bgpsession_delete'),
path('session//changelog/', ObjectChangeLogView.as_view(), name='bgpsession_changelog', kwargs={'model': BGPSession}),
# Routing Policies
-
- path('routing-policy/', RoutingPolicyListView.as_view(), name='routingpolicy_list'),
- path('routing-policy/add/', RoutingPolicyEditView.as_view(), name='routingpolicy_add'),
- path('routing-policy/delete/', RoutingPolicyBulkDeleteView.as_view(), name='routingpolicy_bulk_delete'),
- path('routing-policy//', RoutingPolicyView.as_view(), name='routingpolicy'),
- path('routing-policy//edit/', RoutingPolicyEditView.as_view(), name='routingpolicy_edit'),
- path('routing-policy//delete/', RoutingPolicyDeleteView.as_view(), name='routingpolicy_delete'),
+ path('routing-policy/', views.RoutingPolicyListView.as_view(), name='routingpolicy_list'),
+ path('routing-policy/add/', views.RoutingPolicyEditView.as_view(), name='routingpolicy_add'),
+ path('routing-policy/delete/', views.RoutingPolicyBulkDeleteView.as_view(), name='routingpolicy_bulk_delete'),
+ path('routing-policy//', views.RoutingPolicyView.as_view(), name='routingpolicy'),
+ path('routing-policy//edit/', views.RoutingPolicyEditView.as_view(), name='routingpolicy_edit'),
+ path('routing-policy//delete/', views.RoutingPolicyDeleteView.as_view(), name='routingpolicy_delete'),
path('routing-policy//changelog/', ObjectChangeLogView.as_view(), name='routingpolicy_changelog', kwargs={'model': RoutingPolicy}),
# Peer Groups
- path('peer-group/', BGPPeerGroupListView.as_view(), name='bgppeergroup_list'),
- path('peer-group/add/', BGPPeerGroupEditView.as_view(), name='bgppeergroup_add'),
- path('peer-group/delete/', BGPPeerGroupBulkDeleteView.as_view(), name='bgppeergroup_bulk_delete'),
- path('peer-group//', BGPPeerGroupView.as_view(), name='bgppeergroup'),
- path('peer-group//edit/', BGPPeerGroupEditView.as_view(), name='bgppeergroup_edit'),
- path('peer-group//delete/', BGPPeerGroupDeleteView.as_view(), name='bgppeergroup_delete'),
+ path('peer-group/', views.BGPPeerGroupListView.as_view(), name='bgppeergroup_list'),
+ path('peer-group/add/', views.BGPPeerGroupEditView.as_view(), name='bgppeergroup_add'),
+ path('peer-group/delete/', views.BGPPeerGroupBulkDeleteView.as_view(), name='bgppeergroup_bulk_delete'),
+ path('peer-group//', views.BGPPeerGroupView.as_view(), name='bgppeergroup'),
+ path('peer-group//edit/', views.BGPPeerGroupEditView.as_view(), name='bgppeergroup_edit'),
+ path('peer-group//delete/', views.BGPPeerGroupDeleteView.as_view(), name='bgppeergroup_delete'),
path('peer-group//changelog/', ObjectChangeLogView.as_view(), name='bgppeergroup_changelog', kwargs={'model': BGPPeerGroup}),
# Routing Policy Rules
- path('routing-policy-rule/', RoutingPolicyRuleListView.as_view(), name='routingpolicyrule_list'),
- path('routing-policy-rule/add/', RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_add'),
- path('routing-policy-rule//', RoutingPolicyRuleView.as_view(), name='routingpolicyrule'),
- path('routing-policy-rule//edit/', RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_edit'),
- path('routing-policy-rule//delete/', RoutingPolicyRuleDeleteView.as_view(), name='routingpolicyrule_delete'),
+ path('routing-policy-rule/', views.RoutingPolicyRuleListView.as_view(), name='routingpolicyrule_list'),
+ path('routing-policy-rule/add/', views.RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_add'),
+ path('routing-policy-rule//', views.RoutingPolicyRuleView.as_view(), name='routingpolicyrule'),
+ path('routing-policy-rule//edit/', views.RoutingPolicyRuleEditView.as_view(), name='routingpolicyrule_edit'),
+ path('routing-policy-rule//delete/', views.RoutingPolicyRuleDeleteView.as_view(), name='routingpolicyrule_delete'),
path('routing-policy-rule//changelog/', ObjectChangeLogView.as_view(), name='routingpolicyrule_changelog', kwargs={'model': RoutingPolicyRule}),
+ # Prefix Lists
+ path('prefix-list/', views.PrefixListListView.as_view(), name='prefixlist_list'),
+ path('prefix-list/add/', views.PrefixListEditView.as_view(), name='prefixlist_add'),
+ path('prefix-list/delete/', views.PrefixListBulkDeleteView.as_view(), name='prefixlist_bulk_delete'),
+ path('prefix-list//', views.PrefixListView.as_view(), name='prefixlist'),
+ path('prefix-list//edit/', views.PrefixListEditView.as_view(), name='prefixlist_edit'),
+ path('prefix-list//delete/', views.PrefixListDeleteView.as_view(), name='prefixlist_delete'),
+ path('prefix-list//changelog/', ObjectChangeLogView.as_view(), name='prefixlist_changelog', kwargs={'model': PrefixList}),
+ # Prefix List Rules
+ path('prefix-list-rule/', views.PrefixListRuleListView.as_view(), name='prefixlistrule_list'),
+ path('prefix-list-rule/add/', views.PrefixListRuleEditView.as_view(), name='prefixlistrule_add'),
+ path('prefix-list-rule//', views.PrefixListRuleView.as_view(), name='prefixlistrule'),
+ path('prefix-list-rule//edit/', views.PrefixListRuleEditView.as_view(), name='prefixlistrule_edit'),
+ path('prefix-list-rule//delete/', views.PrefixListRuleDeleteView.as_view(), name='prefixlistrule_delete'),
+ path('prefix-list-rule//changelog/', ObjectChangeLogView.as_view(), name='prefixlistrule_changelog', kwargs={'model': PrefixListRule}),
]
diff --git a/netbox_bgp/version.py b/netbox_bgp/version.py
index 22049ab..49e0fc1 100644
--- a/netbox_bgp/version.py
+++ b/netbox_bgp/version.py
@@ -1 +1 @@
-__version__ = "0.6.2"
+__version__ = "0.7.0"
diff --git a/netbox_bgp/views.py b/netbox_bgp/views.py
index d7abb73..379219e 100644
--- a/netbox_bgp/views.py
+++ b/netbox_bgp/views.py
@@ -1,29 +1,32 @@
+
from django.db.models import Q
+from django.contrib import messages
+from django.shortcuts import get_object_or_404, redirect, render, reverse
+from django.utils.text import slugify
from netbox.views import generic
+from ipam.models import RIR
+from ipam.models import ASN as CoreASN
-from .filters import (
- ASNFilterSet, CommunityFilterSet, BGPSessionFilterSet,
- RoutingPolicyFilterSet, BGPPeerGroupFilterSet
-)
-from .models import ASN, Community, BGPSession, RoutingPolicy, BGPPeerGroup, RoutingPolicyRule
-from .tables import (
- ASNTable, CommunityTable, BGPSessionTable, RoutingPolicyTable, BGPPeerGroupTable, RoutingPolicyRuleTable
-)
-from .forms import (
- ASNFilterForm, ASNBulkEditForm, ASNForm, CommunityForm,
- CommunityFilterForm, CommunityBulkEditForm, BGPSessionForm,
- BGPSessionFilterForm, BGPSessionAddForm, RoutingPolicyFilterForm,
- RoutingPolicyForm, BGPPeerGroupFilterForm, BGPPeerGroupForm, RoutingPolicyRuleForm
+from .models import (
+ ASN, Community, BGPSession, RoutingPolicy,
+ BGPPeerGroup, RoutingPolicyRule, PrefixList,
+ PrefixListRule
)
+from . import forms, tables, filters
+
+
+# ASN
+
class ASNListView(generic.ObjectListView):
queryset = ASN.objects.all()
- filterset = ASNFilterSet
- filterset_form = ASNFilterForm
- table = ASNTable
+ filterset = filters.ASNFilterSet
+ filterset_form = forms.ASNFilterForm
+ table = tables.ASNTable
action_buttons = ('add',)
+ template_name = 'netbox_bgp/asn_list.html'
class ASNView(generic.ObjectView):
@@ -32,7 +35,7 @@ class ASNView(generic.ObjectView):
def get_extra_context(self, request, instance):
sess = BGPSession.objects.filter(remote_as=instance) | BGPSession.objects.filter(local_as=instance)
- sess_table = BGPSessionTable(sess)
+ sess_table = tables.BGPSessionTable(sess)
return {
'related_session_table': sess_table
}
@@ -40,30 +43,112 @@ def get_extra_context(self, request, instance):
class ASNEditView(generic.ObjectEditView):
queryset = ASN.objects.all()
- form = ASNForm
+ form = forms.ASNForm
class ASNBulkDeleteView(generic.BulkDeleteView):
queryset = ASN.objects.all()
- table = ASNTable
+ table = tables.ASNTable
class ASNBulkEditView(generic.BulkEditView):
queryset = ASN.objects.all()
- filterset = ASNFilterSet
- table = ASNTable
- form = ASNBulkEditForm
+ filterset = filters.ASNFilterSet
+ table = tables.ASNTable
+ form = forms.ASNBulkEditForm
class ASNDeleteView(generic.ObjectDeleteView):
queryset = ASN.objects.all()
+class ASNMigrateView(generic.BulkDeleteView):
+ asn_to_rir = {
+ (64496, 64511): 'RFC5398',
+ (64512, 65534): 'RFC6996',
+ (65536, 65551): 'RFC5398',
+ (4200000000, 4294967294): 'RFC6996',
+ }
+ queryset = ASN.objects.all()
+ table = tables.ASNTable
+ template_name = 'netbox_bgp/asn_migrate.html'
+
+ def post(self, request, **kwargs):
+ model = self.queryset.model
+
+ if request.POST.get('_all'):
+ qs = model.objects.all()
+ if self.filterset is not None:
+ qs = self.filterset(request.GET, qs).qs
+ pk_list = qs.only('pk').values_list('pk', flat=True)
+ else:
+ pk_list = [int(pk) for pk in request.POST.getlist('pk')]
+
+ form_cls = self.get_form()
+
+ if '_confirm' in request.POST:
+ form = form_cls(request.POST)
+ if form.is_valid():
+
+ # Delete objects
+ queryset = self.queryset.filter(pk__in=pk_list)
+ deleted_count = queryset.count()
+ for asn in queryset:
+ rir_name = 'Default'
+ # get rir name
+ for k, v in self.asn_to_rir.items():
+ if asn.number >= k[0] and asn.number <= k[1]:
+ rir_name = v
+ break
+ rir, _ = RIR.objects.get_or_create(name=rir_name, slug=slugify(rir_name))
+ try:
+ new_asn, new_asn_created = CoreASN.objects.get_or_create(
+ asn=asn.number,
+ description=asn.description,
+ tenant=asn.tenant,
+ rir=rir,
+ custom_field_data=asn.custom_field_data
+ )
+ if new_asn_created:
+ # update tags
+ new_asn.tags.set(asn.tags.all())
+ new_asn.save()
+ else:
+ deleted_count -= 1
+ except Exception:
+ deleted_count -= 1
+
+ msg = f"Migrated {deleted_count} {model._meta.verbose_name_plural}"
+ messages.success(request, msg)
+ return redirect(reverse('ipam:asn_list'))
+
+ else:
+ form = form_cls(initial={
+ 'pk': pk_list,
+ 'return_url': self.get_return_url(request),
+ })
+
+ # Retrieve objects being deleted
+ table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
+ if not table.rows:
+ messages.warning(request, "No {} were selected for migration.".format(model._meta.verbose_name_plural))
+ return redirect(self.get_return_url(request))
+
+ return render(request, self.template_name, {
+ 'form': form,
+ 'obj_type_plural': model._meta.verbose_name_plural,
+ 'table': table,
+ 'return_url': self.get_return_url(request),
+ })
+
+# Community
+
+
class CommunityListView(generic.ObjectListView):
queryset = Community.objects.all()
- filterset = CommunityFilterSet
- filterset_form = CommunityFilterForm
- table = CommunityTable
+ filterset = filters.CommunityFilterSet
+ filterset_form = forms.CommunityFilterForm
+ table = tables.CommunityTable
action_buttons = ('add',)
@@ -74,46 +159,51 @@ class CommunityView(generic.ObjectView):
class CommunityEditView(generic.ObjectEditView):
queryset = Community.objects.all()
- form = CommunityForm
+ form = forms.CommunityForm
class CommunityBulkDeleteView(generic.BulkDeleteView):
queryset = Community.objects.all()
- table = CommunityTable
+ table = tables.CommunityTable
class CommunityBulkEditView(generic.BulkEditView):
queryset = Community.objects.all()
- filterset = CommunityFilterSet
- table = CommunityTable
- form = CommunityBulkEditForm
+ filterset = filters.CommunityFilterSet
+ table = tables.CommunityTable
+ form = forms.CommunityBulkEditForm
class CommunityDeleteView(generic.ObjectDeleteView):
queryset = Community.objects.all()
+ default_return_url = 'plugins:netbox_bgp:community_list'
+
+
+# Session
class BGPSessionListView(generic.ObjectListView):
queryset = BGPSession.objects.all()
- filterset = BGPSessionFilterSet
- filterset_form = BGPSessionFilterForm
- table = BGPSessionTable
+ filterset = filters.BGPSessionFilterSet
+ filterset_form = forms.BGPSessionFilterForm
+ table = tables.BGPSessionTable
action_buttons = ('add',)
+ template_name = 'netbox_bgp/bgpsession_list.html'
class BGPSessionEditView(generic.ObjectEditView):
queryset = BGPSession.objects.all()
- form = BGPSessionForm
+ form = forms.BGPSessionForm
class BGPSessionAddView(generic.ObjectEditView):
queryset = BGPSession.objects.all()
- form = BGPSessionAddForm
+ form = forms.BGPSessionAddForm
class BGPSessionBulkDeleteView(generic.BulkDeleteView):
queryset = BGPSession.objects.all()
- table = BGPSessionTable
+ table = tables.BGPSessionTable
class BGPSessionView(generic.ObjectView):
@@ -128,11 +218,11 @@ def get_extra_context(self, request, instance):
import_policies_qs = instance.import_policies.all()
export_policies_qs = instance.export_policies.all()
- import_policies_table = RoutingPolicyTable(
+ import_policies_table = tables.RoutingPolicyTable(
import_policies_qs,
orderable=False
)
- export_policies_table = RoutingPolicyTable(
+ export_policies_table = tables.RoutingPolicyTable(
export_policies_qs,
orderable=False
)
@@ -145,24 +235,27 @@ def get_extra_context(self, request, instance):
class BGPSessionDeleteView(generic.ObjectDeleteView):
queryset = BGPSession.objects.all()
+ default_return_url = 'plugins:netbox_bgp:bgpsession_list'
+
+# Routing Policy
class RoutingPolicyListView(generic.ObjectListView):
queryset = RoutingPolicy.objects.all()
- filterset = RoutingPolicyFilterSet
- filterset_form = RoutingPolicyFilterForm
- table = RoutingPolicyTable
+ filterset = filters.RoutingPolicyFilterSet
+ filterset_form = forms.RoutingPolicyFilterForm
+ table = tables.RoutingPolicyTable
action_buttons = ('add',)
class RoutingPolicyEditView(generic.ObjectEditView):
queryset = RoutingPolicy.objects.all()
- form = RoutingPolicyForm
+ form = forms.RoutingPolicyForm
class RoutingPolicyBulkDeleteView(generic.BulkDeleteView):
queryset = RoutingPolicy.objects.all()
- table = RoutingPolicyTable
+ table = tables.RoutingPolicyTable
class RoutingPolicyView(generic.ObjectView):
@@ -177,9 +270,9 @@ def get_extra_context(self, request, instance):
| Q(peer_group__in=instance.group_export_policies.all())
)
sess = sess.distinct()
- sess_table = BGPSessionTable(sess)
+ sess_table = tables.BGPSessionTable(sess)
rules = instance.rules.all()
- rules_table = RoutingPolicyRuleTable(rules)
+ rules_table = tables.RoutingPolicyRuleTable(rules)
return {
'rules_table': rules_table,
'related_session_table': sess_table
@@ -188,24 +281,28 @@ def get_extra_context(self, request, instance):
class RoutingPolicyDeleteView(generic.ObjectDeleteView):
queryset = RoutingPolicy.objects.all()
+ default_return_url = 'plugins:netbox_bgp:routingpolicy_list'
+
+
+# Peer Group
class BGPPeerGroupListView(generic.ObjectListView):
queryset = BGPPeerGroup.objects.all()
- filterset = BGPPeerGroupFilterSet
- filterset_form = BGPPeerGroupFilterForm
- table = BGPPeerGroupTable
+ filterset = filters.BGPPeerGroupFilterSet
+ filterset_form = forms.BGPPeerGroupFilterForm
+ table = tables.BGPPeerGroupTable
action_buttons = ('add',)
class BGPPeerGroupEditView(generic.ObjectEditView):
queryset = BGPPeerGroup.objects.all()
- form = BGPPeerGroupForm
+ form = forms.BGPPeerGroupForm
class BGPPeerGroupBulkDeleteView(generic.BulkDeleteView):
queryset = BGPPeerGroup.objects.all()
- table = BGPPeerGroupTable
+ table = tables.BGPPeerGroupTable
class BGPPeerGroupView(generic.ObjectView):
@@ -213,18 +310,18 @@ class BGPPeerGroupView(generic.ObjectView):
template_name = 'netbox_bgp/bgppeergroup.html'
def get_extra_context(self, request, instance):
- import_policies_table = RoutingPolicyTable(
+ import_policies_table = tables.RoutingPolicyTable(
instance.import_policies.all(),
orderable=False
)
- export_policies_table = RoutingPolicyTable(
+ export_policies_table = tables.RoutingPolicyTable(
instance.export_policies.all(),
orderable=False
)
sess = BGPSession.objects.filter(peer_group=instance)
sess = sess.distinct()
- sess_table = BGPSessionTable(sess)
+ sess_table = tables.BGPSessionTable(sess)
return {
'import_policies_table': import_policies_table,
'export_policies_table': export_policies_table,
@@ -234,15 +331,20 @@ def get_extra_context(self, request, instance):
class BGPPeerGroupDeleteView(generic.ObjectDeleteView):
queryset = BGPPeerGroup.objects.all()
+ default_return_url = 'plugins:netbox_bgp:bgppeergroup_list'
+
+
+# Routing Policy Rule
class RoutingPolicyRuleEditView(generic.ObjectEditView):
queryset = RoutingPolicyRule.objects.all()
- form = RoutingPolicyRuleForm
+ form = forms.RoutingPolicyRuleForm
class RoutingPolicyRuleDeleteView(generic.ObjectDeleteView):
queryset = RoutingPolicyRule.objects.all()
+ default_return_url = 'plugins:netbox_bgp:routingpolicyrule_list'
class RoutingPolicyRuleView(generic.ObjectView):
@@ -266,7 +368,79 @@ def get_extra_context(self, request, instance):
class RoutingPolicyRuleListView(generic.ObjectListView):
queryset = RoutingPolicyRule.objects.all()
- #filterset = RoutingPolicyRuleFilterSet
- #filterset_form = RoutingPolicyRuleFilterForm
- table = RoutingPolicyRuleTable
+ # filterset = RoutingPolicyRuleFilterSet
+ # filterset_form = RoutingPolicyRuleFilterForm
+ table = tables.RoutingPolicyRuleTable
action_buttons = ('add',)
+
+
+# Prefix List
+
+
+class PrefixListListView(generic.ObjectListView):
+ queryset = PrefixList.objects.all()
+ filterset = filters.PrefixListFilterSet
+ filterset_form = forms.PrefixListFilterForm
+ table = tables.PrefixListTable
+ action_buttons = ('add',)
+
+
+class PrefixListEditView(generic.ObjectEditView):
+ queryset = PrefixList.objects.all()
+ form = forms.PrefixListForm
+
+
+class PrefixListBulkDeleteView(generic.BulkDeleteView):
+ queryset = PrefixList.objects.all()
+ table = tables.PrefixListTable
+
+
+class PrefixListView(generic.ObjectView):
+ queryset = PrefixList.objects.all()
+ template_name = 'netbox_bgp/prefixlist.html'
+
+ def get_extra_context(self, request, instance):
+ rprules = instance.plrules.all()
+ rprules_table = tables.RoutingPolicyRuleTable(rprules)
+ rules = instance.prefrules.all()
+ rules_table = tables.PrefixListRuleTable(rules)
+ return {
+ 'rules_table': rules_table,
+ 'rprules_table': rprules_table
+ }
+
+
+class PrefixListDeleteView(generic.ObjectDeleteView):
+ queryset = PrefixList.objects.all()
+ default_return_url = 'plugins:netbox_bgp:prefixlist_list'
+
+
+# Prefix List Rule
+
+
+class PrefixListRuleListView(generic.ObjectListView):
+ queryset = PrefixListRule.objects.all()
+ # filterset = RoutingPolicyRuleFilterSet
+ # filterset_form = RoutingPolicyRuleFilterForm
+ table = tables.PrefixListRuleTable
+ action_buttons = ('add',)
+
+
+class PrefixListRuleEditView(generic.ObjectEditView):
+ queryset = PrefixListRule.objects.all()
+ form = forms.PrefixListRuleForm
+
+
+class PrefixListRuleBulkDeleteView(generic.BulkDeleteView):
+ queryset = PrefixListRule.objects.all()
+ table = tables.PrefixListRuleTable
+
+
+class PrefixListRuleDeleteView(generic.ObjectDeleteView):
+ queryset = PrefixListRule.objects.all()
+ default_return_url = 'plugins:netbox_bgp:prefixlistrule_list'
+
+
+class PrefixListRuleView(generic.ObjectView):
+ queryset = PrefixListRule.objects.all()
+ template_name = 'netbox_bgp/prefixlistrule.html'