From 8ec69d955bd9420c3242aa4c02fc2cc1c2839a9c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 21 Oct 2016 12:34:02 -0400 Subject: [PATCH] Closes #87: Added status field to IP addresses --- netbox/ipam/api/serializers.py | 4 +-- netbox/ipam/filters.py | 2 +- netbox/ipam/forms.py | 25 +++++++++++++------ .../migrations/0009_ipaddress_add_status.py | 20 +++++++++++++++ netbox/ipam/models.py | 13 ++++++++++ netbox/ipam/tables.py | 3 ++- netbox/templates/ipam/ipaddress.html | 6 +++++ .../templates/ipam/ipaddress_bulk_edit.html | 2 ++ netbox/templates/ipam/ipaddress_edit.html | 1 + netbox/templates/ipam/ipaddress_import.html | 7 +++++- 10 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 netbox/ipam/migrations/0009_ipaddress_add_status.py diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 653d9eba512..f7cf20636fb 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -159,8 +159,8 @@ class IPAddressSerializer(CustomFieldSerializer, serializers.ModelSerializer): class Meta: model = IPAddress - fields = ['id', 'family', 'address', 'vrf', 'tenant', 'interface', 'description', 'nat_inside', 'nat_outside', - 'custom_fields'] + fields = ['id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside', + 'nat_outside', 'custom_fields'] class IPAddressNestedSerializer(IPAddressSerializer): diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index a998bb2a040..e7e150b3437 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -232,7 +232,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet): class Meta: model = IPAddress - fields = ['q', 'family', 'device_id', 'device', 'interface_id'] + fields = ['q', 'family', 'status', 'device_id', 'device', 'interface_id'] def search(self, queryset, value): qs_filter = Q(description__icontains=value) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 1a08aa7fc51..7a4b369e554 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -5,16 +5,15 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField, + APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField, add_blank_choice, ) from .models import ( - Aggregate, IPAddress, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF, + Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, VLAN, VLANGroup, + VLAN_STATUS_CHOICES, VRF, ) -FORM_PREFIX_STATUS_CHOICES = (('', '---------'),) + PREFIX_STATUS_CHOICES -FORM_VLAN_STATUS_CHOICES = (('', '---------'),) + VLAN_STATUS_CHOICES IP_FAMILY_CHOICES = [ ('', 'All'), (4, 'IPv4'), @@ -248,7 +247,7 @@ class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False) vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF') tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) - status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False) + status = forms.ChoiceField(choices=add_blank_choice(PREFIX_STATUS_CHOICES), required=False) role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False) description = forms.CharField(max_length=100, required=False) @@ -301,7 +300,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): class Meta: model = IPAddress - fields = ['address', 'vrf', 'tenant', 'nat_device', 'nat_inside', 'description'] + fields = ['address', 'vrf', 'tenant', 'status', 'nat_device', 'nat_inside', 'description'] help_texts = { 'address': "IPv4 or IPv6 address and mask", 'vrf': "VRF (if applicable)", @@ -352,6 +351,7 @@ class IPAddressFromCSVForm(forms.ModelForm): error_messages={'invalid_choice': 'VRF not found.'}) tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False, error_messages={'invalid_choice': 'Tenant not found.'}) + status_name = forms.ChoiceField(choices=[(s[1], s[0]) for s in IPADDRESS_STATUS_CHOICES]) device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name', error_messages={'invalid_choice': 'Device not found.'}) interface_name = forms.CharField(required=False) @@ -359,7 +359,7 @@ class IPAddressFromCSVForm(forms.ModelForm): class Meta: model = IPAddress - fields = ['address', 'vrf', 'tenant', 'device', 'interface_name', 'is_primary', 'description'] + fields = ['address', 'vrf', 'tenant', 'status_name', 'device', 'interface_name', 'is_primary', 'description'] def clean(self): @@ -406,12 +406,20 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput) 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) description = forms.CharField(max_length=100, required=False) class Meta: nullable_fields = ['vrf', 'tenant', 'description'] +def ipaddress_status_choices(): + status_counts = {} + for status in IPAddress.objects.values('status').annotate(count=Count('status')).order_by('status'): + status_counts[status['status']] = status['count'] + return [(s[0], u'{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in IPADDRESS_STATUS_CHOICES] + + class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): model = IPAddress parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={ @@ -422,6 +430,7 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm): label='VRF', null_option=(0, 'Global')) tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')), to_field_name='slug', null_option=(0, 'None')) + status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False) # @@ -510,7 +519,7 @@ class VLANBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False) group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False) tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) - status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False) + status = forms.ChoiceField(choices=add_blank_choice(VLAN_STATUS_CHOICES), required=False) role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False) description = forms.CharField(max_length=100, required=False) diff --git a/netbox/ipam/migrations/0009_ipaddress_add_status.py b/netbox/ipam/migrations/0009_ipaddress_add_status.py new file mode 100644 index 00000000000..ad876c3b6b7 --- /dev/null +++ b/netbox/ipam/migrations/0009_ipaddress_add_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-21 15:44 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0008_prefix_change_order'), + ] + + operations = [ + migrations.AddField( + model_name='ipaddress', + name='status', + field=models.PositiveSmallIntegerField(choices=[(1, b'Active'), (2, b'Reserved'), (5, b'DHCP')], default=1, verbose_name=b'Status'), + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 794198b9254..88d83470e2e 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -29,6 +29,12 @@ (3, 'Deprecated') ) +IPADDRESS_STATUS_CHOICES = ( + (1, 'Active'), + (2, 'Reserved'), + (5, 'DHCP') +) + VLAN_STATUS_CHOICES = ( (1, 'Active'), (2, 'Reserved'), @@ -40,6 +46,8 @@ 1: 'primary', 2: 'info', 3: 'danger', + 4: 'warning', + 5: 'success', } @@ -333,6 +341,7 @@ 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) 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, @@ -387,6 +396,7 @@ def to_csv(self): str(self.address), self.vrf.rd if self.vrf else '', self.tenant.name if self.tenant else '', + self.get_status_display(), self.device.identifier if self.device else '', self.interface.name if self.interface else '', 'True' if is_primary else '', @@ -399,6 +409,9 @@ def device(self): return self.interface.device return None + def get_status_class(self): + return STATUS_CHOICE_CLASSES[self.status] + class VLANGroup(models.Model): """ diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index c669362c51a..0a787ad3b76 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -193,6 +193,7 @@ class Meta(BaseTable.Meta): class IPAddressTable(BaseTable): pk = ToggleColumn() address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address') + status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status') vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF') tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant') device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False, @@ -202,7 +203,7 @@ class IPAddressTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress - fields = ('pk', 'address', 'vrf', 'tenant', 'device', 'interface', 'description') + fields = ('pk', 'address', 'status', 'vrf', 'tenant', 'device', 'interface', 'description') row_attrs = { 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '', } diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 7524b00fe23..b85b98c931a 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -76,6 +76,12 @@

