mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #819: Implemented IP address functional roles
This commit is contained in:
@ -6,8 +6,8 @@ from rest_framework.validators import UniqueTogetherValidator
|
|||||||
from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer
|
from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.models import (
|
from ipam.models import (
|
||||||
Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
|
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, IP_PROTOCOL_CHOICES, Prefix,
|
||||||
Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
||||||
)
|
)
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer
|
from utilities.api import ChoiceFieldSerializer
|
||||||
@ -236,12 +236,13 @@ class IPAddressSerializer(CustomFieldModelSerializer):
|
|||||||
vrf = NestedVRFSerializer()
|
vrf = NestedVRFSerializer()
|
||||||
tenant = NestedTenantSerializer()
|
tenant = NestedTenantSerializer()
|
||||||
status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES)
|
status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES)
|
||||||
|
role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES)
|
||||||
interface = InterfaceSerializer()
|
interface = InterfaceSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
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',
|
'nat_outside', 'custom_fields',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -261,7 +262,10 @@ class WritableIPAddressSerializer(CustomFieldModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
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',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -11,8 +11,8 @@ from extras.filters import CustomFieldFilterSet
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
|
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
|
||||||
from .models import (
|
from .models import (
|
||||||
Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN,
|
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
|
||||||
VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
Service, VLAN, VLAN_STATUS_CHOICES, VLANGroup, VRF,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -247,6 +247,9 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPADDRESS_STATUS_CHOICES
|
choices=IPADDRESS_STATUS_CHOICES
|
||||||
)
|
)
|
||||||
|
role = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=IPADDRESS_ROLE_CHOICES
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
|
@ -13,8 +13,8 @@ from utilities.forms import (
|
|||||||
add_blank_choice,
|
add_blank_choice,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN,
|
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
|
||||||
VLANGroup, VLAN_STATUS_CHOICES, VRF,
|
Service, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -477,7 +477,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
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',
|
'nat_inside', 'tenant_group', 'tenant',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -555,7 +555,7 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['address', 'status', 'vrf', 'description', 'tenant_group', 'tenant']
|
fields = ['address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(IPAddressBulkAddForm, self).__init__(*args, **kwargs)
|
super(IPAddressBulkAddForm, self).__init__(*args, **kwargs)
|
||||||
@ -585,6 +585,11 @@ class IPAddressCSVForm(forms.ModelForm):
|
|||||||
choices=PREFIX_STATUS_CHOICES,
|
choices=PREFIX_STATUS_CHOICES,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
|
role = CSVChoiceField(
|
||||||
|
choices=IPADDRESS_ROLE_CHOICES,
|
||||||
|
required=False,
|
||||||
|
help_text='Functional role'
|
||||||
|
)
|
||||||
device = FlexibleModelChoiceField(
|
device = FlexibleModelChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -605,7 +610,7 @@ class IPAddressCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
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):
|
def clean(self):
|
||||||
|
|
||||||
@ -651,10 +656,11 @@ class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
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)
|
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)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['vrf', 'tenant', 'description']
|
nullable_fields = ['vrf', 'role', 'tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
def ipaddress_status_choices():
|
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]
|
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):
|
class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(required=False, label='Search')
|
||||||
@ -684,6 +697,7 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
null_option=(0, 'None')
|
null_option=(0, 'None')
|
||||||
)
|
)
|
||||||
status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False)
|
status = forms.MultipleChoiceField(choices=ipaddress_status_choices, required=False)
|
||||||
|
role = forms.MultipleChoiceField(choices=ipaddress_role_choices, required=False)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
25
netbox/ipam/migrations/0017_ipaddress_roles.py
Normal file
25
netbox/ipam/migrations/0017_ipaddress_roles.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -47,6 +47,23 @@ IPADDRESS_STATUS_CHOICES = (
|
|||||||
(IPADDRESS_STATUS_DHCP, 'DHCP')
|
(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_ACTIVE = 1
|
||||||
VLAN_STATUS_RESERVED = 2
|
VLAN_STATUS_RESERVED = 2
|
||||||
VLAN_STATUS_DEPRECATED = 3
|
VLAN_STATUS_DEPRECATED = 3
|
||||||
@ -65,7 +82,6 @@ STATUS_CHOICE_CLASSES = {
|
|||||||
5: 'success',
|
5: 'success',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
IP_PROTOCOL_TCP = 6
|
IP_PROTOCOL_TCP = 6
|
||||||
IP_PROTOCOL_UDP = 17
|
IP_PROTOCOL_UDP = 17
|
||||||
IP_PROTOCOL_CHOICES = (
|
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,
|
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)
|
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,
|
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,
|
||||||
@ -438,7 +460,9 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
|
|
||||||
objects = IPAddressManager()
|
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:
|
class Meta:
|
||||||
ordering = ['family', 'address']
|
ordering = ['family', 'address']
|
||||||
@ -490,6 +514,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.vrf.rd if self.vrf else None,
|
self.vrf.rd if self.vrf else None,
|
||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
self.get_status_display(),
|
self.get_status_display(),
|
||||||
|
self.get_role_display(),
|
||||||
self.device.identifier if self.device else None,
|
self.device.identifier if self.device else None,
|
||||||
self.interface.name if self.interface else None,
|
self.interface.name if self.interface else None,
|
||||||
is_primary,
|
is_primary,
|
||||||
|
@ -299,7 +299,7 @@ class IPAddressTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
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 = {
|
row_attrs = {
|
||||||
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
|
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
|
||||||
}
|
}
|
||||||
@ -328,7 +328,7 @@ class IPAddressSearchTable(SearchTable):
|
|||||||
|
|
||||||
class Meta(SearchTable.Meta):
|
class Meta(SearchTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ('address', 'status', 'vrf', 'tenant', 'device', 'interface', 'description')
|
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'device', 'interface', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -82,6 +82,12 @@
|
|||||||
<span class="label label-{{ ipaddress.get_status_class }}">{{ ipaddress.get_status_display }}</span>
|
<span class="label label-{{ ipaddress.get_status_class }}">{{ ipaddress.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Role</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'ipam:ipaddress_list' %}?role={{ ipaddress.role }}">{{ ipaddress.get_role_display }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field pattern_form.pattern %}
|
{% render_field pattern_form.pattern %}
|
||||||
{% render_field model_form.status %}
|
{% render_field model_form.status %}
|
||||||
|
{% render_field model_form.role %}
|
||||||
{% render_field model_form.vrf %}
|
{% render_field model_form.vrf %}
|
||||||
{% render_field model_form.description %}
|
{% render_field model_form.description %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.address %}
|
{% render_field form.address %}
|
||||||
{% render_field form.status %}
|
{% render_field form.status %}
|
||||||
|
{% render_field form.role %}
|
||||||
{% render_field form.vrf %}
|
{% render_field form.vrf %}
|
||||||
{% render_field form.description %}
|
{% render_field form.description %}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user