diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py
index b1c5931e2..1a16142d0 100644
--- a/netbox/dcim/forms/model_forms.py
+++ b/netbox/dcim/forms/model_forms.py
@@ -436,7 +436,7 @@ class ModuleTypeForm(NetBoxModelForm):
fieldsets = (
FieldSet('profile', 'manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
- FieldSet('attributes', name=_('Profile Attributes')),
+ FieldSet('attribute_data', name=_('Profile Attributes')),
FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
)
@@ -444,7 +444,7 @@ class ModuleTypeForm(NetBoxModelForm):
model = ModuleType
fields = [
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
- 'attributes', 'comments', 'tags',
+ 'attribute_data', 'comments', 'tags',
]
diff --git a/netbox/dcim/migrations/0203_moduletypeprofile.py b/netbox/dcim/migrations/0203_moduletypeprofile.py
index 69c373340..33b14e3bb 100644
--- a/netbox/dcim/migrations/0203_moduletypeprofile.py
+++ b/netbox/dcim/migrations/0203_moduletypeprofile.py
@@ -36,7 +36,7 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='moduletype',
- name='attributes',
+ name='attribute_data',
field=models.JSONField(blank=True, null=True),
),
migrations.AddField(
diff --git a/netbox/dcim/models/modules.py b/netbox/dcim/models/modules.py
index 40c4beb4a..6029eccd3 100644
--- a/netbox/dcim/models/modules.py
+++ b/netbox/dcim/models/modules.py
@@ -93,7 +93,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
blank=True,
null=True
)
- attributes = models.JSONField(
+ attribute_data = models.JSONField(
blank=True,
null=True,
verbose_name=_('attributes')
@@ -122,19 +122,32 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
def full_name(self):
return f"{self.manufacturer} {self.model}"
+ @property
+ def attributes(self):
+ """
+ Returns a human-friendly representation of the attributes defined for a ModuleType according to its profile.
+ """
+ if self.profile is None or not self.profile.schema:
+ return {}
+ attrs = {}
+ for name, options in self.profile.schema.get('properties', {}).items():
+ key = options.get('title', name)
+ attrs[key] = self.attribute_data.get(name)
+ return dict(sorted(attrs.items()))
+
def clean(self):
super().clean()
# Validate any attributes against the assigned profile's schema
if self.profile:
try:
- jsonschema.validate(self.attributes, schema=self.profile.schema)
+ jsonschema.validate(self.attribute_data, schema=self.profile.schema)
except JSONValidationError as e:
raise ValidationError({
'attributes': _("Invalid schema: {error}").format(error=e)
})
else:
- self.attributes = None
+ self.attribute_data = None
def to_yaml(self):
data = {
diff --git a/netbox/dcim/tables/modules.py b/netbox/dcim/tables/modules.py
index 4fc6d866c..78615ed71 100644
--- a/netbox/dcim/tables/modules.py
+++ b/netbox/dcim/tables/modules.py
@@ -1,7 +1,7 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
-from dcim.models import Module, ModuleType
+from dcim.models import Module, ModuleType, ModuleTypeProfile
from netbox.tables import NetBoxTable, columns
from .template_code import WEIGHT
@@ -13,15 +13,19 @@ __all__ = (
class ModuleTypeProfileTable(NetBoxTable):
+ name = tables.Column(
+ verbose_name=_('Name'),
+ linkify=True
+ )
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
tags = columns.TagColumn(
- url_name='dcim:moduletype_list'
+ url_name='dcim:moduletypeprofile_list'
)
class Meta(NetBoxTable.Meta):
- model = ModuleType
+ model = ModuleTypeProfile
fields = (
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
)
@@ -43,6 +47,12 @@ class ModuleTypeTable(NetBoxTable):
linkify=True,
verbose_name=_('Module Type')
)
+ weight = columns.TemplateColumn(
+ verbose_name=_('Weight'),
+ template_code=WEIGHT,
+ order_by=('_abs_weight', 'weight_unit')
+ )
+ attributes = columns.DictColumn()
instance_count = columns.LinkedCountColumn(
viewname='dcim:module_list',
url_params={'module_type_id': 'pk'},
@@ -54,17 +64,12 @@ class ModuleTypeTable(NetBoxTable):
tags = columns.TagColumn(
url_name='dcim:moduletype_list'
)
- weight = columns.TemplateColumn(
- verbose_name=_('Weight'),
- template_code=WEIGHT,
- order_by=('_abs_weight', 'weight_unit')
- )
class Meta(NetBoxTable.Meta):
model = ModuleType
fields = (
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
- 'comments', 'tags', 'created', 'last_updated',
+ 'attributes', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'model', 'profile', 'manufacturer', 'part_number',
diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py
index cf6e1f133..f0e55176d 100644
--- a/netbox/netbox/tables/columns.py
+++ b/netbox/netbox/tables/columns.py
@@ -35,6 +35,7 @@ __all__ = (
'ContentTypesColumn',
'CustomFieldColumn',
'CustomLinkColumn',
+ 'DictColumn',
'DistanceColumn',
'DurationColumn',
'LinkedCountColumn',
@@ -707,3 +708,14 @@ class DistanceColumn(TemplateColumn):
def __init__(self, template_code=template_code, order_by='_abs_distance', **kwargs):
super().__init__(template_code=template_code, order_by=order_by, **kwargs)
+
+
+class DictColumn(tables.Column):
+ """
+ Render a dictionary of data in a simple key: value format, one pair per line.
+ """
+ def render(self, value):
+ output = '
'.join([
+ f'{escape(k)}: {escape(v)}' for k, v in value.items()
+ ])
+ return mark_safe(output)
diff --git a/netbox/templates/dcim/moduletype.html b/netbox/templates/dcim/moduletype.html
index 2b1039abb..adc5e2a98 100644
--- a/netbox/templates/dcim/moduletype.html
+++ b/netbox/templates/dcim/moduletype.html
@@ -25,7 +25,7 @@
{% trans "Profile" %} |
- {{ object.profile|linkify }} |
+ {{ object.profile|linkify|placeholder }} |
{% trans "Manufacturer" %} |
@@ -64,6 +64,27 @@
{% plugin_left_page object %}
+
+
+ {% if not object.profile %}
+
+ {% trans "No profile assigned" %}
+
+ {% elif object.attributes %}
+
+ {% for k, v in object.attributes.items %}
+
+ {{ k }} |
+ {{ v|placeholder }} |
+
+ {% endfor %}
+
+ {% else %}
+
+ {% trans "None" %}
+
+ {% endif %}
+
{% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/image_attachments.html' %}
diff --git a/netbox/templates/dcim/moduletypeprofile.html b/netbox/templates/dcim/moduletypeprofile.html
index 7277fdc87..61d8daee8 100644
--- a/netbox/templates/dcim/moduletypeprofile.html
+++ b/netbox/templates/dcim/moduletypeprofile.html
@@ -28,8 +28,11 @@
-
-
{{ object.schema|json }}
+
+
{{ object.schema|json }}
{% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/custom_fields.html' %}