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:
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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 = {
|
||||
|
17
netbox/dcim/migrations/0134_interface_wwn.py
Normal file
17
netbox/dcim/migrations/0134_interface_wwn.py
Normal 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),
|
||||
),
|
||||
]
|
@ -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,
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -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>]
|
||||
|
Reference in New Issue
Block a user