From 14a7a33cc258d487756a6c4a33df36f06839e49b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 27 Nov 2019 22:09:16 -0500 Subject: [PATCH] IPAddress.role to slug (#3569) --- netbox/ipam/api/serializers.py | 2 +- netbox/ipam/choices.py | 34 +++++++++++++++ netbox/ipam/constants.py | 41 +------------------ netbox/ipam/filters.py | 2 +- netbox/ipam/forms.py | 6 +-- .../migrations/0029_3569_ipaddress_fields.py | 32 +++++++++++++++ netbox/ipam/models.py | 31 +++++++++++--- netbox/ipam/tests/test_models.py | 6 +-- netbox/ipam/views.py | 6 +-- 9 files changed, 104 insertions(+), 56 deletions(-) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 4c49dbf57..8792063aa 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -202,7 +202,7 @@ class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer): vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceField(choices=IPAddressStatusChoices, required=False) - role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False, allow_null=True) + role = ChoiceField(choices=IPAddressRoleChoices, required=False, allow_null=True) interface = IPAddressInterfaceSerializer(required=False, allow_null=True) nat_inside = NestedIPAddressSerializer(required=False, allow_null=True) nat_outside = NestedIPAddressSerializer(read_only=True) diff --git a/netbox/ipam/choices.py b/netbox/ipam/choices.py index 2cdda3731..daf8aebec 100644 --- a/netbox/ipam/choices.py +++ b/netbox/ipam/choices.py @@ -51,3 +51,37 @@ class IPAddressStatusChoices(ChoiceSet): STATUS_DEPRECATED: 3, STATUS_DHCP: 5, } + + +class IPAddressRoleChoices(ChoiceSet): + + ROLE_LOOPBACK = 'loopback' + ROLE_SECONDARY = 'secondary' + ROLE_ANYCAST = 'anycast' + ROLE_VIP = 'vip' + ROLE_VRRP = 'vrrp' + ROLE_HSRP = 'hsrp' + ROLE_GLBP = 'glbp' + ROLE_CARP = 'carp' + + CHOICES = ( + (ROLE_LOOPBACK, 'Loopback'), + (ROLE_SECONDARY, 'Secondary'), + (ROLE_ANYCAST, 'Anycast'), + (ROLE_VIP, 'VIP'), + (ROLE_VRRP, 'VRRP'), + (ROLE_HSRP, 'HSRP'), + (ROLE_GLBP, 'GLBP'), + (ROLE_CARP, 'CARP'), + ) + + LEGACY_MAP = { + ROLE_LOOPBACK: 10, + ROLE_SECONDARY: 20, + ROLE_ANYCAST: 30, + ROLE_VIP: 40, + ROLE_VRRP: 41, + ROLE_HSRP: 42, + ROLE_GLBP: 43, + ROLE_CARP: 44, + } diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index 6a95e247c..59e25489c 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -4,36 +4,6 @@ AF_CHOICES = ( (6, 'IPv6'), ) -# IP address roles -IPADDRESS_ROLE_LOOPBACK = 10 -IPADDRESS_ROLE_SECONDARY = 20 -IPADDRESS_ROLE_ANYCAST = 30 -IPADDRESS_ROLE_VIP = 40 -IPADDRESS_ROLE_VRRP = 41 -IPADDRESS_ROLE_HSRP = 42 -IPADDRESS_ROLE_GLBP = 43 -IPADDRESS_ROLE_CARP = 44 -IPADDRESS_ROLE_CHOICES = ( - (IPADDRESS_ROLE_LOOPBACK, 'Loopback'), - (IPADDRESS_ROLE_SECONDARY, 'Secondary'), - (IPADDRESS_ROLE_ANYCAST, 'Anycast'), - (IPADDRESS_ROLE_VIP, 'VIP'), - (IPADDRESS_ROLE_VRRP, 'VRRP'), - (IPADDRESS_ROLE_HSRP, 'HSRP'), - (IPADDRESS_ROLE_GLBP, 'GLBP'), - (IPADDRESS_ROLE_CARP, 'CARP'), -) - -IPADDRESS_ROLES_NONUNIQUE = ( - # IPAddress roles which are exempt from unique address enforcement - IPADDRESS_ROLE_ANYCAST, - IPADDRESS_ROLE_VIP, - IPADDRESS_ROLE_VRRP, - IPADDRESS_ROLE_HSRP, - IPADDRESS_ROLE_GLBP, - IPADDRESS_ROLE_CARP, -) - # VLAN statuses VLAN_STATUS_ACTIVE = 1 VLAN_STATUS_RESERVED = 2 @@ -53,16 +23,7 @@ STATUS_CHOICE_CLASSES = { 4: 'warning', 5: 'success', } -ROLE_CHOICE_CLASSES = { - 10: 'default', - 20: 'primary', - 30: 'warning', - 40: 'success', - 41: 'success', - 42: 'success', - 43: 'success', - 44: 'success', -} + # IP protocols (for services) IP_PROTOCOL_TCP = 6 diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index c4be858fa..26e5297bd 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -315,7 +315,7 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet): null_value=None ) role = django_filters.MultipleChoiceFilter( - choices=IPADDRESS_ROLE_CHOICES + choices=IPAddressRoleChoices ) tag = TagFilter() diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index e2f5140d2..5bf620a5e 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -769,7 +769,7 @@ class IPAddressCSVForm(forms.ModelForm): help_text='Operational status' ) role = CSVChoiceField( - choices=IPADDRESS_ROLE_CHOICES, + choices=IPAddressRoleChoices, required=False, help_text='Functional role' ) @@ -899,7 +899,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd widget=StaticSelect2() ) role = forms.ChoiceField( - choices=add_blank_choice(IPADDRESS_ROLE_CHOICES), + choices=add_blank_choice(IPAddressRoleChoices), required=False, widget=StaticSelect2() ) @@ -978,7 +978,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo widget=StaticSelect2Multiple() ) role = forms.MultipleChoiceField( - choices=IPADDRESS_ROLE_CHOICES, + choices=IPAddressRoleChoices, required=False, widget=StaticSelect2Multiple() ) diff --git a/netbox/ipam/migrations/0029_3569_ipaddress_fields.py b/netbox/ipam/migrations/0029_3569_ipaddress_fields.py index ae5ede5e9..e5556900e 100644 --- a/netbox/ipam/migrations/0029_3569_ipaddress_fields.py +++ b/netbox/ipam/migrations/0029_3569_ipaddress_fields.py @@ -8,6 +8,17 @@ IPADDRESS_STATUS_CHOICES = ( (3, 'deprecated'), ) +IPADDRESS_ROLE_CHOICES = ( + (10, 'loopback'), + (20, 'secondary'), + (30, 'anycast'), + (40, 'vip'), + (41, 'vrrp'), + (42, 'hsrp'), + (43, 'glbp'), + (44, 'carp'), +) + def ipaddress_status_to_slug(apps, schema_editor): IPAddress = apps.get_model('ipam', 'IPAddress') @@ -15,6 +26,12 @@ def ipaddress_status_to_slug(apps, schema_editor): IPAddress.objects.filter(status=str(id)).update(status=slug) +def ipaddress_role_to_slug(apps, schema_editor): + IPAddress = apps.get_model('ipam', 'IPAddress') + for id, slug in IPADDRESS_STATUS_CHOICES: + IPAddress.objects.filter(role=str(id)).update(role=slug) + + class Migration(migrations.Migration): atomic = False @@ -34,4 +51,19 @@ class Migration(migrations.Migration): code=ipaddress_status_to_slug ), + # IPAddress.role + migrations.AlterField( + model_name='ipaddress', + name='role', + field=models.CharField(blank=True, default='', max_length=50), + ), + migrations.RunPython( + code=ipaddress_role_to_slug + ), + migrations.AlterField( + model_name='ipaddress', + name='role', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 631805cb1..51431b483 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -21,6 +21,17 @@ from .querysets import PrefixQuerySet from .validators import DNSValidator +IPADDRESS_ROLES_NONUNIQUE = ( + # IPAddress roles which are exempt from unique address enforcement + IPAddressRoleChoices.ROLE_ANYCAST, + IPAddressRoleChoices.ROLE_VIP, + IPAddressRoleChoices.ROLE_VRRP, + IPAddressRoleChoices.ROLE_HSRP, + IPAddressRoleChoices.ROLE_GLBP, + IPAddressRoleChoices.ROLE_CARP, +) + + class VRF(ChangeLoggedModel, CustomFieldModel): """ A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing @@ -565,11 +576,10 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): default=IPAddressStatusChoices.STATUS_ACTIVE, help_text='The operational status of this IP' ) - role = models.PositiveSmallIntegerField( - verbose_name='Role', - choices=IPADDRESS_ROLE_CHOICES, + role = models.CharField( + max_length=50, + choices=IPAddressRoleChoices, blank=True, - null=True, help_text='The functional role of this IP' ) interface = models.ForeignKey( @@ -620,6 +630,17 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): 'dhcp': 'success', } + ROLE_CLASS_MAP = { + 'loopback': 'default', + 'secondary': 'primary', + 'anycast': 'warning', + 'vip': 'success', + 'vrrp': 'success', + 'hsrp': 'success', + 'glbp': 'success', + 'carp': 'success', + } + class Meta: ordering = ['family', 'address'] verbose_name = 'IP address' @@ -756,7 +777,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): return self.STATUS_CLASS_MAP.get(self.status) def get_role_class(self): - return ROLE_CHOICE_CLASSES[self.role] + return self.ROLE_CLASS_MAP[self.role] class VLANGroup(ChangeLoggedModel): diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index f7f1705ff..fc8b665f7 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -2,7 +2,7 @@ import netaddr from django.core.exceptions import ValidationError from django.test import TestCase, override_settings -from ipam.constants import IPADDRESS_ROLE_VIP +from ipam.choices import IPAddressRoleChoices from ipam.models import IPAddress, Prefix, VRF @@ -61,5 +61,5 @@ class TestIPAddress(TestCase): @override_settings(ENFORCE_GLOBAL_UNIQUE=True) def test_duplicate_nonunique_role(self): - IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP) - IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPADDRESS_ROLE_VIP) + IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP) + IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index ce1d9bca7..8bd568df2 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -14,7 +14,7 @@ from utilities.views import ( ) from virtualization.models import VirtualMachine from . import filters, forms, tables -from .choices import PrefixStatusChoices +from .choices import * from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -666,8 +666,8 @@ class IPAddressView(PermissionRequiredMixin, View): 'nat_inside', 'interface__device' ) # Exclude anycast IPs if this IP is anycast - if ipaddress.role == IPADDRESS_ROLE_ANYCAST: - duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST) + if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST: + duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST) duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False) # Related IP table