Просмотр исходного кода

move cooling attributes to rack type

Arthur 1 неделя назад
Родитель
Сommit
a69c4e9e74

+ 3 - 3
docs/models/dcim/rack.md

@@ -53,15 +53,15 @@ A unique, locally-administered label used to identify hardware resources.
 
 
 ### Cooling Capability
 ### Cooling Capability
 
 
-The rack's coolant capability: air-only, liquid-capable, or liquid-required.
+The rack's coolant capability: air-only, liquid-capable, or liquid-required. When the rack is assigned a [rack type](./racktype.md), this value is inherited from the rack type.
 
 
 ### Has RDHx
 ### Has RDHx
 
 
-Indicates whether the rack is equipped with a rear-door heat exchanger (RDHx).
+Indicates whether the rack is equipped with a rear-door heat exchanger (RDHx). This is a per-rack attribute and is not derived from the rack type.
 
 
 ### Cooling Capacity
 ### Cooling Capacity
 
 
-The rack's cooling capacity, expressed in kilowatts (kW).
+The rack's cooling capacity, expressed in kilowatts (kW). When the rack is assigned a [rack type](./racktype.md), this value is inherited from the rack type.
 
 
 !!! note
 !!! note
     Some additional fields pertaining to physical attributes such as height and weight can also be defined on each rack, but should generally be defined instead on the [rack type](./racktype.md).
     Some additional fields pertaining to physical attributes such as height and weight can also be defined on each rack, but should generally be defined instead on the [rack type](./racktype.md).

+ 8 - 0
docs/models/dcim/racktype.md

@@ -54,6 +54,14 @@ The numeric weight of the rack, including a unit designation (e.g. 10 kilograms
 
 
 The maximum total weight capacity for all installed devices, inclusive of the rack itself.
 The maximum total weight capacity for all installed devices, inclusive of the rack itself.
 
 
+### Cooling Capability
+
+The rack design's coolant capability: air-only, liquid-capable, or liquid-required. Racks of this type inherit this value.
+
+### Cooling Capacity
+
+The rack design's cooling capacity, expressed in kilowatts (kW). Racks of this type inherit this value.
+
 ### Descending Units
 ### Descending Units
 
 
 If selected, the rack's elevation will display unit 1 at the top of the rack. (Most racks use ascending numbering, with unit 1 assigned to the bottommost position.)
 If selected, the rack's elevation will display unit 1 at the top of the rack. (Most racks use ascending numbering, with unit 1 assigned to the bottommost position.)

+ 12 - 4
netbox/dcim/api/serializers_/racks.py

@@ -77,6 +77,12 @@ class RackBaseSerializer(PrimaryModelSerializer):
         required=False,
         required=False,
         allow_null=True
         allow_null=True
     )
     )
+    cooling_capability = ChoiceField(
+        choices=RackCoolingCapabilityChoices,
+        allow_blank=True,
+        required=False,
+        allow_null=True
+    )
 
 
 
 
 class RackTypeSerializer(RackBaseSerializer):
 class RackTypeSerializer(RackBaseSerializer):
@@ -88,8 +94,9 @@ class RackTypeSerializer(RackBaseSerializer):
         fields = [
         fields = [
             'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
             'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
             'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
             'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
-            'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'owner', 'comments',
-            'tags', 'custom_fields', 'created', 'last_updated', 'rack_count',
+            'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'cooling_capability',
+            'cooling_capacity', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
+            'rack_count',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'rack_count')
         brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'rack_count')
 
 
@@ -152,8 +159,9 @@ class RackSerializer(RackBaseSerializer):
             'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'group', 'tenant',
             'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'group', 'tenant',
             'status', 'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit',
             'status', 'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit',
             'weight', 'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
             'weight', 'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'airflow', 'description', 'owner', 'comments', 'tags', 'custom_fields',
