mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 00:36:11 -06:00
Merge branch 'feature' into 13150-table-translation
This commit is contained in:
commit
b36be1a9e5
@ -514,12 +514,18 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
|||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
|
rf_role = ChoiceField(
|
||||||
|
choices=WirelessRoleChoices,
|
||||||
|
required=False,
|
||||||
|
allow_blank=True,
|
||||||
|
allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only',
|
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only',
|
||||||
'description', 'bridge', 'poe_mode', 'poe_type', 'created', 'last_updated',
|
'description', 'bridge', 'poe_mode', 'poe_type', 'rf_role', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -696,6 +696,9 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
|||||||
poe_type = django_filters.MultipleChoiceFilter(
|
poe_type = django_filters.MultipleChoiceFilter(
|
||||||
choices=InterfacePoETypeChoices
|
choices=InterfacePoETypeChoices
|
||||||
)
|
)
|
||||||
|
rf_role = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=WirelessRoleChoices
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
|
@ -76,14 +76,14 @@ class PowerOutletBulkCreateForm(
|
|||||||
|
|
||||||
class InterfaceBulkCreateForm(
|
class InterfaceBulkCreateForm(
|
||||||
form_from_model(Interface, [
|
form_from_model(Interface, [
|
||||||
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type',
|
'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'poe_mode', 'poe_type', 'rf_role'
|
||||||
]),
|
]),
|
||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = Interface
|
model = Interface
|
||||||
field_order = (
|
field_order = (
|
||||||
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
||||||
'poe_type', 'mark_connected', 'description', 'tags',
|
'poe_type', 'mark_connected', 'rf_role', 'description', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
|||||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
||||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||||
|
from wireless.choices import WirelessRoleChoices
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CableBulkEditForm',
|
'CableBulkEditForm',
|
||||||
@ -922,8 +923,14 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
|
|||||||
initial='',
|
initial='',
|
||||||
label=_('PoE type')
|
label=_('PoE type')
|
||||||
)
|
)
|
||||||
|
rf_role = forms.ChoiceField(
|
||||||
|
choices=add_blank_choice(WirelessRoleChoices),
|
||||||
|
required=False,
|
||||||
|
initial='',
|
||||||
|
label=_('Wireless role')
|
||||||
|
)
|
||||||
|
|
||||||
nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
|
nullable_fields = ('label', 'description', 'poe_mode', 'poe_type', 'rf_role')
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateBulkEditForm(BulkEditForm):
|
class FrontPortTemplateBulkEditForm(BulkEditForm):
|
||||||
|
@ -826,13 +826,14 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
||||||
('PoE', ('poe_mode', 'poe_type'))
|
('PoE', ('poe_mode', 'poe_type')),
|
||||||
|
('Wireless', ('rf_role',))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge',
|
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext as _
|
|||||||
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
|
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
|
from wireless.choices import WirelessRoleChoices
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConsolePortTemplateImportForm',
|
'ConsolePortTemplateImportForm',
|
||||||
@ -96,11 +97,17 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('PoE type')
|
label=_('PoE type')
|
||||||
)
|
)
|
||||||
|
rf_role = forms.ChoiceField(
|
||||||
|
choices=WirelessRoleChoices,
|
||||||
|
required=False,
|
||||||
|
label=_('Wireless role')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'poe_mode',
|
||||||
|
'poe_type', 'rf_role'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -277,6 +277,9 @@ class InterfaceTemplateType(ComponentTemplateObjectType):
|
|||||||
def resolve_poe_type(self, info):
|
def resolve_poe_type(self, info):
|
||||||
return self.poe_type or None
|
return self.poe_type or None
|
||||||
|
|
||||||
|
def resolve_rf_role(self, info):
|
||||||
|
return self.rf_role or None
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemType(ComponentObjectType):
|
class InventoryItemType(ComponentObjectType):
|
||||||
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
|
component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
|
||||||
|
18
netbox/dcim/migrations/0179_interfacetemplate_rf_role.py
Normal file
18
netbox/dcim/migrations/0179_interfacetemplate_rf_role.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-07-18 07:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0178_virtual_chassis_member_counter'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='rf_role',
|
||||||
|
field=models.CharField(blank=True, max_length=30),
|
||||||
|
),
|
||||||
|
]
|
@ -13,6 +13,7 @@ from utilities.fields import ColorField, NaturalOrderingField
|
|||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
from utilities.tracking import TrackingModelMixin
|
from utilities.tracking import TrackingModelMixin
|
||||||
|
from wireless.choices import WirelessRoleChoices
|
||||||
from .device_components import (
|
from .device_components import (
|
||||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||||
RearPort,
|
RearPort,
|
||||||
@ -388,6 +389,12 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='PoE type'
|
verbose_name='PoE type'
|
||||||
)
|
)
|
||||||
|
rf_role = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=WirelessRoleChoices,
|
||||||
|
blank=True,
|
||||||
|
verbose_name='Wireless role'
|
||||||
|
)
|
||||||
|
|
||||||
component_model = Interface
|
component_model = Interface
|
||||||
|
|
||||||
@ -406,6 +413,11 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
'bridge': f"Bridge interface ({self.bridge}) must belong to the same module type"
|
'bridge': f"Bridge interface ({self.bridge}) must belong to the same module type"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
|
||||||
|
raise ValidationError({
|
||||||
|
'rf_role': "Wireless role may be set only on wireless interfaces."
|
||||||
|
})
|
||||||
|
|
||||||
def instantiate(self, **kwargs):
|
def instantiate(self, **kwargs):
|
||||||
return self.component_model(
|
return self.component_model(
|
||||||
name=self.resolve_name(kwargs.get('module')),
|
name=self.resolve_name(kwargs.get('module')),
|
||||||
@ -415,6 +427,7 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
mgmt_only=self.mgmt_only,
|
mgmt_only=self.mgmt_only,
|
||||||
poe_mode=self.poe_mode,
|
poe_mode=self.poe_mode,
|
||||||
poe_type=self.poe_type,
|
poe_type=self.poe_type,
|
||||||
|
rf_role=self.rf_role,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
instantiate.do_not_call_in_templates = True
|
instantiate.do_not_call_in_templates = True
|
||||||
@ -430,6 +443,7 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
'bridge': self.bridge.name if self.bridge else None,
|
'bridge': self.bridge.name if self.bridge else None,
|
||||||
'poe_mode': self.poe_mode,
|
'poe_mode': self.poe_mode,
|
||||||
'poe_type': self.poe_type,
|
'poe_type': self.poe_type,
|
||||||
|
'rf_role': self.rf_role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -229,7 +229,10 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
|||||||
|
|
||||||
class Meta(ComponentTemplateTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = models.InterfaceTemplate
|
model = models.InterfaceTemplate
|
||||||
fields = ('pk', 'name', 'label', 'enabled', 'mgmt_only', 'type', 'description', 'bridge', 'poe_mode', 'poe_type', 'actions')
|
fields = (
|
||||||
|
'pk', 'name', 'label', 'enabled', 'mgmt_only', 'type', 'description', 'bridge', 'poe_mode', 'poe_type',
|
||||||
|
'rf_role', 'actions',
|
||||||
|
)
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from graphene.types import Dynamic as GQLDynamic, List as GQLList, Union as GQLUnion
|
from graphene.types import Dynamic as GQLDynamic, List as GQLList, Union as GQLUnion, String as GQLString, NonNull as GQLNonNull
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
@ -452,6 +452,13 @@ class APIViewTestCases:
|
|||||||
# Compile list of fields to include
|
# Compile list of fields to include
|
||||||
fields_string = ''
|
fields_string = ''
|
||||||
for field_name, field in type_class._meta.fields.items():
|
for field_name, field in type_class._meta.fields.items():
|
||||||
|
is_string_array = False
|
||||||
|
if type(field.type) is GQLList:
|
||||||
|
if field.type.of_type is GQLString:
|
||||||
|
is_string_array = True
|
||||||
|
elif type(field.type.of_type) is GQLNonNull and field.type.of_type.of_type is GQLString:
|
||||||
|
is_string_array = True
|
||||||
|
|
||||||
if type(field) is GQLDynamic:
|
if type(field) is GQLDynamic:
|
||||||
# Dynamic fields must specify a subselection
|
# Dynamic fields must specify a subselection
|
||||||
fields_string += f'{field_name} {{ id }}\n'
|
fields_string += f'{field_name} {{ id }}\n'
|
||||||
@ -461,7 +468,7 @@ class APIViewTestCases:
|
|||||||
elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
|
elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
|
||||||
# Union types dont' have an id or consistent values
|
# Union types dont' have an id or consistent values
|
||||||
continue
|
continue
|
||||||
elif type(field.type) is GQLList and field_name != 'choices':
|
elif type(field.type) is GQLList and not is_string_array:
|
||||||
# TODO: Come up with something more elegant
|
# TODO: Come up with something more elegant
|
||||||
# Temporary hack to support automated testing of reverse generic relations
|
# Temporary hack to support automated testing of reverse generic relations
|
||||||
fields_string += f'{field_name} {{ id }}\n'
|
fields_string += f'{field_name} {{ id }}\n'
|
||||||
|
Loading…
Reference in New Issue
Block a user