UI cleanup for front/rear ports

This commit is contained in:
Jeremy Stretch
2025-11-21 14:03:16 -05:00
parent 85d4066501
commit 62620101db
5 changed files with 52 additions and 27 deletions

View File

@@ -1119,7 +1119,7 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
FieldSet('device_type', name=_('Device Type')), FieldSet('device_type', name=_('Device Type')),
FieldSet('module_type', name=_('Module Type')), FieldSet('module_type', name=_('Module Type')),
), ),
'name', 'label', 'positions', 'rear_ports', 'description', 'name', 'label', 'type', 'positions', 'rear_ports', 'description',
), ),
) )

View File

@@ -133,9 +133,10 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
# Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate # Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate
# positions # positions
positions = self.cleaned_data['positions']
frontport_count = len(self.cleaned_data['name']) frontport_count = len(self.cleaned_data['name'])
rearport_count = len(self.cleaned_data['rear_ports']) rearport_count = len(self.cleaned_data['rear_ports'])
if frontport_count != rearport_count: if frontport_count * positions != rearport_count:
raise forms.ValidationError({ raise forms.ValidationError({
'rear_ports': _( 'rear_ports': _(
"The number of front port templates to be created ({frontport_count}) must match the selected " "The number of front port templates to be created ({frontport_count}) must match the selected "
@@ -251,10 +252,11 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
def clean(self): def clean(self):
super(NetBoxModelForm, self).clean() super(NetBoxModelForm, self).clean()
# Check that the number of FrontPorts to be created matches the selected number of RearPort positions # Check that the number of FrontPorts to be created matches the selected number of RearPorts
positions = self.cleaned_data['positions']
frontport_count = len(self.cleaned_data['name']) frontport_count = len(self.cleaned_data['name'])
rearport_count = len(self.cleaned_data['rear_ports']) rearport_count = len(self.cleaned_data['rear_ports'])
if frontport_count != rearport_count: if frontport_count * positions != rearport_count:
raise forms.ValidationError({ raise forms.ValidationError({
'rear_ports': _( 'rear_ports': _(
"The number of front ports to be created ({frontport_count}) must match the selected number of " "The number of front ports to be created ({frontport_count}) must match the selected number of "

View File

@@ -635,6 +635,20 @@ class RearPortTemplate(ModularComponentTemplateModel):
verbose_name = _('rear port template') verbose_name = _('rear port template')
verbose_name_plural = _('rear port templates') verbose_name_plural = _('rear port templates')
def clean(self):
super().clean()
# Check that positions count is greater than or equal to the number of associated FrontPortTemplates
if not self._state.adding:
assignment_count = self.assignments.count()
if self.positions < assignment_count:
raise ValidationError({
"positions": _(
"The number of positions cannot be less than the number of mapped front port templates "
"({count})"
).format(count=assignment_count)
})
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')),

View File

@@ -749,12 +749,9 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
color = columns.ColorColumn( color = columns.ColorColumn(
verbose_name=_('Color'), verbose_name=_('Color'),
) )
rear_port_position = tables.Column( assignments = columns.ManyToManyColumn(
verbose_name=_('Position') verbose_name=_('Assignments'),
) transform=lambda obj: f'{obj.rear_port}:{obj.rear_port_position}'
rear_port = tables.Column(
verbose_name=_('Rear Port'),
linkify=True
) )
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='dcim:frontport_list' url_name='dcim:frontport_list'
@@ -763,12 +760,12 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
class Meta(DeviceComponentTable.Meta): class Meta(DeviceComponentTable.Meta):
model = models.FrontPort model = models.FrontPort
fields = ( fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description', 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'assignments',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created',
'inventory_items', 'tags', 'created', 'last_updated', 'last_updated',
) )
default_columns = ( default_columns = (
'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'assignments', 'description',
) )
@@ -786,11 +783,11 @@ class DeviceFrontPortTable(FrontPortTable):
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
model = models.FrontPort model = models.FrontPort
fields = ( fields = (
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position', 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'assignments',
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
) )
default_columns = ( default_columns = (
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'link_peer', 'pk', 'name', 'label', 'type', 'color', 'positions', 'assignments', 'description', 'cable', 'link_peer',
) )
@@ -805,6 +802,10 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
color = columns.ColorColumn( color = columns.ColorColumn(
verbose_name=_('Color'), verbose_name=_('Color'),
) )
assignments = columns.ManyToManyColumn(
verbose_name=_('Assignments'),
transform=lambda obj: f'{obj.front_port}:{obj.front_port_position}'
)
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='dcim:rearport_list' url_name='dcim:rearport_list'
) )
@@ -812,10 +813,13 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
class Meta(DeviceComponentTable.Meta): class Meta(DeviceComponentTable.Meta):
model = models.RearPort model = models.RearPort
fields = ( fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description', 'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'assignments',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created', 'last_updated', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created',
'last_updated',
)
default_columns = (
'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'assignments', 'description',
) )
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
class DeviceRearPortTable(RearPortTable): class DeviceRearPortTable(RearPortTable):
@@ -832,11 +836,11 @@ class DeviceRearPortTable(RearPortTable):
class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta): class Meta(CableTerminationTable.Meta, DeviceComponentTable.Meta):
model = models.RearPort model = models.RearPort
fields = ( fields = (
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected', 'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'assignments',
'cable', 'cable_color', 'link_peer', 'tags', 'actions', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
) )
default_columns = ( default_columns = (
'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'link_peer', 'pk', 'name', 'label', 'type', 'positions', 'assignments', 'description', 'cable', 'link_peer',
) )

View File

@@ -249,12 +249,13 @@ class InterfaceTemplateTable(ComponentTemplateTable):
class FrontPortTemplateTable(ComponentTemplateTable): class FrontPortTemplateTable(ComponentTemplateTable):
rear_port_position = tables.Column(
verbose_name=_('Position')
)
color = columns.ColorColumn( color = columns.ColorColumn(
verbose_name=_('Color'), verbose_name=_('Color'),
) )
assignments = columns.ManyToManyColumn(
verbose_name=_('Assignments'),
transform=lambda obj: f'{obj.rear_port}:{obj.rear_port_position}'
)
actions = columns.ActionsColumn( actions = columns.ActionsColumn(
actions=('edit', 'delete'), actions=('edit', 'delete'),
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -262,7 +263,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta): class Meta(ComponentTemplateTable.Meta):
model = models.FrontPortTemplate model = models.FrontPortTemplate
fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions') fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'assignments', 'description', 'actions')
empty_text = "None" empty_text = "None"
@@ -270,6 +271,10 @@ class RearPortTemplateTable(ComponentTemplateTable):
color = columns.ColorColumn( color = columns.ColorColumn(
verbose_name=_('Color'), verbose_name=_('Color'),
) )
assignments = columns.ManyToManyColumn(
verbose_name=_('Assignments'),
transform=lambda obj: f'{obj.front_port}:{obj.front_port_position}'
)
actions = columns.ActionsColumn( actions = columns.ActionsColumn(
actions=('edit', 'delete'), actions=('edit', 'delete'),
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
@@ -277,7 +282,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta): class Meta(ComponentTemplateTable.Meta):
model = models.RearPortTemplate model = models.RearPortTemplate
fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions') fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'assignments', 'description', 'actions')
empty_text = "None" empty_text = "None"