mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #87: Added status field to IP addresses
This commit is contained in:
@ -159,8 +159,8 @@ class IPAddressSerializer(CustomFieldSerializer, serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['id', 'family', 'address', 'vrf', 'tenant', 'interface', 'description', 'nat_inside', 'nat_outside',
|
fields = ['id', 'family', 'address', 'vrf', 'tenant', 'status', 'interface', 'description', 'nat_inside',
|
||||||
'custom_fields']
|
'nat_outside', 'custom_fields']
|
||||||
|
|
||||||
|
|
||||||
class IPAddressNestedSerializer(IPAddressSerializer):
|
class IPAddressNestedSerializer(IPAddressSerializer):
|
||||||
|
@ -232,7 +232,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['q', 'family', 'device_id', 'device', 'interface_id']
|
fields = ['q', 'family', 'status', 'device_id', 'device', 'interface_id']
|
||||||
|
|
||||||
def search(self, queryset, value):
|
def search(self, queryset, value):
|
||||||
qs_filter = Q(description__icontains=value)
|
qs_filter = Q(description__icontains=value)
|
||||||
|
@ -5,16 +5,15 @@ from dcim.models import Site, Device, Interface
|
|||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField,
|
APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField, add_blank_choice,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
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 = [
|
IP_FAMILY_CHOICES = [
|
||||||
('', 'All'),
|
('', 'All'),
|
||||||
(4, 'IPv4'),
|
(4, 'IPv4'),
|
||||||
@ -248,7 +247,7 @@ class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
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)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
@ -301,7 +300,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['address', 'vrf', 'tenant', 'nat_device', 'nat_inside', 'description']
|
fields = ['address', 'vrf', 'tenant', 'status', 'nat_device', 'nat_inside', 'description']
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'address': "IPv4 or IPv6 address and mask",
|
'address': "IPv4 or IPv6 address and mask",
|
||||||
'vrf': "VRF (if applicable)",
|
'vrf': "VRF (if applicable)",
|
||||||
@ -352,6 +351,7 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
|||||||
error_messages={'invalid_choice': 'VRF not found.'})
|
error_messages={'invalid_choice': 'VRF not found.'})
|
||||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
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',
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
|
||||||
error_messages={'invalid_choice': 'Device not found.'})
|
error_messages={'invalid_choice': 'Device not found.'})
|
||||||
interface_name = forms.CharField(required=False)
|
interface_name = forms.CharField(required=False)
|
||||||
@ -359,7 +359,7 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
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):
|
def clean(self):
|
||||||
|
|
||||||
@ -406,12 +406,20 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
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)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['vrf', 'tenant', 'description']
|
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):
|
class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={
|
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'))
|
label='VRF', null_option=(0, 'Global'))
|
||||||
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')),
|
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('ip_addresses')),
|
||||||
to_field_name='slug', null_option=(0, 'None'))
|
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)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.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)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
20
netbox/ipam/migrations/0009_ipaddress_add_status.py
Normal file
20
netbox/ipam/migrations/0009_ipaddress_add_status.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -29,6 +29,12 @@ PREFIX_STATUS_CHOICES = (
|
|||||||
(3, 'Deprecated')
|
(3, 'Deprecated')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
IPADDRESS_STATUS_CHOICES = (
|
||||||
|
(1, 'Active'),
|
||||||
|
(2, 'Reserved'),
|
||||||
|
(5, 'DHCP')
|
||||||
|
)
|
||||||
|
|
||||||
VLAN_STATUS_CHOICES = (
|
VLAN_STATUS_CHOICES = (
|
||||||
(1, 'Active'),
|
(1, 'Active'),
|
||||||
(2, 'Reserved'),
|
(2, 'Reserved'),
|
||||||
@ -40,6 +46,8 @@ STATUS_CHOICE_CLASSES = {
|
|||||||
1: 'primary',
|
1: 'primary',
|
||||||
2: 'info',
|
2: 'info',
|
||||||
3: 'danger',
|
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,
|
vrf = models.ForeignKey('VRF', related_name='ip_addresses', on_delete=models.PROTECT, blank=True, null=True,
|
||||||
verbose_name='VRF')
|
verbose_name='VRF')
|
||||||
tenant = models.ForeignKey(Tenant, related_name='ip_addresses', blank=True, null=True, on_delete=models.PROTECT)
|
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,
|
interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
|
nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
|
||||||
@ -387,6 +396,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
str(self.address),
|
str(self.address),
|
||||||
self.vrf.rd if self.vrf else '',
|
self.vrf.rd if self.vrf else '',
|
||||||
self.tenant.name if self.tenant else '',
|
self.tenant.name if self.tenant else '',
|
||||||
|
self.get_status_display(),
|
||||||
self.device.identifier if self.device else '',
|
self.device.identifier if self.device else '',
|
||||||
self.interface.name if self.interface else '',
|
self.interface.name if self.interface else '',
|
||||||
'True' if is_primary else '',
|
'True' if is_primary else '',
|
||||||
@ -399,6 +409,9 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
return self.interface.device
|
return self.interface.device
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_status_class(self):
|
||||||
|
return STATUS_CHOICE_CLASSES[self.status]
|
||||||
|
|
||||||
|
|
||||||
class VLANGroup(models.Model):
|
class VLANGroup(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -193,6 +193,7 @@ class PrefixBriefTable(BaseTable):
|
|||||||
class IPAddressTable(BaseTable):
|
class IPAddressTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
|
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')
|
vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
|
||||||
tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
|
tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
|
||||||
device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
|
device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
|
||||||
@ -202,7 +203,7 @@ class IPAddressTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ('pk', 'address', 'vrf', 'tenant', 'device', 'interface', 'description')
|
fields = ('pk', 'address', 'status', 'vrf', 'tenant', 'device', 'interface', 'description')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
|
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>
|
||||||
|
<span class="label label-{{ ipaddress.get_status_class }}">{{ ipaddress.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<th>IP Address</th>
|
<th>IP Address</th>
|
||||||
<th>VRF</th>
|
<th>VRF</th>
|
||||||
<th>Tenant</th>
|
<th>Tenant</th>
|
||||||
|
<th>Status</th>
|
||||||
<th>Assigned</th>
|
<th>Assigned</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -16,6 +17,7 @@
|
|||||||
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
||||||
<td>{{ ipaddress.vrf|default:"Global" }}</td>
|
<td>{{ ipaddress.vrf|default:"Global" }}</td>
|
||||||
<td>{{ ipaddress.tenant }}</td>
|
<td>{{ ipaddress.tenant }}</td>
|
||||||
|
<td>{{ ipaddress.get_status_display }}</td>
|
||||||
<td>{% if ipaddress.interface %}<i class="glyphicon glyphicon-ok text-success" title="{{ ipaddress.interface.device }} {{ ipaddress.interface }}"></i>{% endif %}</td>
|
<td>{% if ipaddress.interface %}<i class="glyphicon glyphicon-ok text-success" title="{{ ipaddress.interface.device }} {{ ipaddress.interface }}"></i>{% endif %}</td>
|
||||||
<td>{{ ipaddress.description }}</td>
|
<td>{{ ipaddress.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
{% render_field form.address %}
|
{% render_field form.address %}
|
||||||
{% render_field form.vrf %}
|
{% render_field form.vrf %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
|
{% render_field form.status %}
|
||||||
{% if obj %}
|
{% if obj %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label">Device</label>
|
<label class="col-md-3 control-label">Device</label>
|
||||||
|
@ -43,6 +43,11 @@
|
|||||||
<td>Name of tenant (optional)</td>
|
<td>Name of tenant (optional)</td>
|
||||||
<td>ABC01</td>
|
<td>ABC01</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>Current status</td>
|
||||||
|
<td>Active</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>Device name (optional)</td>
|
<td>Device name (optional)</td>
|
||||||
@ -66,7 +71,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h4>Example</h4>
|
<h4>Example</h4>
|
||||||
<pre>192.0.2.42/24,65000:123,ABC01,switch12,ge-0/0/31,True,Management IP</pre>
|
<pre>192.0.2.42/24,65000:123,ABC01,Active,switch12,ge-0/0/31,True,Management IP</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user