mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #10371: Add operational status field for modules
This commit is contained in:
@ -58,9 +58,11 @@ The following model fields support configurable choices:
|
|||||||
* `circuits.Circuit.status`
|
* `circuits.Circuit.status`
|
||||||
* `dcim.Device.status`
|
* `dcim.Device.status`
|
||||||
* `dcim.Location.status`
|
* `dcim.Location.status`
|
||||||
|
* `dcim.Module.status`
|
||||||
* `dcim.PowerFeed.status`
|
* `dcim.PowerFeed.status`
|
||||||
* `dcim.Rack.status`
|
* `dcim.Rack.status`
|
||||||
* `dcim.Site.status`
|
* `dcim.Site.status`
|
||||||
|
* `dcim.VirtualDeviceContext.status`
|
||||||
* `extras.JournalEntry.kind`
|
* `extras.JournalEntry.kind`
|
||||||
* `ipam.IPAddress.status`
|
* `ipam.IPAddress.status`
|
||||||
* `ipam.IPRange.status`
|
* `ipam.IPRange.status`
|
||||||
|
@ -18,6 +18,13 @@ The [module bay](./modulebay.md) into which the module is installed.
|
|||||||
|
|
||||||
The [module type](./moduletype.md) which represents the physical make & model of hardware. By default, module components will be instantiated automatically from the module type when creating a new module.
|
The [module type](./moduletype.md) which represents the physical make & model of hardware. By default, module components will be instantiated automatically from the module type when creating a new module.
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
The module's operational status.
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
Additional statuses may be defined by setting `Module.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
|
||||||
|
|
||||||
### Serial Number
|
### Serial Number
|
||||||
|
|
||||||
The unique physical serial number assigned to this module by its manufacturer.
|
The unique physical serial number assigned to this module by its manufacturer.
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
|
* [#815](https://github.com/netbox-community/netbox/issues/815) - Enable specifying terminations when bulk importing circuits
|
||||||
|
* [#10371](https://github.com/netbox-community/netbox/issues/10371) - Add operational status field for modules
|
||||||
* [#10945](https://github.com/netbox-community/netbox/issues/10945) - Enabled recurring execution of scheduled reports & scripts
|
* [#10945](https://github.com/netbox-community/netbox/issues/10945) - Enabled recurring execution of scheduled reports & scripts
|
||||||
* [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
|
* [#11090](https://github.com/netbox-community/netbox/issues/11090) - Add regular expression support to global search engine
|
||||||
* [#11022](https://github.com/netbox-community/netbox/issues/11022) - Introduce `QUEUE_MAPPINGS` configuration parameter to allow customization of background task prioritization
|
* [#11022](https://github.com/netbox-community/netbox/issues/11022) - Introduce `QUEUE_MAPPINGS` configuration parameter to allow customization of background task prioritization
|
||||||
@ -134,6 +135,8 @@ This release introduces a new programmatic API that enables plugins and custom s
|
|||||||
* Added a `description` field
|
* Added a `description` field
|
||||||
* dcim.Interface
|
* dcim.Interface
|
||||||
* Added the `vdcs` field
|
* Added the `vdcs` field
|
||||||
|
* dcim.Module
|
||||||
|
* Added a `status` field
|
||||||
* dcim.ModuleType
|
* dcim.ModuleType
|
||||||
* Added a `description` field
|
* Added a `description` field
|
||||||
* Added optional `weight` and `weight_unit` fields
|
* Added optional `weight` and `weight_unit` fields
|
||||||
|
@ -697,8 +697,8 @@ class ModuleSerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description',
|
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag',
|
||||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,6 +194,30 @@ class DeviceAirflowChoices(ChoiceSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Modules
|
||||||
|
#
|
||||||
|
|
||||||
|
class ModuleStatusChoices(ChoiceSet):
|
||||||
|
key = 'Module.status'
|
||||||
|
|
||||||
|
STATUS_OFFLINE = 'offline'
|
||||||
|
STATUS_ACTIVE = 'active'
|
||||||
|
STATUS_PLANNED = 'planned'
|
||||||
|
STATUS_STAGED = 'staged'
|
||||||
|
STATUS_FAILED = 'failed'
|
||||||
|
STATUS_DECOMMISSIONING = 'decommissioning'
|
||||||
|
|
||||||
|
CHOICES = [
|
||||||
|
(STATUS_OFFLINE, 'Offline', 'gray'),
|
||||||
|
(STATUS_ACTIVE, 'Active', 'green'),
|
||||||
|
(STATUS_PLANNED, 'Planned', 'cyan'),
|
||||||
|
(STATUS_STAGED, 'Staged', 'blue'),
|
||||||
|
(STATUS_FAILED, 'Failed', 'red'),
|
||||||
|
(STATUS_DECOMMISSIONING, 'Decommissioning', 'yellow'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ConsolePorts
|
# ConsolePorts
|
||||||
#
|
#
|
||||||
|
@ -1082,13 +1082,17 @@ class ModuleFilterSet(NetBoxModelFilterSet):
|
|||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
label=_('Device (ID)'),
|
label=_('Device (ID)'),
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=ModuleStatusChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
serial = MultiValueCharFilter(
|
serial = MultiValueCharFilter(
|
||||||
lookup_expr='iexact'
|
lookup_expr='iexact'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = ['id', 'asset_tag']
|
fields = ['id', 'status', 'asset_tag']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -574,6 +574,12 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
'manufacturer_id': '$manufacturer'
|
'manufacturer_id': '$manufacturer'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
status = forms.ChoiceField(
|
||||||
|
choices=add_blank_choice(ModuleStatusChoices),
|
||||||
|
required=False,
|
||||||
|
initial='',
|
||||||
|
widget=StaticSelect()
|
||||||
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
required=False,
|
required=False,
|
||||||
@ -590,7 +596,7 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Module
|
model = Module
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('manufacturer', 'module_type', 'serial', 'description')),
|
(None, ('manufacturer', 'module_type', 'status', 'serial', 'description')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('serial', 'description', 'comments')
|
nullable_fields = ('serial', 'description', 'comments')
|
||||||
|
|
||||||
|
@ -450,11 +450,15 @@ class ModuleImportForm(NetBoxModelImportForm):
|
|||||||
queryset=ModuleType.objects.all(),
|
queryset=ModuleType.objects.all(),
|
||||||
to_field_name='model'
|
to_field_name='model'
|
||||||
)
|
)
|
||||||
|
status = CSVChoiceField(
|
||||||
|
choices=ModuleStatusChoices,
|
||||||
|
help_text=_('Operational status')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description', 'comments', 'tags',
|
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'status', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
|
@ -763,7 +763,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||||||
model = Module
|
model = Module
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
(None, ('q', 'filter_id', 'tag')),
|
||||||
('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')),
|
('Hardware', ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
|
||||||
)
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@ -780,6 +780,10 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
fetch_trigger='open'
|
fetch_trigger='open'
|
||||||
)
|
)
|
||||||
|
status = MultipleChoiceField(
|
||||||
|
choices=ModuleStatusChoices,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
serial = forms.CharField(
|
serial = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
@ -703,7 +703,7 @@ class ModuleForm(NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Module', (
|
('Module', (
|
||||||
'device', 'module_bay', 'manufacturer', 'module_type', 'description', 'tags',
|
'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'description', 'tags',
|
||||||
)),
|
)),
|
||||||
('Hardware', (
|
('Hardware', (
|
||||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
||||||
@ -713,7 +713,7 @@ class ModuleForm(NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags',
|
'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag', 'tags',
|
||||||
'replicate_components', 'adopt_components', 'description', 'comments',
|
'replicate_components', 'adopt_components', 'description', 'comments',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
18
netbox/dcim/migrations/0167_module_status.py
Normal file
18
netbox/dcim/migrations/0167_module_status.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.2 on 2022-12-09 15:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0166_virtualdevicecontext'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='module',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(default='active', max_length=50),
|
||||||
|
),
|
||||||
|
]
|
@ -925,6 +925,11 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='instances'
|
related_name='instances'
|
||||||
)
|
)
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=ModuleStatusChoices,
|
||||||
|
default=ModuleStatusChoices.STATUS_ACTIVE
|
||||||
|
)
|
||||||
serial = models.CharField(
|
serial = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -939,7 +944,7 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
help_text=_('A unique tag used to identify this device')
|
help_text=_('A unique tag used to identify this device')
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('device', 'module_type')
|
clone_fields = ('device', 'module_type', 'status')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('module_bay',)
|
ordering = ('module_bay',)
|
||||||
@ -950,6 +955,9 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:module', args=[self.pk])
|
return reverse('dcim:module', args=[self.pk])
|
||||||
|
|
||||||
|
def get_status_color(self):
|
||||||
|
return ModuleStatusChoices.colors.get(self.status)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ class ModuleTable(NetBoxTable):
|
|||||||
module_type = tables.Column(
|
module_type = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
status = columns.ChoiceFieldColumn()
|
||||||
comments = columns.MarkdownColumn()
|
comments = columns.MarkdownColumn()
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:module_list'
|
url_name='dcim:module_list'
|
||||||
@ -64,9 +65,9 @@ class ModuleTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Module
|
model = Module
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'description',
|
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
|
||||||
'comments', 'tags',
|
'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag',
|
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
|
||||||
)
|
)
|
||||||
|
@ -1271,6 +1271,7 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'module_bay': module_bays[3].pk,
|
'module_bay': module_bays[3].pk,
|
||||||
'module_type': module_types[0].pk,
|
'module_type': module_types[0].pk,
|
||||||
|
'status': ModuleStatusChoices.STATUS_ACTIVE,
|
||||||
'serial': 'ABC123',
|
'serial': 'ABC123',
|
||||||
'asset_tag': 'Foo1',
|
'asset_tag': 'Foo1',
|
||||||
},
|
},
|
||||||
@ -1278,6 +1279,7 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'module_bay': module_bays[4].pk,
|
'module_bay': module_bays[4].pk,
|
||||||
'module_type': module_types[1].pk,
|
'module_type': module_types[1].pk,
|
||||||
|
'status': ModuleStatusChoices.STATUS_ACTIVE,
|
||||||
'serial': 'DEF456',
|
'serial': 'DEF456',
|
||||||
'asset_tag': 'Foo2',
|
'asset_tag': 'Foo2',
|
||||||
},
|
},
|
||||||
@ -1285,6 +1287,7 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'module_bay': module_bays[5].pk,
|
'module_bay': module_bays[5].pk,
|
||||||
'module_type': module_types[2].pk,
|
'module_type': module_types[2].pk,
|
||||||
|
'status': ModuleStatusChoices.STATUS_ACTIVE,
|
||||||
'serial': 'GHI789',
|
'serial': 'GHI789',
|
||||||
'asset_tag': 'Foo3',
|
'asset_tag': 'Foo3',
|
||||||
},
|
},
|
||||||
|
@ -1876,15 +1876,15 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
ModuleBay.objects.bulk_create(module_bays)
|
ModuleBay.objects.bulk_create(module_bays)
|
||||||
|
|
||||||
modules = (
|
modules = (
|
||||||
Module(device=devices[0], module_bay=module_bays[0], module_type=module_types[0], serial='A', asset_tag='A'),
|
Module(device=devices[0], module_bay=module_bays[0], module_type=module_types[0], status=ModuleStatusChoices.STATUS_ACTIVE, serial='A', asset_tag='A'),
|
||||||
Module(device=devices[0], module_bay=module_bays[1], module_type=module_types[1], serial='B', asset_tag='B'),
|
Module(device=devices[0], module_bay=module_bays[1], module_type=module_types[1], status=ModuleStatusChoices.STATUS_ACTIVE, serial='B', asset_tag='B'),
|
||||||
Module(device=devices[0], module_bay=module_bays[2], module_type=module_types[2], serial='C', asset_tag='C'),
|
Module(device=devices[0], module_bay=module_bays[2], module_type=module_types[2], status=ModuleStatusChoices.STATUS_ACTIVE, serial='C', asset_tag='C'),
|
||||||
Module(device=devices[1], module_bay=module_bays[3], module_type=module_types[0], serial='D', asset_tag='D'),
|
Module(device=devices[1], module_bay=module_bays[3], module_type=module_types[0], status=ModuleStatusChoices.STATUS_ACTIVE, serial='D', asset_tag='D'),
|
||||||
Module(device=devices[1], module_bay=module_bays[4], module_type=module_types[1], serial='E', asset_tag='E'),
|
Module(device=devices[1], module_bay=module_bays[4], module_type=module_types[1], status=ModuleStatusChoices.STATUS_ACTIVE, serial='E', asset_tag='E'),
|
||||||
Module(device=devices[1], module_bay=module_bays[5], module_type=module_types[2], serial='F', asset_tag='F'),
|
Module(device=devices[1], module_bay=module_bays[5], module_type=module_types[2], status=ModuleStatusChoices.STATUS_ACTIVE, serial='F', asset_tag='F'),
|
||||||
Module(device=devices[2], module_bay=module_bays[6], module_type=module_types[0], serial='G', asset_tag='G'),
|
Module(device=devices[2], module_bay=module_bays[6], module_type=module_types[0], status=ModuleStatusChoices.STATUS_ACTIVE, serial='G', asset_tag='G'),
|
||||||
Module(device=devices[2], module_bay=module_bays[7], module_type=module_types[1], serial='H', asset_tag='H'),
|
Module(device=devices[2], module_bay=module_bays[7], module_type=module_types[1], status=ModuleStatusChoices.STATUS_PLANNED, serial='H', asset_tag='H'),
|
||||||
Module(device=devices[2], module_bay=module_bays[8], module_type=module_types[2], serial='I', asset_tag='I'),
|
Module(device=devices[2], module_bay=module_bays[8], module_type=module_types[2], status=ModuleStatusChoices.STATUS_FAILED, serial='I', asset_tag='I'),
|
||||||
)
|
)
|
||||||
Module.objects.bulk_create(modules)
|
Module.objects.bulk_create(modules)
|
||||||
|
|
||||||
@ -1912,6 +1912,10 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'device_id': [device_types[0].pk, device_types[1].pk]}
|
params = {'device_id': [device_types[0].pk, device_types[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
|
def test_status(self):
|
||||||
|
params = {'status': [ModuleStatusChoices.STATUS_PLANNED, ModuleStatusChoices.STATUS_FAILED]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_serial(self):
|
def test_serial(self):
|
||||||
params = {'serial': ['A', 'B']}
|
params = {'serial': ['A', 'B']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -1887,26 +1887,28 @@ class ModuleTestCase(
|
|||||||
'device': devices[0].pk,
|
'device': devices[0].pk,
|
||||||
'module_bay': module_bays[3].pk,
|
'module_bay': module_bays[3].pk,
|
||||||
'module_type': module_types[0].pk,
|
'module_type': module_types[0].pk,
|
||||||
|
'status': ModuleStatusChoices.STATUS_ACTIVE,
|
||||||
'serial': 'A',
|
'serial': 'A',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
'module_type': module_types[3].pk,
|
'module_type': module_types[3].pk,
|
||||||
|
'status': ModuleStatusChoices.STATUS_PLANNED,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"device,module_bay,module_type,serial,asset_tag",
|
"device,module_bay,module_type,status,serial,asset_tag",
|
||||||
"Device 2,Module Bay 1,Module Type 1,A,A",
|
"Device 2,Module Bay 1,Module Type 1,active,A,A",
|
||||||
"Device 2,Module Bay 2,Module Type 2,B,B",
|
"Device 2,Module Bay 2,Module Type 2,planned,B,B",
|
||||||
"Device 2,Module Bay 3,Module Type 3,C,C",
|
"Device 2,Module Bay 3,Module Type 3,failed,C,C",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
"id,serial",
|
"id,status,serial",
|
||||||
f"{modules[0].pk},Serial 2",
|
f"{modules[0].pk},offline,Serial 2",
|
||||||
f"{modules[1].pk},Serial 3",
|
f"{modules[1].pk},offline,Serial 3",
|
||||||
f"{modules[2].pk},Serial 1",
|
f"{modules[2].pk},offline,Serial 1",
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
@ -62,6 +62,10 @@
|
|||||||
<th scope="row">Module Type</th>
|
<th scope="row">Module Type</th>
|
||||||
<td>{{ object.module_type|linkify }}</td>
|
<td>{{ object.module_type|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Status</th>
|
||||||
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Description</th>
|
<th scope="row">Description</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
Reference in New Issue
Block a user