-            'created', 'last_updated', 'device_count', 'powerfeed_count',
+            'outer_unit', 'mounting_depth', 'airflow', 'cooling_capability', 'has_rdhx', 'cooling_capacity',
+            'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
+            'powerfeed_count',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
         brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
 
 

+ 6 - 1
netbox/dcim/filtersets.py

@@ -360,12 +360,17 @@ class RackTypeFilterSet(PrimaryModelFilterSet):
         choices=RackWidthChoices,
         choices=RackWidthChoices,
         distinct=False,
         distinct=False,
     )
     )
+    cooling_capability = django_filters.MultipleChoiceFilter(
+        choices=RackCoolingCapabilityChoices,
+        distinct=False,
+    )
 
 
     class Meta:
     class Meta:
         model = RackType
         model = RackType
         fields = (
         fields = (
             'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
             'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'cooling_capacity',
+            'description',
 
 
             # Counters
             # Counters
             'rack_count',
             'rack_count',

+ 12 - 1
netbox/dcim/forms/bulk_edit.py

@@ -306,6 +306,16 @@ class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
         required=False,
         required=False,
         initial=''
         initial=''
     )
     )
+    cooling_capability = forms.ChoiceField(
+        label=_('Cooling capability'),
+        choices=add_blank_choice(RackCoolingCapabilityChoices),
+        required=False
+    )
+    cooling_capacity = forms.DecimalField(
+        label=_('Cooling capacity'),
+        min_value=0,
+        required=False
+    )
 
 
     model = RackType
     model = RackType
     fieldsets = (
     fieldsets = (
@@ -317,10 +327,11 @@ class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
             name=_('Dimensions')
             name=_('Dimensions')
         ),
         ),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
+        FieldSet('cooling_capability', 'cooling_capacity', name=_('Cooling')),
     )
     )
     nullable_fields = (
     nullable_fields = (
         'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
         'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'weight',
-        'max_weight', 'weight_unit', 'description', 'comments',
+        'max_weight', 'weight_unit', 'cooling_capability', 'cooling_capacity', 'description', 'comments',
     )
     )
 
 
 
 

+ 7 - 1
netbox/dcim/forms/bulk_import.py

@@ -242,13 +242,19 @@ class RackTypeImportForm(PrimaryModelImportForm):
         required=False,
         required=False,
         help_text=_('Unit for rack weights')
         help_text=_('Unit for rack weights')
     )
     )
+    cooling_capability = CSVChoiceField(
+        label=_('Cooling capability'),
+        choices=RackCoolingCapabilityChoices,
+        required=False,
+        help_text=_('Cooling capability')
+    )
 
 
     class Meta:
     class Meta:
         model = RackType
         model = RackType
         fields = (
         fields = (
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
             'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
-            'weight_unit', 'description', 'owner', 'comments', 'tags',
+            'weight_unit', 'cooling_capability', 'cooling_capacity', 'description', 'owner', 'comments', 'tags',
         )
         )
 
 
     def __init__(self, data=None, *args, **kwargs):
     def __init__(self, data=None, *args, **kwargs):

+ 6 - 5
netbox/dcim/forms/filtersets.py

@@ -343,6 +343,11 @@ class RackBaseFilterForm(PrimaryModelFilterSetForm):
         choices=add_blank_choice(WeightUnitChoices),
         choices=add_blank_choice(WeightUnitChoices),
         required=False
         required=False
     )
     )
+    cooling_capability = forms.MultipleChoiceField(
+        label=_('Cooling capability'),
+        choices=RackCoolingCapabilityChoices,
+        required=False
+    )
 
 
 
 
 class RackTypeFilterForm(RackBaseFilterForm):
 class RackTypeFilterForm(RackBaseFilterForm):
@@ -352,6 +357,7 @@ class RackTypeFilterForm(RackBaseFilterForm):
         FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', 'rack_count', name=_('Rack Type')),
         FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', 'rack_count', name=_('Rack Type')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
         FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