{{ ipaddress }}

{% endif %} + + Status + + {{ ipaddress.get_status_display }} + + Description diff --git a/netbox/templates/ipam/ipaddress_bulk_edit.html b/netbox/templates/ipam/ipaddress_bulk_edit.html index 818fc20e66d..7dc0f6d1afb 100644 --- a/netbox/templates/ipam/ipaddress_bulk_edit.html +++ b/netbox/templates/ipam/ipaddress_bulk_edit.html @@ -8,6 +8,7 @@ IP Address VRF Tenant + Status Assigned Description @@ -16,6 +17,7 @@ {{ ipaddress }} {{ ipaddress.vrf|default:"Global" }} {{ ipaddress.tenant }} + {{ ipaddress.get_status_display }} {% if ipaddress.interface %}{% endif %} {{ ipaddress.description }} diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index eb36ff977f2..e5e7f0b5b59 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -9,6 +9,7 @@ {% render_field form.address %} {% render_field form.vrf %} {% render_field form.tenant %} + {% render_field form.status %} {% if obj %}
diff --git a/netbox/templates/ipam/ipaddress_import.html b/netbox/templates/ipam/ipaddress_import.html index b819df49488..ad62b44df38 100644 --- a/netbox/templates/ipam/ipaddress_import.html +++ b/netbox/templates/ipam/ipaddress_import.html @@ -43,6 +43,11 @@

CSV Format

Name of tenant (optional) ABC01 + + Status + Current status + Active + Device Device name (optional) @@ -66,7 +71,7 @@

CSV Format

Example

-
192.0.2.42/24,65000:123,ABC01,switch12,ge-0/0/31,True,Management IP
+
192.0.2.42/24,65000:123,ABC01,Active,switch12,ge-0/0/31,True,Management IP
{% endblock %}