1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #1337: Add WWN field to interfaces

This commit is contained in:
jeremystretch
2021-10-07 15:09:42 -04:00
parent 6463fd902c
commit 18c3bb673f
16 changed files with 107 additions and 19 deletions

View File

@ -3,6 +3,10 @@
!!! warning "PostgreSQL 10 Required"
NetBox v3.1 requires PostgreSQL 10 or later.
### Enhancements
* [#1337](https://github.com/netbox-community/netbox/issues/1337) - Add WWN field to interfaces
### Other Changes
* [#7318](https://github.com/netbox-community/netbox/issues/7318) - Raise minimum required PostgreSQL version from 9.6 to 10

View File

@ -645,7 +645,7 @@ class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, Co
model = Interface
fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address',
'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable',
'wwn', 'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable',
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
'_occupied',

View File

@ -2,11 +2,30 @@ from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from netaddr import AddrFormatError, EUI, mac_unix_expanded
from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded
from ipam.constants import BGP_ASN_MAX, BGP_ASN_MIN
from .lookups import PathContains
__all__ = (
'ASNField',
'MACAddressField',
'PathField',
'WWNField',
)
class mac_unix_expanded_uppercase(mac_unix_expanded):
word_fmt = '%.2X'
class eui64_unix_expanded_uppercase(eui64_unix_expanded):
word_fmt = '%.2X'
#
# Fields
#
class ASNField(models.BigIntegerField):
description = "32-bit ASN field"
@ -24,10 +43,6 @@ class ASNField(models.BigIntegerField):
return super().formfield(**defaults)
class mac_unix_expanded_uppercase(mac_unix_expanded):
word_fmt = '%.2X'
class MACAddressField(models.Field):
description = "PostgreSQL MAC Address field"
@ -42,8 +57,8 @@ class MACAddressField(models.Field):
return value
try:
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
except AddrFormatError as e:
raise ValidationError("Invalid MAC address format: {}".format(value))
except AddrFormatError:
raise ValidationError(f"Invalid MAC address format: {value}")
def db_type(self, connection):
return 'macaddr'
@ -54,6 +69,32 @@ class MACAddressField(models.Field):
return str(self.to_python(value))
class WWNField(models.Field):
description = "World Wide Name field"
def python_type(self):
return EUI
def from_db_value(self, value, expression, connection):
return self.to_python(value)
def to_python(self, value):
if value is None:
return value
try:
return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase)
except AddrFormatError:
raise ValidationError(f"Invalid WWN format: {value}")
def db_type(self, connection):
return 'macaddr8'
def get_prep_value(self, value):
if not value:
return None
return str(self.to_python(value))
class PathField(ArrayField):
"""
An ArrayField which holds a set of objects, each identified by a (type, ID) tuple.

View File

@ -10,7 +10,7 @@ from tenancy.filtersets import TenancyFilterSet
from tenancy.models import Tenant
from utilities.choices import ColorChoices
from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
TreeNodeMultipleChoiceFilter,
)
from virtualization.models import Cluster
@ -964,6 +964,7 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
label='LAG interface (ID)',
)
mac_address = MultiValueMACAddressFilter()
wwn = MultiValueWWNFilter()
tag = TagFilter()
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',

View File

@ -921,7 +921,8 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm(
form_from_model(Interface, [
'label', 'type', 'parent', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'mode',
'label', 'type', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description',
'mode',
]),
BootstrapMixin,
AddRemoveTagsForm,
@ -972,7 +973,8 @@ class InterfaceBulkEditForm(
class Meta:
nullable_fields = [
'label', 'parent', 'lag', 'mac_address', 'mtu', 'description', 'mode', 'untagged_vlan', 'tagged_vlans'
'label', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'untagged_vlan',
'tagged_vlans',
]
def __init__(self, *args, **kwargs):

View File

@ -577,8 +577,8 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
class Meta:
model = Interface
fields = (
'device', 'name', 'label', 'parent', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'mtu',
'mgmt_only', 'description', 'mode',
'device', 'name', 'label', 'parent', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'wwn',
'mtu', 'mgmt_only', 'description', 'mode',
)
def __init__(self, *args, **kwargs):

View File

@ -957,7 +957,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
model = Interface
field_groups = [
['q', 'tag'],
['name', 'label', 'type', 'enabled', 'mgmt_only', 'mac_address'],
['name', 'label', 'type', 'enabled', 'mgmt_only', 'mac_address', 'wwn'],
['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'],
]
type = forms.MultipleChoiceField(
@ -981,6 +981,10 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
required=False,
label='MAC address'
)
wwn = forms.CharField(
required=False,
label='WWN'
)
tag = TagFilterField(model)

View File

@ -1091,7 +1091,7 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
class Meta:
model = Interface
fields = [
'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mac_address', 'mtu', 'mgmt_only',
'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
'mark_connected', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
]
widgets = {

View File

@ -0,0 +1,17 @@
import dcim.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0133_port_colors'),
]
operations = [
migrations.AddField(
model_name='interface',
name='wwn',
field=dcim.fields.WWNField(blank=True, null=True),
),
]

View File

@ -9,7 +9,7 @@ from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import *
from dcim.constants import *
from dcim.fields import MACAddressField
from dcim.fields import MACAddressField, WWNField
from dcim.svg import CableTraceSVG
from extras.utils import extras_features
from netbox.models import PrimaryModel
@ -511,6 +511,12 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
verbose_name='Management only',
help_text='This interface is used only for out-of-band management'
)
wwn = WWNField(
null=True,
blank=True,
verbose_name='WWN',
help_text='64-bit World Wide Name'
)
untagged_vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.SET_NULL,

View File

@ -492,7 +492,7 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
class Meta(DeviceComponentTable.Meta):
model = Interface
fields = (
'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address',
'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses',
'untagged_vlan', 'tagged_vlans',
)
@ -524,7 +524,7 @@ class DeviceInterfaceTable(InterfaceTable):
class Meta(DeviceComponentTable.Meta):
model = Interface
fields = (
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address',
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses',
'untagged_vlan', 'tagged_vlans', 'actions',
)

View File

@ -1469,6 +1469,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'enabled': False,
'lag': interfaces[3].pk,
'mac_address': EUI('01:02:03:04:05:06'),
'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
'mtu': 65000,
'mgmt_only': True,
'description': 'A front port',
@ -1485,6 +1486,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'enabled': False,
'lag': interfaces[3].pk,
'mac_address': EUI('01:02:03:04:05:06'),
'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
'mtu': 2000,
'mgmt_only': True,
'description': 'A front port',
@ -1499,6 +1501,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'enabled': True,
'lag': interfaces[3].pk,
'mac_address': EUI('01:02:03:04:05:06'),
'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
'mtu': 2000,
'mgmt_only': True,
'description': 'New description',

View File

@ -2,7 +2,7 @@ import graphene
from graphene_django.converter import convert_django_field
from taggit.managers import TaggableManager
from dcim.fields import MACAddressField
from dcim.fields import MACAddressField, WWNField
from ipam.fields import IPAddressField, IPNetworkField
@ -17,6 +17,7 @@ def convert_field_to_tags_list(field, registry=None):
@convert_django_field.register(IPAddressField)
@convert_django_field.register(IPNetworkField)
@convert_django_field.register(MACAddressField)
@convert_django_field.register(WWNField)
def convert_field_to_string(field, registry=None):
# TODO: Update to use get_django_field_description under django_graphene v3.0
return graphene.String(description=field.help_text, required=not field.null)

View File

@ -91,6 +91,10 @@
<th scope="row">MAC Address</th>
<td><span class="text-monospace">{{ object.mac_address|placeholder }}</span></td>
</tr>
<tr>
<th scope="row">WWN</th>
<td><span class="text-monospace">{{ object.wwn|placeholder }}</span></td>
</tr>
<tr>
<th scope="row">802.1Q Mode</th>
<td>{{ object.get_mode_display|placeholder }}</td>

View File

@ -20,6 +20,7 @@
{% render_field form.parent %}
{% render_field form.lag %}
{% render_field form.mac_address %}
{% render_field form.wwn %}
{% render_field form.mtu %}
{% render_field form.description %}
{% render_field form.tags %}

View File

@ -57,6 +57,10 @@ class MultiValueMACAddressFilter(django_filters.MultipleChoiceFilter):
field_class = multivalue_field_factory(MACAddressField)
class MultiValueWWNFilter(django_filters.MultipleChoiceFilter):
field_class = multivalue_field_factory(MACAddressField)
class TreeNodeMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
"""
Filters for a set of Models, including all descendant models within a Tree. Example: [<Region: R1>,<Region: R2>]