mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-24 16:26:09 -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_null=True
|
||||
)
|
||||
rf_role = ChoiceField(
|
||||
choices=WirelessRoleChoices,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
allow_null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
fields = [
|
||||
'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(
|
||||
choices=InterfacePoETypeChoices
|
||||
)
|
||||
rf_role = django_filters.MultipleChoiceFilter(
|
||||
choices=WirelessRoleChoices
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
|
@ -76,14 +76,14 @@ class PowerOutletBulkCreateForm(
|
||||
|
||||
class InterfaceBulkCreateForm(
|
||||
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
|
||||
):
|
||||
model = Interface
|
||||
field_order = (
|
||||
'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.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
|
||||
__all__ = (
|
||||
'CableBulkEditForm',
|
||||
@ -922,8 +923,14 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
|
||||
initial='',
|
||||
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):
|
||||
|
@ -826,13 +826,14 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
||||
|
||||
fieldsets = (
|
||||
(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:
|
||||
model = InterfaceTemplate
|
||||
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.models import *
|
||||
from utilities.forms import BootstrapMixin
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
|
||||
__all__ = (
|
||||
'ConsolePortTemplateImportForm',
|
||||
@ -96,11 +97,17 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
||||
required=False,
|
||||
label=_('PoE type')
|
||||
)
|
||||
rf_role = forms.ChoiceField(
|
||||
choices=WirelessRoleChoices,
|
||||
required=False,
|
||||
label=_('Wireless role')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
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):
|
||||
return self.poe_type or None
|
||||
|
||||
def resolve_rf_role(self, info):
|
||||
return self.rf_role or None
|
||||
|
||||
|
||||
class InventoryItemType(ComponentObjectType):
|
||||
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.ordering import naturalize_interface
|
||||
from utilities.tracking import TrackingModelMixin
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
from .device_components import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||
RearPort,
|
||||
@ -388,6 +389,12 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
blank=True,
|
||||
verbose_name='PoE type'
|
||||
)
|
||||
rf_role = models.CharField(
|
||||
max_length=30,
|
||||
choices=WirelessRoleChoices,
|
||||
blank=True,
|
||||
verbose_name='Wireless role'
|
||||
)
|
||||
|
||||
component_model = Interface
|
||||
|
||||
@ -406,6 +413,11 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
'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):
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
@ -415,6 +427,7 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
mgmt_only=self.mgmt_only,
|
||||
poe_mode=self.poe_mode,
|
||||
poe_type=self.poe_type,
|
||||
rf_role=self.rf_role,
|
||||
**kwargs
|
||||
)
|
||||
instantiate.do_not_call_in_templates = True
|
||||
@ -430,6 +443,7 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
'bridge': self.bridge.name if self.bridge else None,
|
||||
'poe_mode': self.poe_mode,
|
||||
'poe_type': self.poe_type,
|
||||
'rf_role': self.rf_role,
|
||||
}
|
||||
|
||||
|
||||
|
@ -229,7 +229,10 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
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"
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
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.test import APIClient
|
||||
|
||||
@ -452,6 +452,13 @@ class APIViewTestCases:
|
||||
# Compile list of fields to include
|
||||
fields_string = ''
|
||||
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:
|
||||
# Dynamic fields must specify a subselection
|
||||
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):
|
||||
# Union types dont' have an id or consistent values
|
||||
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
|
||||
# Temporary hack to support automated testing of reverse generic relations
|
||||
fields_string += f'{field_name} {{ id }}\n'
|
||||
|
Loading…
Reference in New Issue
Block a user