+        FieldSet('cooling_capability', name=_('Cooling')),
         FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
         FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
     )
     )
     selector_fields = ('filter_id', 'q', 'manufacturer_id')
     selector_fields = ('filter_id', 'q', 'manufacturer_id')
@@ -445,11 +451,6 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, RackBaseFilterFo
         choices=add_blank_choice(RackAirflowChoices),
         choices=add_blank_choice(RackAirflowChoices),
         required=False
         required=False
     )
     )
-    cooling_capability = forms.MultipleChoiceField(
-        label=_('Cooling capability'),
-        choices=RackCoolingCapabilityChoices,
-        required=False
-    )
     has_rdhx = forms.NullBooleanField(
     has_rdhx = forms.NullBooleanField(
         required=False,
         required=False,
         label=_('Has RDHx'),
         label=_('Has RDHx'),

+ 2 - 1
netbox/dcim/forms/model_forms.py

@@ -284,6 +284,7 @@ class RackTypeForm(PrimaryModelForm):
             'mounting_depth', name=_('Dimensions')
             'mounting_depth', name=_('Dimensions')
         ),
         ),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
         FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
+        FieldSet('cooling_capability', 'cooling_capacity', name=_('Cooling')),
     )
     )
 
 
     class Meta:
     class Meta:
@@ -291,7 +292,7 @@ class RackTypeForm(PrimaryModelForm):
         fields = [
         fields = [
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
             'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
             'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
-            'weight_unit', 'description', 'owner', 'comments', 'tags',
+            'weight_unit', 'cooling_capability', 'cooling_capacity', 'description', 'owner', 'comments', 'tags',
         ]
         ]
 
 
 
 

+ 13 - 1
netbox/dcim/migrations/0241_device_cooling_method_device_cooling_outlet_count_and_more.py

@@ -1,4 +1,4 @@
-# Generated by Django 6.0.6 on 2026-06-23 17:04
+# Generated by Django 6.0.6 on 2026-06-23 18:45
 
 
 import django.contrib.postgres.fields
 import django.contrib.postgres.fields
 import django.core.validators
 import django.core.validators
@@ -109,6 +109,18 @@ class Migration(migrations.Migration):
             name="has_rdhx",
             name="has_rdhx",
             field=models.BooleanField(default=False),
             field=models.BooleanField(default=False),
         ),
         ),
