Fix UI regression: transform= changed the rendered column, not just CSV export
django-tables2's ManyToManyColumn.render() and NetBox's own value() override both call self.transform() for each item -- there's no built-in way to give CSV export a different representation than the rendered column. Setting transform=lambda obj: obj.name on the three module_bay_types columns to fix CSV export therefore also dropped the manufacturer prefix from the Bay Types column in the Module Bays, Module Bay Templates, and Module Types list views -- the opposite of what ModuleBayType.__str__() adds that prefix for. Verified directly: with the old transform=, two same-named bay types from different manufacturers render as visually identical "SFP28" list items. Add export_transform to NetBox's ManyToManyColumn subclass, defaulting to transform so existing columns are unaffected, and used only by value() (export) rather than render() (UI). Switch the three columns to export_transform=lambda obj: obj.name, leaving transform unset so render() keeps str()'s manufacturer prefix. Extended the existing round-trip test to also assert the rendered column still includes the manufacturer name; confirmed it fails against the old transform= approach and passes with export_transform=.
This commit is contained in:
parent
faafb1a11f
commit
e5c0d60d09
|
|
@ -1028,8 +1028,8 @@ class ModuleBayTable(ModularDeviceComponentTable):
|
|||
module_bay_types = columns.ManyToManyColumn(
|
||||
verbose_name=_('Bay Types'),
|
||||
linkify_item=True,
|
||||
# __str__() includes the manufacturer, but import resolves by name alone.
|
||||
transform=lambda obj: obj.name,
|
||||
# Export as bare name (import resolves by name alone); UI keeps str()'s manufacturer prefix.
|
||||
export_transform=lambda obj: obj.name,
|
||||
)
|
||||
|
||||
class Meta(ModularDeviceComponentTable.Meta):
|
||||
|
|
|
|||
|
|
@ -305,8 +305,8 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
|
|||
module_bay_types = columns.ManyToManyColumn(
|
||||
verbose_name=_('Bay Types'),
|
||||
linkify_item=True,
|
||||
# __str__() includes the manufacturer, but import resolves by name alone.
|
||||
transform=lambda obj: obj.name,
|
||||
# Export as bare name (import resolves by name alone); UI keeps str()'s manufacturer prefix.
|
||||
export_transform=lambda obj: obj.name,
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete')
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ class ModuleTypeTable(PrimaryModelTable):
|
|||
module_bay_types = columns.ManyToManyColumn(
|
||||
verbose_name=_('Bay Types'),
|
||||
linkify_item=True,
|
||||
# __str__() includes the manufacturer, but import resolves by name alone.
|
||||
transform=lambda obj: obj.name,
|
||||
# Export as bare name (import resolves by name alone); UI keeps str()'s manufacturer prefix.
|
||||
export_transform=lambda obj: obj.name,
|
||||
)
|
||||
model = tables.Column(
|
||||
linkify=True,
|
||||
|
|
|
|||
|
|
@ -516,16 +516,18 @@ class ModuleTypeImportFormTestCase(TestCase):
|
|||
self.assertFalse(form.save().module_bay_types.exists())
|
||||
|
||||
def test_module_bay_types_round_trips_through_the_table_column_export_value(self):
|
||||
"""The table's CSV export (multi-value separator, name-only transform) must be re-importable."""
|
||||
"""The table's CSV export (multi-value separator, name-only transform) must be re-importable,
|
||||
without changing the rendered UI column, which should keep str()'s manufacturer prefix."""
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||
bay_type_a = ModuleBayType.objects.create(name='SFP28', slug='sfp28', manufacturer=manufacturer)
|
||||
bay_type_b = ModuleBayType.objects.create(name='QSFP28', slug='qsfp28', manufacturer=manufacturer)
|
||||
original = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
|
||||
original.module_bay_types.set([bay_type_a, bay_type_b])
|
||||
|
||||
table = ModuleTypeTable([original])
|
||||
exported_value = table.columns['module_bay_types'].column.value(original.module_bay_types.all())
|
||||
column = ModuleTypeTable([original]).columns['module_bay_types'].column
|
||||
exported_value = column.value(original.module_bay_types.all())
|
||||
self.assertEqual(exported_value, 'QSFP28, SFP28')
|
||||
self.assertIn(str(manufacturer), str(column.render(original.module_bay_types.all())))
|
||||
|
||||
form = ModuleTypeImportForm({
|
||||
'manufacturer': manufacturer.name,
|
||||
|
|
|
|||
|
|
@ -131,9 +131,17 @@ class DurationColumn(tables.Column):
|
|||
class ManyToManyColumn(tables.ManyToManyColumn):
|
||||
"""
|
||||
Overrides django-tables2's stock ManyToManyColumn to ensure that value() returns only plaintext data.
|
||||
|
||||
export_transform: optional callable used only for value() (CSV/table export), letting export use a
|
||||
different representation than the rendered column (e.g. a bare name where the UI shows str(obj)).
|
||||
Defaults to transform, matching the stock behavior of exporting the same text that's rendered.
|
||||
"""
|
||||
def __init__(self, *args, export_transform=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.export_transform = export_transform or self.transform
|
||||
|
||||
def value(self, value):
|
||||
items = [self.transform(item) for item in self.filter(value)]
|
||||
items = [self.export_transform(item) for item in self.filter(value)]
|
||||
return self.separator.join(items)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue