diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index fc0c390cf..fa9269584 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -8,6 +8,7 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer +from ipam.choices import * from ipam.constants import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from tenancy.api.nested_serializers import NestedTenantSerializer @@ -140,7 +141,7 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer): vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) vlan = NestedVLANSerializer(required=False, allow_null=True) - status = ChoiceField(choices=PREFIX_STATUS_CHOICES, required=False) + status = ChoiceField(choices=PrefixStatusChoices, required=False) role = NestedRoleSerializer(required=False, allow_null=True) tags = TagListSerializerField(required=False) diff --git a/netbox/ipam/choices.py b/netbox/ipam/choices.py new file mode 100644 index 000000000..fc9874bb4 --- /dev/null +++ b/netbox/ipam/choices.py @@ -0,0 +1,27 @@ +from utilities.choices import ChoiceSet + + +# +# Prefixes +# + +class PrefixStatusChoices(ChoiceSet): + + STATUS_CONTAINER = 'container' + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + + CHOICES = ( + (STATUS_CONTAINER, 'Container'), + (STATUS_ACTIVE, 'Active'), + (STATUS_RESERVED, 'Reserved'), + (STATUS_DEPRECATED, 'Deprecated'), + ) + + LEGACY_MAP = { + STATUS_CONTAINER: 0, + STATUS_ACTIVE: 1, + STATUS_RESERVED: 2, + STATUS_DEPRECATED: 3, + } diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index e2fd1ca01..93521e77d 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -4,18 +4,6 @@ AF_CHOICES = ( (6, 'IPv6'), ) -# Prefix statuses -PREFIX_STATUS_CONTAINER = 0 -PREFIX_STATUS_ACTIVE = 1 -PREFIX_STATUS_RESERVED = 2 -PREFIX_STATUS_DEPRECATED = 3 -PREFIX_STATUS_CHOICES = ( - (PREFIX_STATUS_CONTAINER, 'Container'), - (PREFIX_STATUS_ACTIVE, 'Active'), - (PREFIX_STATUS_RESERVED, 'Reserved'), - (PREFIX_STATUS_DEPRECATED, 'Deprecated') -) - # IP address statuses IPADDRESS_STATUS_ACTIVE = 1 IPADDRESS_STATUS_RESERVED = 2 diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index c57006b27..d75d312cd 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -9,6 +9,7 @@ from extras.filters import CustomFieldFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter from virtualization.models import VirtualMachine +from .choices import * from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -178,7 +179,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet): label='Role (slug)', ) status = django_filters.MultipleChoiceFilter( - choices=PREFIX_STATUS_CHOICES, + choices=PrefixStatusChoices, null_value=None ) tag = TagFilter() diff --git a/netbox/ipam/fixtures/ipam.json b/netbox/ipam/fixtures/ipam.json index 10e22b2d7..9d8da496c 100644 --- a/netbox/ipam/fixtures/ipam.json +++ b/netbox/ipam/fixtures/ipam.json @@ -40,7 +40,7 @@ "site": 1, "vrf": null, "vlan": null, - "status": 1, + "status": "active", "role": 1, "description": "" } @@ -56,7 +56,7 @@ "site": 1, "vrf": null, "vlan": null, - "status": 1, + "status": "active", "role": 1, "description": "" } diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 002d2a72a..0c7108f41 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -13,6 +13,7 @@ from utilities.forms import ( StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import VirtualMachine +from .choices import * from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -374,7 +375,7 @@ class PrefixCSVForm(forms.ModelForm): required=False ) status = CSVChoiceField( - choices=PREFIX_STATUS_CHOICES, + choices=PrefixStatusChoices, help_text='Operational status' ) role = forms.ModelChoiceField( @@ -459,7 +460,7 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF ) ) status = forms.ChoiceField( - choices=add_blank_choice(PREFIX_STATUS_CHOICES), + choices=add_blank_choice(PrefixStatusChoices), required=False, widget=StaticSelect2() ) @@ -527,7 +528,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ) ) status = forms.MultipleChoiceField( - choices=PREFIX_STATUS_CHOICES, + choices=PrefixStatusChoices, required=False, widget=StaticSelect2Multiple() ) diff --git a/netbox/ipam/migrations/0028_3569_prefix_fields.py b/netbox/ipam/migrations/0028_3569_prefix_fields.py new file mode 100644 index 000000000..1aaa9c22b --- /dev/null +++ b/netbox/ipam/migrations/0028_3569_prefix_fields.py @@ -0,0 +1,34 @@ +from django.db import migrations, models + + +PREFIX_STATUS_CHOICES = ( + (0, 'container'), + (1, 'active'), + (2, 'reserved'), + (3, 'deprecated'), +) + + +def prefix_status_to_slug(apps, schema_editor): + Prefix = apps.get_model('ipam', 'Prefix') + for id, slug in PREFIX_STATUS_CHOICES: + Prefix.objects.filter(status=str(id)).update(status=slug) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ('ipam', '0027_ipaddress_add_dns_name'), + ] + + operations = [ + migrations.AlterField( + model_name='prefix', + name='status', + field=models.CharField(default='active', max_length=50), + ), + migrations.RunPython( + code=prefix_status_to_slug + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 8f9b64b59..bb922c287 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -14,6 +14,7 @@ from extras.models import CustomFieldModel, ObjectChange, TaggedItem from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object from virtualization.models import VirtualMachine +from .choices import * from .constants import * from .fields import IPNetworkField, IPAddressField from .querysets import PrefixQuerySet @@ -297,9 +298,10 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): null=True, verbose_name='VLAN' ) - status = models.PositiveSmallIntegerField( - choices=PREFIX_STATUS_CHOICES, - default=PREFIX_STATUS_ACTIVE, + status = models.CharField( + max_length=50, + choices=PrefixStatusChoices, + default=PrefixStatusChoices.STATUS_ACTIVE, verbose_name='Status', help_text='Operational status of this prefix' ) @@ -333,6 +335,13 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', ] + STATUS_CLASS_MAP = { + 'container': 'default', + 'active': 'primary', + 'reserved': 'info', + 'deprecated': 'danger', + } + class Meta: ordering = [F('vrf').asc(nulls_first=True), 'family', 'prefix'] verbose_name_plural = 'prefixes' @@ -404,7 +413,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): prefix_length = property(fset=_set_prefix_length) def get_status_class(self): - return STATUS_CHOICE_CLASSES[self.status] + return self.STATUS_CLASS_MAP.get(self.status) def get_duplicates(self): return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk) @@ -414,7 +423,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child Prefixes belonging to any VRF. """ - if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: + if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER: return Prefix.objects.filter(prefix__net_contained=str(self.prefix)) else: return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf) @@ -424,7 +433,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return child IPAddresses belonging to any VRF. """ - if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: + if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER: return IPAddress.objects.filter(address__net_host_contained=str(self.prefix)) else: return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf) @@ -490,7 +499,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of "container", calculate utilization based on child prefixes. For all others, count child IP addresses. """ - if self.status == PREFIX_STATUS_CONTAINER: + if self.status == PrefixStatusChoices.STATUS_CONTAINER: queryset = Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf) child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) return int(float(child_prefixes.size) / self.prefix.size * 100) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 2cc1a0ea8..ce1d9bca7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -14,6 +14,7 @@ from utilities.views import ( ) from virtualization.models import VirtualMachine from . import filters, forms, tables +from .choices import PrefixStatusChoices from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -217,13 +218,13 @@ class RIRListView(PermissionRequiredMixin, ObjectListView): # Find all consumed space for each prefix status (we ignore containers for this purpose). active_prefixes = netaddr.cidr_merge( - [p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)] + [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_ACTIVE)] ) reserved_prefixes = netaddr.cidr_merge( - [p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)] + [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_RESERVED)] ) deprecated_prefixes = netaddr.cidr_merge( - [p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)] + [p.prefix for p in queryset.filter(status=PrefixStatusChoices.STATUS_DEPRECATED)] ) # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.