mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Add attributes property on ModuleType
This commit is contained in:
parent
69e67d0258
commit
3cda074cbd
@ -436,7 +436,7 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('profile', 'manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
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')),
|
FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -444,7 +444,7 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = [
|
fields = [
|
||||||
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
|
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
|
||||||
'attributes', 'comments', 'tags',
|
'attribute_data', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='moduletype',
|
model_name='moduletype',
|
||||||
name='attributes',
|
name='attribute_data',
|
||||||
field=models.JSONField(blank=True, null=True),
|
field=models.JSONField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
@ -93,7 +93,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
attributes = models.JSONField(
|
attribute_data = models.JSONField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name=_('attributes')
|
verbose_name=_('attributes')
|
||||||
@ -122,19 +122,32 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
def full_name(self):
|
def full_name(self):
|
||||||
return f"{self.manufacturer} {self.model}"
|
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):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate any attributes against the assigned profile's schema
|
# Validate any attributes against the assigned profile's schema
|
||||||
if self.profile:
|
if self.profile:
|
||||||
try:
|
try:
|
||||||
jsonschema.validate(self.attributes, schema=self.profile.schema)
|
jsonschema.validate(self.attribute_data, schema=self.profile.schema)
|
||||||
except JSONValidationError as e:
|
except JSONValidationError as e:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'attributes': _("Invalid schema: {error}").format(error=e)
|
'attributes': _("Invalid schema: {error}").format(error=e)
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
self.attributes = None
|
self.attribute_data = None
|
||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
data = {
|
data = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import django_tables2 as tables
|
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 netbox.tables import NetBoxTable, columns
|
||||||
from .template_code import WEIGHT
|
from .template_code import WEIGHT
|
||||||
|
|
||||||
@ -13,15 +13,19 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class ModuleTypeProfileTable(NetBoxTable):
|
class ModuleTypeProfileTable(NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
comments = columns.MarkdownColumn(
|
comments = columns.MarkdownColumn(
|
||||||
verbose_name=_('Comments'),
|
verbose_name=_('Comments'),
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:moduletype_list'
|
url_name='dcim:moduletypeprofile_list'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ModuleType
|
model = ModuleTypeProfile
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
|
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
@ -43,6 +47,12 @@ class ModuleTypeTable(NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Module Type')
|
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(
|
instance_count = columns.LinkedCountColumn(
|
||||||
viewname='dcim:module_list',
|
viewname='dcim:module_list',
|
||||||
url_params={'module_type_id': 'pk'},
|
url_params={'module_type_id': 'pk'},
|
||||||
@ -54,17 +64,12 @@ class ModuleTypeTable(NetBoxTable):
|
|||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='dcim:moduletype_list'
|
url_name='dcim:moduletype_list'
|
||||||
)
|
)
|
||||||
weight = columns.TemplateColumn(
|
|
||||||
verbose_name=_('Weight'),
|
|
||||||
template_code=WEIGHT,
|
|
||||||
order_by=('_abs_weight', 'weight_unit')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
|
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
|
||||||
'comments', 'tags', 'created', 'last_updated',
|
'attributes', 'comments', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'model', 'profile', 'manufacturer', 'part_number',
|
'pk', 'model', 'profile', 'manufacturer', 'part_number',
|
||||||
|
@ -35,6 +35,7 @@ __all__ = (
|
|||||||
'ContentTypesColumn',
|
'ContentTypesColumn',
|
||||||
'CustomFieldColumn',
|
'CustomFieldColumn',
|
||||||
'CustomLinkColumn',
|
'CustomLinkColumn',
|
||||||
|
'DictColumn',
|
||||||
'DistanceColumn',
|
'DistanceColumn',
|
||||||
'DurationColumn',
|
'DurationColumn',
|
||||||
'LinkedCountColumn',
|
'LinkedCountColumn',
|
||||||
@ -707,3 +708,14 @@ class DistanceColumn(TemplateColumn):
|
|||||||
|
|
||||||
def __init__(self, template_code=template_code, order_by='_abs_distance', **kwargs):
|
def __init__(self, template_code=template_code, order_by='_abs_distance', **kwargs):
|
||||||
super().__init__(template_code=template_code, order_by=order_by, **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 = '<br />'.join([
|
||||||
|
f'{escape(k)}: {escape(v)}' for k, v in value.items()
|
||||||
|
])
|
||||||
|
return mark_safe(output)
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Profile" %}</th>
|
<th scope="row">{% trans "Profile" %}</th>
|
||||||
<td>{{ object.profile|linkify }}</td>
|
<td>{{ object.profile|linkify|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Manufacturer" %}</th>
|
<th scope="row">{% trans "Manufacturer" %}</th>
|
||||||
@ -64,6 +64,27 @@
|
|||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Attributes" %}</h2>
|
||||||
|
{% if not object.profile %}
|
||||||
|
<div class="card-body text-muted">
|
||||||
|
{% trans "No profile assigned" %}
|
||||||
|
</div>
|
||||||
|
{% elif object.attributes %}
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
{% for k, v in object.attributes.items %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ k }}</th>
|
||||||
|
<td>{{ v|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div class="card-body text-muted">
|
||||||
|
{% trans "None" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/image_attachments.html' %}
|
{% include 'inc/panels/image_attachments.html' %}
|
||||||
|
@ -28,8 +28,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Schema" %}</h2>
|
<h2 class="card-header d-flex justify-content-between">
|
||||||
<pre>{{ object.schema|json }}</pre>
|
{% trans "Schema" %}
|
||||||
|
{% copy_content 'profile_schema' %}
|
||||||
|
</h2>
|
||||||
|
<pre id="profile_schema">{{ object.schema|json }}</pre>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
Loading…
Reference in New Issue
Block a user