+        migrations.AddField(
+            model_name="racktype",
+            name="cooling_capability",
+            field=models.CharField(blank=True, max_length=50, null=True),
+        ),
+        migrations.AddField(
+            model_name="racktype",
+            name="cooling_capacity",
+            field=models.DecimalField(
+                blank=True, decimal_places=2, max_digits=8, null=True
+            ),
+        ),
         migrations.CreateModel(
         migrations.CreateModel(
             name="CoolingPort",
             name="CoolingPort",
             fields=[
             fields=[

+ 21 - 17
netbox/dcim/models/racks.py

@@ -135,6 +135,23 @@ class RackBase(WeightMixin, PrimaryModel):
         null=True
         null=True
     )
     )
 
 
+    # Cooling
+    cooling_capability = models.CharField(
+        verbose_name=_('cooling capability'),
+        max_length=50,
+        choices=RackCoolingCapabilityChoices,
+        blank=True,
+        null=True
+    )
+    cooling_capacity = models.DecimalField(
+        verbose_name=_('cooling capacity'),
+        max_digits=8,
+        decimal_places=2,
+        blank=True,
+        null=True,
+        help_text=_('Cooling capacity (kW)')
+    )
+
     class Meta:
     class Meta:
         abstract = True
         abstract = True
 
 
@@ -174,7 +191,8 @@ class RackType(ImageAttachmentsMixin, RackBase):
 
 
     clone_fields = (
     clone_fields = (
         'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
         'manufacturer', 'form_factor', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
-        'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit',
+        'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'cooling_capability',
+        'cooling_capacity',
     )
     )
     prerequisite_models = (
     prerequisite_models = (
         'dcim.Manufacturer',
         'dcim.Manufacturer',
@@ -269,7 +287,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, TrackingModelMixin, RackBase):
     # Fields which cannot be set locally if a RackType is assigned
     # Fields which cannot be set locally if a RackType is assigned
     RACKTYPE_FIELDS = (
     RACKTYPE_FIELDS = (
         'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
         'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height',
-        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight',
+        'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'max_weight', 'cooling_capability',
+        'cooling_capacity',
     )
     )
 
 
     form_factor = models.CharField(
     form_factor = models.CharField(
@@ -359,26 +378,11 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, TrackingModelMixin, RackBase):
         blank=True,
         blank=True,
         null=True
         null=True
     )
     )
-    cooling_capability = models.CharField(
-        verbose_name=_('cooling capability'),
-        max_length=50,
-        choices=RackCoolingCapabilityChoices,
-        blank=True,
-        null=True
-    )
     has_rdhx = models.BooleanField(
     has_rdhx = models.BooleanField(
         verbose_name=_('has RDHx'),
         verbose_name=_('has RDHx'),
         default=False,
         default=False,
         help_text=_('Rack is equipped with a rear-door heat exchanger')
         help_text=_('Rack is equipped with a rear-door heat exchanger')
     )
     )
-    cooling_capacity = models.DecimalField(
-        verbose_name=_('cooling capacity'),
-        max_digits=8,
-        decimal_places=2,
-        blank=True,
-        null=True,
-        help_text=_('Cooling capacity (kW)')
-    )
 
 
     # Generic relations
     # Generic relations
     vlan_groups = GenericRelation(
     vlan_groups = GenericRelation(

+ 8 - 2
netbox/dcim/tables/racks.py

@@ -106,6 +106,12 @@ class RackTypeTable(PrimaryModelTable):
         url_params={'rack_type_id': 'pk'},
         url_params={'rack_type_id': 'pk'},
         verbose_name=_('Rack Count'),
         verbose_name=_('Rack Count'),
     )
     )
+    cooling_capability = columns.ChoiceFieldColumn(
+        verbose_name=_('Cooling Capability'),
+    )
+    cooling_capacity = tables.Column(
+        verbose_name=_('Cooling Capacity (kW)')
+    )
     tags = columns.TagColumn(
     tags = columns.TagColumn(
         url_name='dcim:rack_list'
         url_name='dcim:rack_list'
     )
     )
@@ -114,8 +120,8 @@ class RackTypeTable(PrimaryModelTable):
         model = RackType
         model = RackType
         fields = (
         fields = (
             'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
             'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
-            'outer_height', 'outer_depth', 'mounting_depth', 'weight', 'max_weight', 'description', 'comments',
-            'rack_count', 'tags', 'created', 'last_updated',
+            'outer_height', 'outer_depth', 'mounting_depth', 'weight', 'max_weight', 'cooling_capability',
+            'cooling_capacity', 'description', 'comments', 'rack_count', 'tags', 'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'rack_count',
             'pk', 'model', 'manufacturer', 'type', 'u_height', 'description', 'rack_count',

+ 5 - 0
netbox/dcim/tests/test_api.py

@@ -648,6 +648,8 @@ class RackTypeTestCase(APIViewTestCases.APIViewTestCase):
     brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'rack_count', 'slug', 'url']
     brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'rack_count', 'slug', 'url']
     bulk_update_data = {
     bulk_update_data = {
         'description': 'new description',
         'description': 'new description',
+        'cooling_capability': RackCoolingCapabilityChoices.LIQUID_CAPABLE,
+        'cooling_capacity': 50,
     }
     }
     user_permissions = ('dcim.view_manufacturer',)
     user_permissions = ('dcim.view_manufacturer',)
 
 
@@ -687,12 +689,15 @@ class RackTypeTestCase(APIViewTestCases.APIViewTestCase):
                 'model': 'Rack Type 4',
                 'model': 'Rack Type 4',
                 'slug': 'rack-type-4',
                 'slug': 'rack-type-4',
                 'form_factor': RackFormFactorChoices.TYPE_CABINET,
                 'form_factor': RackFormFactorChoices.TYPE_CABINET,
+                'cooling_capability': RackCoolingCapabilityChoices.LIQUID_REQUIRED,
+                'cooling_capacity': 80,
             },
             },
             {
             {
                 'manufacturer': manufacturers[1].pk,
                 'manufacturer': manufacturers[1].pk,
                 'model': 'Rack Type 5',
                 'model': 'Rack Type 5',
                 'slug': 'rack-type-5',
                 'slug': 'rack-type-5',
                 'form_factor': RackFormFactorChoices.TYPE_CABINET,
                 'form_factor': RackFormFactorChoices.TYPE_CABINET,
+                'cooling_capability': RackCoolingCapabilityChoices.LIQUID_CAPABLE,
             },
             },
             {
             {
                 'manufacturer': manufacturers[1].pk,
                 'manufacturer': manufacturers[1].pk,

+ 8 - 1
netbox/dcim/tests/test_models.py

@@ -201,6 +201,8 @@ class RackTypeTestCase(TestCase):
             weight_unit=WeightUnitChoices.UNIT_POUND,
             weight_unit=WeightUnitChoices.UNIT_POUND,
             max_weight=7777,
             max_weight=7777,
             mounting_depth=8,
             mounting_depth=8,
+            cooling_capability=RackCoolingCapabilityChoices.LIQUID_REQUIRED,
+            cooling_capacity=80,
         )
         )
 
 
     def test_rack_creation(self):
     def test_rack_creation(self):
