diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 8a618fcb1..e02e384ed 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -6,8 +6,8 @@ from rest_framework.validators import UniqueTogetherValidator from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer from extras.api.customfields import CustomFieldModelSerializer from ipam.models import ( - Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, - Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF, + Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix, + PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF, ) from tenancy.api.serializers import NestedTenantSerializer from utilities.api import ChoiceFieldSerializer @@ -236,12 +236,13 @@ class IPAddressSerializer(CustomFieldModelSerializer): vrf = NestedVRFSerializer() tenant = NestedTenantSerializer() status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES) + role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES) interface = InterfaceSerializer() class Meta: model = IPAddress fields = [ - 'id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside', + 'id', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside', 'nat_outside', 'custom_fields', ] @@ -261,7 +262,10 @@ class WritableIPAddressSerializer(CustomFieldModelSerializer): class Meta: model = IPAddress - fields = ['id', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside', 'custom_fields'] + fields = [ + 'id', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside', + 'custom_fields', + ] # diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 11c19b7ee..a4532edb4 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -11,8 +11,8 @@ from extras.filters import CustomFieldFilterSet from tenancy.models import Tenant from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from .models import ( - Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, - VLAN_STATUS_CHOICES, VLANGroup, VRF, + Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, + Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF, ) @@ -247,6 +247,9 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): status = django_filters.MultipleChoiceFilter( choices=IPADDRESS_STATUS_CHOICES ) + role = django_filters.MultipleChoiceFilter( + choices=IPADDRESS_ROLE_CHOICES + ) class Meta: model = IPAddress diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index e3fe96c4c..66d563b2c 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -13,8 +13,8 @@ from utilities.forms import ( add_blank_choice, ) from .models import ( - Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, - VLANGroup, VLAN_STATUS_CHOICES, VRF, + Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, + Service, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF, ) @@ -477,7 +477,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) class Meta: model = IPAddress fields = [ - 'address', 'vrf', 'status', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack', + 'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack', 'nat_inside', 'tenant_group', 'tenant', ] @@ -555,7 +555,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm): class Meta: model = IPAddress - fields = ['address', 'status', 'vrf', 'description', 'tenant_group', 'tenant'] + fields = ['address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant'] def __init__(self, *args, **kwargs): super(IPAddressBulkAddForm, self).__init__(*args, **kwargs) @@ -585,6 +585,11 @@ class IPAddressCSVForm(forms.ModelForm): choices=PREFIX_STATUS_CHOICES, help_text='Operational status' ) + role = CSVChoiceField( + choices=IPADDRESS_ROLE_CHOICES, + required=False, + help_text='Functional role' + ) device = FlexibleModelChoiceField( queryset=Device.objects.all(), required=False, @@ -605,7 +610,7 @@ class IPAddressCSVForm(forms.ModelForm): class Meta: model = IPAddress - fields = ['address', 'vrf', 'tenant', 'status', 'device', 'interface_name', 'is_primary', 'description'] + fields = ['address', 'vrf', 'tenant', 'status', 'role', 'device', 'interface_name', 'is_primary', 'description'] def clean(self): @@ -651,10 +656,11 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF') tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) status = forms.ChoiceField(choices=add_blank_choice(IPADDRESS_STATUS_CHOICES), required=False) + role = forms.ChoiceField(choices=add_blank_choice(IPADDRESS_ROLE_CHOICES), required=False) description = forms.CharField(max_length=100, required=False) class Meta: - nullable_fields = ['vrf', 'tenant', 'description'] + nullable_fields = ['vrf', 'role', 'tenant', 'description'] def ipaddress_status_choices(): @@ -664,6 +670,13 @@ def ipaddress_status_choices(): return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in IPADDRESS_STATUS_CHOICES] +def ipaddress_role_choices(): + role_counts = {} + for role in IPAddress.objects.values('role').annotate(count=Count('role')).order_by('role'): + role_counts[role['role']] = role['count'] + return [(r[0], '{} ({})'.format(r[1], role_counts.get(r[0], 0))) for r in IPADDRESS_ROLE_CHOICES] + + class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): model = IPAddress q = forms.CharField(required=False, label='Search') @@ -684,6 +697,7 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): null_option=(0, 'None') ) status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False) + role = forms.MultipleChoiceField(choices=ipaddress_role_choices, required=False) # diff --git a/netbox/ipam/migrations/0017_ipaddress_roles.py b/netbox/ipam/migrations/0017_ipaddress_roles.py new file mode 100644 index 000000000..6ad44c146 --- /dev/null +++ b/netbox/ipam/migrations/0017_ipaddress_roles.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-06-14 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0016_unicode_literals'), + ] + + operations = [ + migrations.AddField( + model_name='ipaddress', + name='role', + field=models.PositiveSmallIntegerField(blank=True, choices=[(10, 'Loopback'), (20, 'Secondary'), (30, 'Anycast'), (40, 'Virtual'), (41, 'VRRP'), (42, 'HSRP'), (43, 'GLBP')], help_text='The functional role of this IP', null=True, verbose_name='Role'), + ), + migrations.AlterField( + model_name='ipaddress', + name='status', + field=models.PositiveSmallIntegerField(choices=[(1, 'Active'), (2, 'Reserved'), (3, 'Deprecated'), (5, 'DHCP')], default=1, help_text='The operational status of this IP', verbose_name='Status'), + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index d3ed9addd..bc1e8fe76 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -47,6 +47,23 @@ IPADDRESS_STATUS_CHOICES = ( (IPADDRESS_STATUS_DHCP, 'DHCP') ) +IPADDRESS_ROLE_LOOPBACK = 10 +IPADDRESS_ROLE_SECONDARY = 20 +IPADDRESS_ROLE_ANYCAST = 30 +IPADDRESS_ROLE_VIRTUAL = 40 +IPADDRESS_ROLE_VRRP = 41 +IPADDRESS_ROLE_HSRP = 42 +IPADDRESS_ROLE_GLBP = 43 +IPADDRESS_ROLE_CHOICES = ( + (IPADDRESS_ROLE_LOOPBACK, 'Loopback'), + (IPADDRESS_ROLE_SECONDARY, 'Secondary'), + (IPADDRESS_ROLE_ANYCAST, 'Anycast'), + (IPADDRESS_ROLE_VIRTUAL, 'Virtual'), + (IPADDRESS_ROLE_VRRP, 'VRRP'), + (IPADDRESS_ROLE_HSRP, 'HSRP'), + (IPADDRESS_ROLE_GLBP, 'GLBP'), +) + VLAN_STATUS_ACTIVE = 1 VLAN_STATUS_RESERVED = 2 VLAN_STATUS_DEPRECATED = 3 @@ -65,7 +82,6 @@ STATUS_CHOICE_CLASSES = { 5: 'success', } - IP_PROTOCOL_TCP = 6 IP_PROTOCOL_UDP = 17 IP_PROTOCOL_CHOICES = ( @@ -427,7 +443,13 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): vrf = models.ForeignKey('VRF', related_name='ip_addresses', on_delete=models.PROTECT, blank=True, null=True, verbose_name='VRF') tenant = models.ForeignKey(Tenant, related_name='ip_addresses', blank=True, null=True, on_delete=models.PROTECT) - status = models.PositiveSmallIntegerField('Status', choices=IPADDRESS_STATUS_CHOICES, default=1) + status = models.PositiveSmallIntegerField( + 'Status', choices=IPADDRESS_STATUS_CHOICES, default=IPADDRESS_STATUS_ACTIVE, + help_text='The operational status of this IP' + ) + role = models.PositiveSmallIntegerField( + 'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP' + ) interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True, null=True) nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True, @@ -438,7 +460,9 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): objects = IPAddressManager() - csv_headers = ['address', 'vrf', 'tenant', 'status', 'device', 'interface_name', 'is_primary', 'description'] + csv_headers = [ + 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'interface_name', 'is_primary', 'description', + ] class Meta: ordering = ['family', 'address'] @@ -490,6 +514,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): self.vrf.rd if self.vrf else None, self.tenant.name if self.tenant else None, self.get_status_display(), + self.get_role_display(), self.device.identifier if self.device else None, self.interface.name if self.interface else None, is_primary, diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 767bd2cec..bfdab9319 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -299,7 +299,7 @@ class IPAddressTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress - fields = ('pk', 'address', 'status', 'vrf', 'tenant', 'nat_inside', 'device', 'description') + fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'device', 'description') row_attrs = { 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '', } @@ -328,7 +328,7 @@ class IPAddressSearchTable(SearchTable): class Meta(SearchTable.Meta): model = IPAddress - fields = ('address', 'status', 'vrf', 'tenant', 'device', 'interface', 'description') + fields = ('address', 'vrf', 'status', 'role', 'tenant', 'device', 'interface', 'description') # diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index e6dd489df..44c5ec5ff 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -82,6 +82,12 @@ {{ ipaddress.get_status_display }} + + Role + + {{ ipaddress.get_role_display }} + + Description diff --git a/netbox/templates/ipam/ipaddress_bulk_add.html b/netbox/templates/ipam/ipaddress_bulk_add.html index 668f495eb..78406a3f2 100644 --- a/netbox/templates/ipam/ipaddress_bulk_add.html +++ b/netbox/templates/ipam/ipaddress_bulk_add.html @@ -14,6 +14,7 @@
{% render_field pattern_form.pattern %} {% render_field model_form.status %} + {% render_field model_form.role %} {% render_field model_form.vrf %} {% render_field model_form.description %}
diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index 64dc22353..5a625e03c 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -14,6 +14,7 @@
{% render_field form.address %} {% render_field form.status %} + {% render_field form.role %} {% render_field form.vrf %} {% render_field form.description %}