Jeremy Stretch 6 лет назад
Родитель
Сommit
14a7a33cc2

+ 1 - 1
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)

+ 34 - 0
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,
+    }

+ 1 - 40
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

+ 1 - 1
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()
 

+ 3 - 3
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()
     )

+ 32 - 0
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),
+        ),
+
     ]

+ 26 - 5
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):

+ 3 - 3
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)

+ 3 - 3
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