@@ -220,7 +222,8 @@ class RackTypeTestCase(TestCase):
             facility_id='A101',
             facility_id='A101',
             site=sites[0],
             site=sites[0],
             location=locations[0],
             location=locations[0],
-            rack_type=rack_type
+            rack_type=rack_type,
+            has_rdhx=True,
         )
         )
         self.assertEqual(rack.width, rack_type.width)
         self.assertEqual(rack.width, rack_type.width)
         self.assertEqual(rack.u_height, rack_type.u_height)
         self.assertEqual(rack.u_height, rack_type.u_height)
@@ -233,6 +236,10 @@ class RackTypeTestCase(TestCase):
         self.assertEqual(rack.weight_unit, rack_type.weight_unit)
         self.assertEqual(rack.weight_unit, rack_type.weight_unit)
         self.assertEqual(rack.max_weight, rack_type.max_weight)
         self.assertEqual(rack.max_weight, rack_type.max_weight)
         self.assertEqual(rack.mounting_depth, rack_type.mounting_depth)
         self.assertEqual(rack.mounting_depth, rack_type.mounting_depth)
+        # Cooling capability/capacity are inherited from the rack type; has_rdhx is rack-specific
+        self.assertEqual(rack.cooling_capability, rack_type.cooling_capability)
+        self.assertEqual(rack.cooling_capacity, rack_type.cooling_capacity)
+        self.assertTrue(rack.has_rdhx)
 
 
 
 
 class RackTestCase(TestCase):
 class RackTestCase(TestCase):

+ 2 - 0
netbox/dcim/ui/panels.py

@@ -85,6 +85,8 @@ class RackTypePanel(panels.ObjectAttributesPanel):
     manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
     manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
     model = attrs.TextAttr('model')
     model = attrs.TextAttr('model')
     description = attrs.TextAttr('description')
     description = attrs.TextAttr('description')
+    cooling_capability = attrs.ChoiceAttr('cooling_capability')
+    cooling_capacity = attrs.TextAttr('cooling_capacity', format_string=_('{} kW'))
 
 
 
 
 class DevicePanel(panels.ObjectAttributesPanel):
 class DevicePanel(panels.ObjectAttributesPanel):