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

Closes #10545: Standardize description & comment fields on primary models (#10834)

* Standardize description & comments fields on primary models

* Update REST API serializers

* Update forms

* Update tables

* Update templates
Jeremy Stretch 3 лет назад
Родитель
Сommit
bc6b5bc4be
100 измененных файлов с 969 добавлено и 513 удалено
  1. 51 0
      docs/release-notes/version-3.4.md
  2. 2 2
      netbox/circuits/api/serializers.py
  3. 5 1
      netbox/circuits/forms/bulk_edit.py
  4. 1 1
      netbox/circuits/forms/bulk_import.py
  5. 4 5
      netbox/circuits/forms/model_forms.py
  6. 18 0
      netbox/circuits/migrations/0041_standardize_description_comments.py
  7. 2 9
      netbox/circuits/models/circuits.py
  8. 3 14
      netbox/circuits/models/providers.py
  9. 2 2
      netbox/circuits/tables/providers.py
  10. 20 21
      netbox/dcim/api/serializers.py
  11. 102 29
      netbox/dcim/forms/bulk_edit.py
  12. 9 8
      netbox/dcim/forms/bulk_import.py
  13. 22 18
      netbox/dcim/forms/model_forms.py
  14. 2 2
      netbox/dcim/forms/object_import.py
  15. 78 0
      netbox/dcim/migrations/0165_standardize_description_comments.py
  16. 3 3
      netbox/dcim/models/cables.py
  17. 6 18
      netbox/dcim/models/devices.py
  18. 3 7
      netbox/dcim/models/power.py
  19. 3 6
      netbox/dcim/models/racks.py
  20. 2 9
      netbox/dcim/models/sites.py
  21. 2 1
      netbox/dcim/tables/cables.py
  22. 33 45
      netbox/dcim/tables/devices.py
  23. 14 30
      netbox/dcim/tables/devicetypes.py
  24. 3 3
      netbox/dcim/tables/modules.py
  25. 4 2
      netbox/dcim/tables/power.py
  26. 5 4
      netbox/dcim/tables/racks.py
  27. 21 18
      netbox/ipam/api/serializers.py
  28. 67 23
      netbox/ipam/forms/bulk_edit.py
  29. 12 12
      netbox/ipam/forms/bulk_import.py
  30. 33 17
      netbox/ipam/forms/model_forms.py
  31. 73 0
      netbox/ipam/migrations/0063_standardize_description_comments.py
  32. 2 6
      netbox/ipam/models/fhrp.py
  33. 6 26
      netbox/ipam/models/ip.py
  34. 2 6
      netbox/ipam/models/l2vpn.py
  35. 3 7
      netbox/ipam/models/services.py
  36. 2 12
      netbox/ipam/models/vlans.py
  37. 3 11
      netbox/ipam/models/vrfs.py
  38. 2 2
      netbox/ipam/tables/fhrp.py
  39. 16 10
      netbox/ipam/tables/ip.py
  40. 6 2
      netbox/ipam/tables/l2vpn.py
  41. 7 3
      netbox/ipam/tables/services.py
  42. 2 1
      netbox/ipam/tables/vlans.py
  43. 7 3
      netbox/ipam/tables/vrfs.py
  44. 19 2
      netbox/netbox/models/__init__.py
  45. 4 0
      netbox/templates/circuits/provider.html
  46. 5 0
      netbox/templates/dcim/cable.html
  47. 15 8
      netbox/templates/dcim/cable_edit.html
  48. 5 1
      netbox/templates/dcim/device.html
  49. 1 0
      netbox/templates/dcim/device_edit.html
  50. 4 0
      netbox/templates/dcim/devicetype.html
  51. 4 0
      netbox/templates/dcim/module.html
  52. 4 0
      netbox/templates/dcim/moduletype.html
  53. 4 0
      netbox/templates/dcim/powerfeed.html
  54. 22 19
      netbox/templates/dcim/powerpanel.html
  55. 4 0
      netbox/templates/dcim/rack.html
  56. 1 0
      netbox/templates/dcim/rack_edit.html
  57. 1 0
      netbox/templates/dcim/rackreservation.html
  58. 6 1
      netbox/templates/dcim/virtualchassis.html
  59. 7 1
      netbox/templates/dcim/virtualchassis_edit.html
  60. 1 0
      netbox/templates/ipam/aggregate.html
  61. 1 0
      netbox/templates/ipam/asn.html
  62. 1 0
      netbox/templates/ipam/fhrpgroup.html
  63. 9 2
      netbox/templates/ipam/fhrpgroup_edit.html
  64. 1 0
      netbox/templates/ipam/ipaddress.html
  65. 7 0
      netbox/templates/ipam/ipaddress_edit.html
  66. 4 3
      netbox/templates/ipam/iprange.html
  67. 1 0
      netbox/templates/ipam/l2vpn.html
  68. 1 0
      netbox/templates/ipam/prefix.html
  69. 1 0
      netbox/templates/ipam/routetarget.html
  70. 4 3
      netbox/templates/ipam/service.html
  71. 7 0
      netbox/templates/ipam/service_create.html
  72. 7 0
      netbox/templates/ipam/service_edit.html
  73. 7 6
      netbox/templates/ipam/servicetemplate.html
  74. 4 3
      netbox/templates/ipam/vlan.html
  75. 7 0
      netbox/templates/ipam/vlan_edit.html
  76. 1 0
      netbox/templates/ipam/vrf.html
  77. 4 0
      netbox/templates/tenancy/contact.html
  78. 4 0
      netbox/templates/virtualization/cluster.html
  79. 4 0
      netbox/templates/virtualization/virtualmachine.html
  80. 1 0
      netbox/templates/wireless/wirelesslan.html
  81. 1 0
      netbox/templates/wireless/wirelesslink.html
  82. 6 0
      netbox/templates/wireless/wirelesslink_edit.html
  83. 2 2
      netbox/tenancy/api/serializers.py
  84. 11 3
      netbox/tenancy/forms/bulk_edit.py
  85. 1 1
      netbox/tenancy/forms/bulk_import.py
  86. 2 2
      netbox/tenancy/forms/model_forms.py
  87. 18 0
      netbox/tenancy/migrations/0009_standardize_description_comments.py
  88. 2 6
      netbox/tenancy/models/contacts.py
  89. 2 10
      netbox/tenancy/models/tenants.py
  90. 2 2
      netbox/tenancy/tables/contacts.py
  91. 4 4
      netbox/virtualization/api/serializers.py
  92. 13 5
      netbox/virtualization/forms/bulk_edit.py
  93. 2 2
      netbox/virtualization/forms/bulk_import.py
  94. 6 4
      netbox/virtualization/forms/model_forms.py
  95. 23 0
      netbox/virtualization/migrations/0034_standardize_description_comments.py
  96. 3 9
      netbox/virtualization/models.py
  97. 2 2
      netbox/virtualization/tables/clusters.py
  98. 2 2
      netbox/virtualization/tables/virtualmachines.py
  99. 2 2
      netbox/wireless/api/serializers.py
  100. 19 9
      netbox/wireless/forms/bulk_edit.py

+ 51 - 0
docs/release-notes/version-3.4.md

@@ -69,18 +69,69 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
 
 * circuits.provider
     * Removed the `asn`, `noc_contact`, `admin_contact`, and `portal_url` fields
+    * Added a `description` field
+* dcim.Cable
+    * Added `description` and `comments` fields
+* dcim.Device
+    * Added a `description` field
 * dcim.DeviceType
+    * Added a `description` field
     * Added optional `weight` and `weight_unit` fields
+* dcim.Module
+    * Added a `description` field
 * dcim.ModuleType
+    * Added a `description` field
     * Added optional `weight` and `weight_unit` fields
+* dcim.PowerFeed
+    * Added a `description` field
+* dcim.PowerPanel
+    * Added `description` and `comments` fields
 * dcim.Rack
+    * Added a `description` field
     * Added optional `weight` and `weight_unit` fields
+* dcim.RackReservation
+    * Added a `comments` field
+* dcim.VirtualChassis
+    * Added `description` and `comments` fields
 * extras.CustomLink
     * Renamed `content_type` field to `content_types`
 * extras.ExportTemplate
     * Renamed `content_type` field to `content_types`
+* ipam.Aggregate
+    * Added a `comments` field
+* ipam.ASN
+    * Added a `comments` field
 * ipam.FHRPGroup
+    * Added a `comments` field
     * Added optional `name` field
+* ipam.IPAddress
+    * Added a `comments` field
+* ipam.IPRange
+    * Added a `comments` field
+* ipam.L2VPN
+    * Added a `comments` field
+* ipam.Prefix
+    * Added a `comments` field
+* ipam.RouteTarget
+    * Added a `comments` field
+* ipam.Service
+    * Added a `comments` field
+* ipam.ServiceTemplate
+    * Added a `comments` field
+* ipam.VLAN
+    * Added a `comments` field
+* ipam.VRF
+    * Added a `comments` field
+* tenancy.Contact
+    * Added a `description` field
+* virtualization.Cluster
+    * Added a `description` field
+* virtualization.VirtualMachine
+    * Added a `description` field
+* wireless.WirelessLAN
+    * Added a `comments` field
+* wireless.WirelessLink
+    * Added a `comments` field
 
 ### GraphQL API Changes
 

+ 2 - 2
netbox/circuits/api/serializers.py

@@ -31,8 +31,8 @@ class ProviderSerializer(NetBoxModelSerializer):
     class Meta:
         model = Provider
         fields = [
-            'id', 'url', 'display', 'name', 'slug', 'account',
-            'comments', 'asns', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
+            'id', 'url', 'display', 'name', 'slug', 'account', 'description', 'comments', 'asns', 'tags',
+            'custom_fields', 'created', 'last_updated', 'circuit_count',
         ]
 
 

+ 5 - 1
netbox/circuits/forms/bulk_edit.py

@@ -30,6 +30,10 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Account number'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
     comments = CommentField(
         widget=SmallTextarea,
         label='Comments'
@@ -40,7 +44,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
         (None, ('asns', 'account', )),
     )
     nullable_fields = (
-        'asns', 'account', 'comments',
+        'asns', 'account', 'description', 'comments',
     )
 
 

+ 1 - 1
netbox/circuits/forms/bulk_import.py

@@ -18,7 +18,7 @@ class ProviderCSVForm(NetBoxModelCSVForm):
     class Meta:
         model = Provider
         fields = (
-            'name', 'slug', 'account', 'comments',
+            'name', 'slug', 'account', 'description', 'comments',
         )
 
 

+ 4 - 5
netbox/circuits/forms/model_forms.py

@@ -1,4 +1,3 @@
-from django import forms
 from django.utils.translation import gettext as _
 
 from circuits.models import *
@@ -7,8 +6,8 @@ from ipam.models import ASN
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.forms import (
-    BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
-    SelectSpeedWidget, SmallTextarea, SlugField, StaticSelect,
+    CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SlugField,
+    StaticSelect,
 )
 
 __all__ = (
@@ -30,14 +29,14 @@ class ProviderForm(NetBoxModelForm):
     comments = CommentField()
 
     fieldsets = (
-        ('Provider', ('name', 'slug', 'asns', 'tags')),
+        ('Provider', ('name', 'slug', 'asns', 'description', 'tags')),
         ('Support Info', ('account',)),
     )
 
     class Meta:
         model = Provider
         fields = [
-            'name', 'slug', 'account', 'asns', 'comments', 'tags',
+            'name', 'slug', 'account', 'asns', 'description', 'comments', 'tags',
         ]
         help_texts = {
             'name': "Full name of the provider",

+ 18 - 0
netbox/circuits/migrations/0041_standardize_description_comments.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2022-11-03 18:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('circuits', '0040_provider_remove_deprecated_fields'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='provider',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+    ]

+ 2 - 9
netbox/circuits/models/circuits.py

@@ -7,7 +7,7 @@ from django.urls import reverse
 from circuits.choices import *
 from dcim.models import CabledObjectModel
 from netbox.models import (
-    ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, NetBoxModel, TagsMixin,
+    ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, PrimaryModel, TagsMixin,
 )
 from netbox.models.features import WebhooksMixin
 
@@ -27,7 +27,7 @@ class CircuitType(OrganizationalModel):
         return reverse('circuits:circuittype', args=[self.pk])
 
 
-class Circuit(NetBoxModel):
+class Circuit(PrimaryModel):
     """
     A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
     circuits. Each circuit is also assigned a CircuitType and a Site.  Circuit port speed and commit rate are measured
@@ -73,13 +73,6 @@ class Circuit(NetBoxModel):
         blank=True,
         null=True,
         verbose_name='Commit rate (Kbps)')
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     contacts = GenericRelation(

+ 3 - 14
netbox/circuits/models/providers.py

@@ -2,8 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
 from django.db import models
 from django.urls import reverse
 
-from dcim.fields import ASNField
-from netbox.models import NetBoxModel
+from netbox.models import PrimaryModel
 
 __all__ = (
     'ProviderNetwork',
@@ -11,7 +10,7 @@ __all__ = (
 )
 
 
-class Provider(NetBoxModel):
+class Provider(PrimaryModel):
     """
     Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
     stores information pertinent to the user's relationship with the Provider.
@@ -34,9 +33,6 @@ class Provider(NetBoxModel):
         blank=True,
         verbose_name='Account number'
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     contacts = GenericRelation(
@@ -57,7 +53,7 @@ class Provider(NetBoxModel):
         return reverse('circuits:provider', args=[self.pk])
 
 
-class ProviderNetwork(NetBoxModel):
+class ProviderNetwork(PrimaryModel):
     """
     This represents a provider network which exists outside of NetBox, the details of which are unknown or
     unimportant to the user.
@@ -75,13 +71,6 @@ class ProviderNetwork(NetBoxModel):
         blank=True,
         verbose_name='Service ID'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
-    comments = models.TextField(
-        blank=True
-    )
 
     class Meta:
         ordering = ('provider', 'name')

+ 2 - 2
netbox/circuits/tables/providers.py

@@ -39,8 +39,8 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Provider
         fields = (
-            'pk', 'id', 'name', 'asns', 'account', 'asn_count',
-            'circuit_count', 'comments', 'contacts', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'name', 'asns', 'account', 'asn_count', 'circuit_count', 'description', 'comments', 'contacts',
+            'tags', 'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'account', 'circuit_count')
 

+ 20 - 21
netbox/dcim/api/serializers.py

@@ -210,8 +210,8 @@ class RackSerializer(NetBoxModelSerializer):
         fields = [
             'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
             'asset_tag', 'type', 'width', 'u_height', 'weight', 'weight_unit', 'desc_units', 'outer_width',
-            'outer_depth', 'outer_unit', 'mounting_depth', 'comments', 'tags', 'custom_fields', 'created',
-            'last_updated', 'device_count', 'powerfeed_count',
+            'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated', 'device_count', 'powerfeed_count',
         ]
 
 
@@ -243,8 +243,8 @@ class RackReservationSerializer(NetBoxModelSerializer):
     class Meta:
         model = RackReservation
         fields = [
-            'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description', 'tags',
-            'custom_fields',
+            'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description',
+            'comments', 'tags', 'custom_fields',
         ]
 
 
@@ -324,8 +324,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
         model = DeviceType
         fields = [
             'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
-            'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags',
-            'custom_fields', 'created', 'last_updated', 'device_count',
+            'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
+            'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
         ]
 
 
@@ -333,13 +333,12 @@ class ModuleTypeSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
     manufacturer = NestedManufacturerSerializer()
     weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False)
-    # module_count = serializers.IntegerField(read_only=True)
 
     class Meta:
         model = ModuleType
         fields = [
-            'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description',
+            'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 
@@ -656,8 +655,8 @@ class DeviceSerializer(NetBoxModelSerializer):
         fields = [
             'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
             'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
-            'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments',
-            'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
+            'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
+            'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
     @swagger_serializer_method(serializer_or_field=NestedDeviceSerializer)
@@ -681,8 +680,8 @@ class ModuleSerializer(NetBoxModelSerializer):
     class Meta:
         model = Module
         fields = [
-            'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description',
+            'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 
@@ -1020,7 +1019,7 @@ class CableSerializer(NetBoxModelSerializer):
         model = Cable
         fields = [
             'id', 'url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'tenant', 'label', 'color',
-            'length', 'length_unit', 'tags', 'custom_fields', 'created', 'last_updated',
+            'length', 'length_unit', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 
@@ -1086,8 +1085,8 @@ class VirtualChassisSerializer(NetBoxModelSerializer):
     class Meta:
         model = VirtualChassis
         fields = [
-            'id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count',
-            'created', 'last_updated',
+            'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
+            'member_count', 'created', 'last_updated',
         ]
 
 
@@ -1108,8 +1107,8 @@ class PowerPanelSerializer(NetBoxModelSerializer):
     class Meta:
         model = PowerPanel
         fields = [
-            'id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count',
-            'created', 'last_updated',
+            'id', 'url', 'display', 'site', 'location', 'name', 'description', 'comments', 'tags', 'custom_fields',
+            'powerfeed_count', 'created', 'last_updated',
         ]
 
 
@@ -1142,7 +1141,7 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
         model = PowerFeed
         fields = [
             'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
-            'amperage', 'max_utilization', 'comments', 'mark_connected', 'cable', 'cable_end', 'link_peers',
-            'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
-            'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
+            'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
+            'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'description',
+            'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
         ]

+ 102 - 29
netbox/dcim/forms/bulk_edit.py

@@ -127,22 +127,26 @@ class SiteBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Contact E-mail'
     )
-    description = forms.CharField(
-        max_length=100,
-        required=False
-    )
     time_zone = TimeZoneFormField(
         choices=add_blank_choice(TimeZoneFormField().choices),
         required=False,
         widget=StaticSelect()
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Site
     fieldsets = (
         (None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
     )
     nullable_fields = (
-        'region', 'group', 'tenant', 'asns', 'description', 'time_zone',
+        'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
     )
 
 
@@ -285,10 +289,6 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         min_value=1
     )
-    comments = CommentField(
-        widget=SmallTextarea,
-        label='Comments'
-    )
     weight = forms.DecimalField(
         min_value=0,
         required=False
@@ -299,10 +299,18 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         initial='',
         widget=StaticSelect()
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Rack
     fieldsets = (
-        ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')),
+        ('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag', 'description')),
         ('Location', ('region', 'site_group', 'site', 'location')),
         ('Hardware', (
             'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
@@ -310,8 +318,8 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
         ('Weight', ('weight', 'weight_unit')),
     )
     nullable_fields = (
-        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
-        'weight', 'weight_unit'
+        'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
+        'weight_unit', 'description', 'comments',
     )
 
 
@@ -328,14 +336,19 @@ class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = RackReservation
     fieldsets = (
         (None, ('user', 'tenant', 'description')),
     )
+    nullable_fields = ('comments',)
 
 
 class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
@@ -383,13 +396,21 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
         initial='',
         widget=StaticSelect()
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = DeviceType
     fieldsets = (
-        ('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')),
+        ('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
         ('Weight', ('weight', 'weight_unit')),
     )
-    nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit')
+    nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
 
 
 class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
@@ -410,13 +431,21 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
         initial='',
         widget=StaticSelect()
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = ModuleType
     fieldsets = (
-        ('Module Type', ('manufacturer', 'part_number')),
+        ('Module Type', ('manufacturer', 'part_number', 'description')),
         ('Weight', ('weight', 'weight_unit')),
     )
-    nullable_fields = ('part_number', 'weight', 'weight_unit')
+    nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
 
 
 class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
@@ -512,15 +541,23 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Serial Number'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Device
     fieldsets = (
-        ('Device', ('device_role', 'status', 'tenant', 'platform')),
+        ('Device', ('device_role', 'status', 'tenant', 'platform', 'description')),
         ('Location', ('site', 'location')),
         ('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
     )
     nullable_fields = (
-        'location', 'tenant', 'platform', 'serial', 'airflow',
+        'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
     )
 
 
@@ -541,12 +578,20 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Serial Number'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Module
     fieldsets = (
-        (None, ('manufacturer', 'module_type', 'serial')),
+        (None, ('manufacturer', 'module_type', 'serial', 'description')),
     )
-    nullable_fields = ('serial',)
+    nullable_fields = ('serial', 'description', 'comments')
 
 
 class CableBulkEditForm(NetBoxModelBulkEditForm):
@@ -583,14 +628,22 @@ class CableBulkEditForm(NetBoxModelBulkEditForm):
         initial='',
         widget=StaticSelect()
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Cable
     fieldsets = (
-        (None, ('type', 'status', 'tenant', 'label')),
+        (None, ('type', 'status', 'tenant', 'label', 'description')),
         ('Attributes', ('color', 'length', 'length_unit')),
     )
     nullable_fields = (
-        'type', 'status', 'tenant', 'label', 'color', 'length',
+        'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
     )
 
 
@@ -599,12 +652,20 @@ class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
         max_length=30,
         required=False
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = VirtualChassis
     fieldsets = (
-        (None, ('domain',)),
+        (None, ('domain', 'description')),
     )
-    nullable_fields = ('domain',)
+    nullable_fields = ('domain', 'description', 'comments')
 
 
 class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
@@ -637,12 +698,20 @@ class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
             'site_id': '$site'
         }
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = PowerPanel
     fieldsets = (
-        (None, ('region', 'site_group', 'site', 'location')),
+        (None, ('region', 'site_group', 'site', 'location', 'description')),
     )
-    nullable_fields = ('location',)
+    nullable_fields = ('location', 'description', 'comments')
 
 
 class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
@@ -691,6 +760,10 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         widget=BulkEditNullBooleanSelect
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
     comments = CommentField(
         widget=SmallTextarea,
         label='Comments'
@@ -698,10 +771,10 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
 
     model = PowerFeed
     fieldsets = (
-        (None, ('power_panel', 'rack', 'status', 'type', 'mark_connected')),
+        (None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description')),
         ('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
     )
-    nullable_fields = ('location', 'comments')
+    nullable_fields = ('location', 'description', 'comments')
 
 
 #

+ 9 - 8
netbox/dcim/forms/bulk_import.py

@@ -196,7 +196,8 @@ class RackCSVForm(NetBoxModelCSVForm):
         model = Rack
         fields = (
             'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
-            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'comments',
+            'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
+            'description', 'comments',
         )
 
     def __init__(self, data=None, *args, **kwargs):
@@ -240,7 +241,7 @@ class RackReservationCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = RackReservation
-        fields = ('site', 'location', 'rack', 'units', 'tenant', 'description')
+        fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments')
 
     def __init__(self, data=None, *args, **kwargs):
         super().__init__(data, *args, **kwargs)
@@ -387,7 +388,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
         fields = [
             'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
             'site', 'location', 'rack', 'position', 'face', 'airflow', 'virtual_chassis', 'vc_position', 'vc_priority',
-            'cluster', 'comments',
+            'cluster', 'description', 'comments',
         ]
 
     def __init__(self, data=None, *args, **kwargs):
@@ -424,7 +425,7 @@ class ModuleCSVForm(NetBoxModelCSVForm):
     class Meta:
         model = Module
         fields = (
-            'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments',
+            'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description', 'comments',
         )
 
     def __init__(self, data=None, *args, **kwargs):
@@ -927,7 +928,7 @@ class CableCSVForm(NetBoxModelCSVForm):
         model = Cable
         fields = [
             'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
-            'status', 'tenant', 'label', 'color', 'length', 'length_unit',
+            'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments',
         ]
         help_texts = {
             'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
@@ -984,7 +985,7 @@ class VirtualChassisCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = VirtualChassis
-        fields = ('name', 'domain', 'master')
+        fields = ('name', 'domain', 'master', 'description')
 
 
 #
@@ -1005,7 +1006,7 @@ class PowerPanelCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = PowerPanel
-        fields = ('site', 'location', 'name')
+        fields = ('site', 'location', 'name', 'description', 'comments')
 
     def __init__(self, data=None, *args, **kwargs):
         super().__init__(data, *args, **kwargs)
@@ -1061,7 +1062,7 @@ class PowerFeedCSVForm(NetBoxModelCSVForm):
         model = PowerFeed
         fields = (
             'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
-            'voltage', 'amperage', 'max_utilization', 'comments',
+            'voltage', 'amperage', 'max_utilization', 'description', 'comments',
         )
 
     def __init__(self, data=None, *args, **kwargs):

+ 22 - 18
netbox/dcim/forms/model_forms.py

@@ -278,7 +278,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
         fields = [
             'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
             'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
-            'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'comments', 'tags',
+            'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'description', 'comments', 'tags',
         ]
         help_texts = {
             'site': "The site at which the rack exists",
@@ -342,6 +342,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
         ),
         widget=StaticSelect()
     )
+    comments = CommentField()
 
     fieldsets = (
         ('Reservation', ('region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
@@ -352,7 +353,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
         model = RackReservation
         fields = [
             'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
-            'description', 'tags',
+            'description', 'comments', 'tags',
         ]
 
 
@@ -383,10 +384,10 @@ class DeviceTypeForm(NetBoxModelForm):
 
     fieldsets = (
         ('Device Type', (
-            'manufacturer', 'model', 'slug', 'part_number', 'tags',
+            'manufacturer', 'model', 'slug', 'description', 'tags',
         )),
         ('Chassis', (
-            'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
+            'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
         )),
         ('Attributes', ('weight', 'weight_unit')),
         ('Images', ('front_image', 'rear_image')),
@@ -396,7 +397,7 @@ class DeviceTypeForm(NetBoxModelForm):
         model = DeviceType
         fields = [
             'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
-            'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags',
+            'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags',
         ]
         widgets = {
             'airflow': StaticSelect(),
@@ -418,15 +419,14 @@ class ModuleTypeForm(NetBoxModelForm):
     comments = CommentField()
 
     fieldsets = (
-        ('Module Type', (
-            'manufacturer', 'model', 'part_number', 'tags', 'weight', 'weight_unit'
-        )),
+        ('Module Type', ('manufacturer', 'model', 'part_number', 'description', 'tags')),
+        ('Weight', ('weight', 'weight_unit'))
     )
 
     class Meta:
         model = ModuleType
         fields = [
-            'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags',
+            'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description', 'comments', 'tags',
         ]
 
         widgets = {
@@ -591,7 +591,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
             'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack',
             'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6',
             'cluster_group', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority',
-            'comments', 'tags', 'local_context_data'
+            'description', 'comments', 'tags', 'local_context_data'
         ]
         help_texts = {
             'device_role': "The function this device serves",
@@ -705,7 +705,7 @@ class ModuleForm(NetBoxModelForm):
 
     fieldsets = (
         ('Module', (
-            'device', 'module_bay', 'manufacturer', 'module_type', 'tags',
+            'device', 'module_bay', 'manufacturer', 'module_type', 'description', 'tags',
         )),
         ('Hardware', (
             'serial', 'asset_tag', 'replicate_components', 'adopt_components',
@@ -716,7 +716,7 @@ class ModuleForm(NetBoxModelForm):
         model = Module
         fields = [
             'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags',
-            'replicate_components', 'adopt_components', 'comments',
+            'replicate_components', 'adopt_components', 'description', 'comments',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -793,11 +793,13 @@ class ModuleForm(NetBoxModelForm):
 
 
 class CableForm(TenancyForm, NetBoxModelForm):
+    comments = CommentField()
 
     class Meta:
         model = Cable
         fields = [
-            'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
+            'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
+            'comments', 'tags',
         ]
         widgets = {
             'status': StaticSelect,
@@ -840,15 +842,16 @@ class PowerPanelForm(NetBoxModelForm):
             'site_id': '$site'
         }
     )
+    comments = CommentField()
 
     fieldsets = (
-        ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
+        ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'description', 'tags')),
     )
 
     class Meta:
         model = PowerPanel
         fields = [
-            'region', 'site_group', 'site', 'location', 'name', 'tags',
+            'region', 'site_group', 'site', 'location', 'name', 'description', 'comments', 'tags',
         ]
 
 
@@ -894,7 +897,7 @@ class PowerFeedForm(NetBoxModelForm):
     comments = CommentField()
 
     fieldsets = (
-        ('Power Panel', ('region', 'site', 'power_panel')),
+        ('Power Panel', ('region', 'site', 'power_panel', 'description')),
         ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
         ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
     )
@@ -903,7 +906,7 @@ class PowerFeedForm(NetBoxModelForm):
         model = PowerFeed
         fields = [
             'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
-            'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
+            'phase', 'voltage', 'amperage', 'max_utilization', 'description', 'comments', 'tags',
         ]
         widgets = {
             'status': StaticSelect(),
@@ -922,11 +925,12 @@ class VirtualChassisForm(NetBoxModelForm):
         queryset=Device.objects.all(),
         required=False,
     )
+    comments = CommentField()
 
     class Meta:
         model = VirtualChassis
         fields = [
-            'name', 'domain', 'master', 'tags',
+            'name', 'domain', 'master', 'description', 'comments', 'tags',
         ]
         widgets = {
             'master': SelectWithPK(),

+ 2 - 2
netbox/dcim/forms/object_import.py

@@ -30,7 +30,7 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm):
         model = DeviceType
         fields = [
             'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
-            'comments',
+            'description', 'comments',
         ]
 
 
@@ -42,7 +42,7 @@ class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = ModuleType
-        fields = ['manufacturer', 'model', 'part_number', 'comments']
+        fields = ['manufacturer', 'model', 'part_number', 'description', 'comments']
 
 
 #

+ 78 - 0
netbox/dcim/migrations/0165_standardize_description_comments.py

@@ -0,0 +1,78 @@
+# Generated by Django 4.1.2 on 2022-11-03 18:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0164_rack_mounting_depth'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='cable',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='cable',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='device',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='devicetype',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='module',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='moduletype',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='powerfeed',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='powerpanel',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='powerpanel',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='rackreservation',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='virtualchassis',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='virtualchassis',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+    ]

+ 3 - 3
netbox/dcim/models/cables.py

@@ -12,8 +12,8 @@ from django.urls import reverse
 from dcim.choices import *
 from dcim.constants import *
 from dcim.fields import PathField
-from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
-from netbox.models import NetBoxModel
+from dcim.utils import decompile_path_node, object_to_path_node
+from netbox.models import PrimaryModel
 from utilities.fields import ColorField
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import to_meters
@@ -34,7 +34,7 @@ trace_paths = Signal()
 # Cables
 #
 
-class Cable(NetBoxModel):
+class Cable(PrimaryModel):
     """
     A physical connection between two endpoints.
     """

+ 6 - 18
netbox/dcim/models/devices.py

@@ -18,7 +18,7 @@ from dcim.constants import *
 from extras.models import ConfigContextModel
 from extras.querysets import ConfigContextModelQuerySet
 from netbox.config import ConfigItem
-from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.choices import ColorChoices
 from utilities.fields import ColorField, NaturalOrderingField
 from .device_components import *
@@ -54,7 +54,7 @@ class Manufacturer(OrganizationalModel):
         return reverse('dcim:manufacturer', args=[self.pk])
 
 
-class DeviceType(NetBoxModel, WeightMixin):
+class DeviceType(PrimaryModel, WeightMixin):
     """
     A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
     well as high-level functional role(s).
@@ -117,9 +117,6 @@ class DeviceType(NetBoxModel, WeightMixin):
         upload_to='devicetype-images',
         blank=True
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     clone_fields = (
         'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
@@ -298,7 +295,7 @@ class DeviceType(NetBoxModel, WeightMixin):
         return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
 
 
-class ModuleType(NetBoxModel, WeightMixin):
+class ModuleType(PrimaryModel, WeightMixin):
     """
     A ModuleType represents a hardware element that can be installed within a device and which houses additional
     components; for example, a line card within a chassis-based switch such as the Cisco Catalyst 6500. Like a
@@ -318,9 +315,6 @@ class ModuleType(NetBoxModel, WeightMixin):
         blank=True,
         help_text='Discrete part number (optional)'
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     images = GenericRelation(
@@ -443,7 +437,7 @@ class Platform(OrganizationalModel):
         return reverse('dcim:platform', args=[self.pk])
 
 
-class Device(NetBoxModel, ConfigContextModel):
+class Device(PrimaryModel, ConfigContextModel):
     """
     A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
     DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
@@ -587,9 +581,6 @@ class Device(NetBoxModel, ConfigContextModel):
         null=True,
         validators=[MaxValueValidator(255)]
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     contacts = GenericRelation(
@@ -906,7 +897,7 @@ class Device(NetBoxModel, ConfigContextModel):
         return round(total_weight / 1000, 2)
 
 
-class Module(NetBoxModel, ConfigContextModel):
+class Module(PrimaryModel, ConfigContextModel):
     """
     A Module represents a field-installable component within a Device which may itself hold multiple device components
     (for example, a line card within a chassis switch). Modules are instantiated from ModuleTypes.
@@ -939,9 +930,6 @@ class Module(NetBoxModel, ConfigContextModel):
         verbose_name='Asset tag',
         help_text='A unique tag used to identify this device'
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     clone_fields = ('device', 'module_type')
 
@@ -1019,7 +1007,7 @@ class Module(NetBoxModel, ConfigContextModel):
 # Virtual chassis
 #
 
-class VirtualChassis(NetBoxModel):
+class VirtualChassis(PrimaryModel):
     """
     A collection of Devices which operate with a shared control plane (e.g. a switch stack).
     """

+ 3 - 7
netbox/dcim/models/power.py

@@ -6,9 +6,8 @@ from django.db import models
 from django.urls import reverse
 
 from dcim.choices import *
-from dcim.constants import *
 from netbox.config import ConfigItem
-from netbox.models import NetBoxModel
+from netbox.models import PrimaryModel
 from utilities.validators import ExclusionValidator
 from .device_components import CabledObjectModel, PathEndpoint
 
@@ -22,7 +21,7 @@ __all__ = (
 # Power
 #
 
-class PowerPanel(NetBoxModel):
+class PowerPanel(PrimaryModel):
     """
     A distribution point for electrical power; e.g. a data center RPP.
     """
@@ -77,7 +76,7 @@ class PowerPanel(NetBoxModel):
             )
 
 
-class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
+class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
     """
     An electrical circuit delivered from a PowerPanel.
     """
@@ -132,9 +131,6 @@ class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
         default=0,
         editable=False
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     clone_fields = (
         'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',

+ 3 - 6
netbox/dcim/models/racks.py

@@ -14,7 +14,7 @@ from django.urls import reverse
 from dcim.choices import *
 from dcim.constants import *
 from dcim.svg import RackElevationSVG
-from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models import OrganizationalModel, PrimaryModel
 from utilities.choices import ColorChoices
 from utilities.fields import ColorField, NaturalOrderingField
 from utilities.utils import array_to_string, drange
@@ -46,7 +46,7 @@ class RackRole(OrganizationalModel):
         return reverse('dcim:rackrole', args=[self.pk])
 
 
-class Rack(NetBoxModel, WeightMixin):
+class Rack(PrimaryModel, WeightMixin):
     """
     Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
     Each Rack is assigned to a Site and (optionally) a Location.
@@ -157,9 +157,6 @@ class Rack(NetBoxModel, WeightMixin):
             'distance between the front and rear rails.'
         )
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     vlan_groups = GenericRelation(
@@ -463,7 +460,7 @@ class Rack(NetBoxModel, WeightMixin):
         return round(total_weight / 1000, 2)
 
 
-class RackReservation(NetBoxModel):
+class RackReservation(PrimaryModel):
     """
     One or more reserved units within a Rack.
     """

+ 2 - 9
netbox/dcim/models/sites.py

@@ -6,7 +6,7 @@ from timezone_field import TimeZoneField
 
 from dcim.choices import *
 from dcim.constants import *
-from netbox.models import NestedGroupModel, NetBoxModel
+from netbox.models import NestedGroupModel, PrimaryModel
 from utilities.fields import NaturalOrderingField
 
 __all__ = (
@@ -131,7 +131,7 @@ class SiteGroup(NestedGroupModel):
 # Sites
 #
 
-class Site(NetBoxModel):
+class Site(PrimaryModel):
     """
     A Site represents a geographic location within a network; typically a building or campus. The optional facility
     field can be used to include an external designation, such as a data center name (e.g. Equinix SV6).
@@ -188,10 +188,6 @@ class Site(NetBoxModel):
     time_zone = TimeZoneField(
         blank=True
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     physical_address = models.CharField(
         max_length=200,
         blank=True
@@ -214,9 +210,6 @@ class Site(NetBoxModel):
         null=True,
         help_text='GPS coordinate (longitude)'
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     vlan_groups = GenericRelation(

+ 2 - 1
netbox/dcim/tables/cables.py

@@ -111,6 +111,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
         order_by=('_abs_length', 'length_unit')
     )
     color = columns.ColorColumn()
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='dcim:cable_list'
     )
@@ -120,7 +121,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
         fields = (
             'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b',
             'location_a', 'location_b', 'site_a', 'site_b', 'status', 'type', 'tenant', 'tenant_group', 'color',
-            'length', 'tags', 'created', 'last_updated',
+            'length', 'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'status', 'type',

+ 33 - 45
netbox/dcim/tables/devices.py

@@ -1,21 +1,5 @@
 import django_tables2 as tables
-from dcim.models import (
-    ConsolePort,
-    ConsoleServerPort,
-    Device,
-    DeviceBay,
-    DeviceRole,
-    FrontPort,
-    Interface,
-    InventoryItem,
-    InventoryItemRole,
-    ModuleBay,
-    Platform,
-    PowerOutlet,
-    PowerPort,
-    RearPort,
-    VirtualChassis,
-)
+from dcim import models
 from django_tables2.utils import Accessor
 from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
 
@@ -106,7 +90,7 @@ class DeviceRoleTable(NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = DeviceRole
+        model = models.DeviceRole
         fields = (
             'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
             'actions', 'created', 'last_updated',
@@ -137,7 +121,7 @@ class PlatformTable(NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = Platform
+        model = models.Platform
         fields = (
             'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
             'description', 'tags', 'actions', 'created', 'last_updated',
@@ -220,12 +204,12 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = Device
+        model = models.Device
         fields = (
             'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
             'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'position', 'face',
             'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
-            'vc_priority', 'comments', 'contacts', 'tags', 'created', 'last_updated',
+            'vc_priority', 'description', 'comments', 'contacts', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
@@ -252,7 +236,7 @@ class DeviceImportTable(TenancyColumnsMixin, NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = Device
+        model = models.Device
         fields = ('id', 'name', 'status', 'tenant', 'tenant_group', 'site', 'rack', 'position', 'device_role', 'device_type')
         empty_text = False
 
@@ -326,7 +310,7 @@ class ConsolePortTable(ModularDeviceComponentTable, PathEndpointTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ConsolePort
+        model = models.ConsolePort
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
             'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
@@ -345,7 +329,7 @@ class DeviceConsolePortTable(ConsolePortTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ConsolePort
+        model = models.ConsolePort
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
             'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions'
@@ -368,7 +352,7 @@ class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ConsoleServerPort
+        model = models.ConsoleServerPort
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
             'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
@@ -388,7 +372,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ConsoleServerPort
+        model = models.ConsoleServerPort
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
             'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
@@ -411,7 +395,7 @@ class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = PowerPort
+        model = models.PowerPort
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'mark_connected',
             'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
@@ -432,7 +416,7 @@ class DevicePowerPortTable(PowerPortTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = PowerPort
+        model = models.PowerPort
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'maximum_draw', 'allocated_draw',
             'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
@@ -460,7 +444,7 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = PowerOutlet
+        model = models.PowerOutlet
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
             'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
@@ -480,7 +464,7 @@ class DevicePowerOutletTable(PowerOutletTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = PowerOutlet
+        model = models.PowerOutlet
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description',
             'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
@@ -544,7 +528,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = Interface
+        model = models.Interface
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
             'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel',
@@ -578,7 +562,7 @@ class DeviceInterfaceTable(InterfaceTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = Interface
+        model = models.Interface
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag',
             'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency',
@@ -617,7 +601,7 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = FrontPort
+        model = models.FrontPort
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
             'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
@@ -640,7 +624,7 @@ class DeviceFrontPortTable(FrontPortTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = FrontPort
+        model = models.FrontPort
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position',
             'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
@@ -666,7 +650,7 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = RearPort
+        model = models.RearPort
         fields = (
             'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
             'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'created', 'last_updated',
@@ -686,7 +670,7 @@ class DeviceRearPortTable(RearPortTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = RearPort
+        model = models.RearPort
         fields = (
             'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected',
             'cable', 'cable_color', 'link_peer', 'tags', 'actions',
@@ -727,7 +711,7 @@ class DeviceBayTable(DeviceComponentTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = DeviceBay
+        model = models.DeviceBay
         fields = (
             'pk', 'id', 'name', 'device', 'label', 'status', 'device_role', 'device_type', 'installed_device', 'description', 'tags',
             'created', 'last_updated',
@@ -748,7 +732,7 @@ class DeviceDeviceBayTable(DeviceBayTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = DeviceBay
+        model = models.DeviceBay
         fields = (
             'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
         )
@@ -777,7 +761,7 @@ class ModuleBayTable(DeviceComponentTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ModuleBay
+        model = models.ModuleBay
         fields = (
             'pk', 'id', 'name', 'device', 'label', 'position', 'installed_module', 'module_serial', 'module_asset_tag',
             'description', 'tags',
@@ -791,7 +775,7 @@ class DeviceModuleBayTable(ModuleBayTable):
     )
 
     class Meta(DeviceComponentTable.Meta):
-        model = ModuleBay
+        model = models.ModuleBay
         fields = (
             'pk', 'id', 'name', 'label', 'position', 'installed_module', 'module_serial', 'module_asset_tag',
             'description', 'tags', 'actions',
@@ -821,7 +805,7 @@ class InventoryItemTable(DeviceComponentTable):
     cable = None  # Override DeviceComponentTable
 
     class Meta(NetBoxTable.Meta):
-        model = InventoryItem
+        model = models.InventoryItem
         fields = (
             'pk', 'id', 'name', 'device', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial',
             'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
@@ -840,7 +824,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = InventoryItem
+        model = models.InventoryItem
         fields = (
             'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
             'description', 'discovered', 'tags', 'actions',
@@ -865,7 +849,7 @@ class InventoryItemRoleTable(NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = InventoryItemRole
+        model = models.InventoryItemRole
         fields = (
             'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
         )
@@ -888,11 +872,15 @@ class VirtualChassisTable(NetBoxTable):
         url_params={'virtual_chassis_id': 'pk'},
         verbose_name='Members'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='dcim:virtualchassis_list'
     )
 
     class Meta(NetBoxTable.Meta):
-        model = VirtualChassis
-        fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags', 'created', 'last_updated',)
+        model = models.VirtualChassis
+        fields = (
+            'pk', 'id', 'name', 'domain', 'master', 'member_count', 'description', 'comments', 'tags', 'created',
+            'last_updated',
+        )
         default_columns = ('pk', 'name', 'domain', 'master', 'member_count')

+ 14 - 30
netbox/dcim/tables/devicetypes.py

@@ -1,19 +1,6 @@
 import django_tables2 as tables
 
-from dcim.models import (
-    ConsolePortTemplate,
-    ConsoleServerPortTemplate,
-    DeviceBayTemplate,
-    DeviceType,
-    FrontPortTemplate,
-    InterfaceTemplate,
-    InventoryItemTemplate,
-    Manufacturer,
-    ModuleBayTemplate,
-    PowerOutletTemplate,
-    PowerPortTemplate,
-    RearPortTemplate,
-)
+from dcim import models
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import ContactsColumnMixin
 from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, DEVICE_WEIGHT
@@ -59,7 +46,7 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
     )
 
     class Meta(NetBoxTable.Meta):
-        model = Manufacturer
+        model = models.Manufacturer
         fields = (
             'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
             'contacts', 'actions', 'created', 'last_updated',
@@ -100,15 +87,12 @@ class DeviceTypeTable(NetBoxTable):
         template_code=DEVICE_WEIGHT,
         order_by=('_abs_weight', 'weight_unit')
     )
-    u_height = columns.TemplateColumn(
-        template_code='{{ value|floatformat }}'
-    )
 
     class Meta(NetBoxTable.Meta):
-        model = DeviceType
+        model = models.DeviceType
         fields = (
             'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
-            'airflow', 'weight', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
+            'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',
@@ -138,7 +122,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = ConsolePortTemplate
+        model = models.ConsolePortTemplate
         fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
         empty_text = "None"
 
@@ -150,7 +134,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = ConsoleServerPortTemplate
+        model = models.ConsoleServerPortTemplate
         fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
         empty_text = "None"
 
@@ -162,7 +146,7 @@ class PowerPortTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = PowerPortTemplate
+        model = models.PowerPortTemplate
         fields = ('pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'actions')
         empty_text = "None"
 
@@ -174,7 +158,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = PowerOutletTemplate
+        model = models.PowerOutletTemplate
         fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
         empty_text = "None"
 
@@ -189,7 +173,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = InterfaceTemplate
+        model = models.InterfaceTemplate
         fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'poe_mode', 'poe_type', 'actions')
         empty_text = "None"
 
@@ -205,7 +189,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = FrontPortTemplate
+        model = models.FrontPortTemplate
         fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions')
         empty_text = "None"
 
@@ -218,7 +202,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = RearPortTemplate
+        model = models.RearPortTemplate
         fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions')
         empty_text = "None"
 
@@ -229,7 +213,7 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = ModuleBayTemplate
+        model = models.ModuleBayTemplate
         fields = ('pk', 'name', 'label', 'position', 'description', 'actions')
         empty_text = "None"
 
@@ -240,7 +224,7 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = DeviceBayTemplate
+        model = models.DeviceBayTemplate
         fields = ('pk', 'name', 'label', 'description', 'actions')
         empty_text = "None"
 
@@ -260,7 +244,7 @@ class InventoryItemTemplateTable(ComponentTemplateTable):
     )
 
     class Meta(ComponentTemplateTable.Meta):
-        model = InventoryItemTemplate
+        model = models.InventoryItemTemplate
         fields = (
             'pk', 'name', 'label', 'parent', 'role', 'manufacturer', 'part_id', 'component', 'description', 'actions',
         )

+ 3 - 3
netbox/dcim/tables/modules.py

@@ -35,7 +35,7 @@ class ModuleTypeTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = ModuleType
         fields = (
-            'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'comments', 'tags',
+            'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'description', 'comments', 'tags',
         )
         default_columns = (
             'pk', 'model', 'manufacturer', 'part_number',
@@ -64,8 +64,8 @@ class ModuleTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Module
         fields = (
-            'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'comments',
-            'tags',
+            'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'description',
+            'comments', 'tags',
         )
         default_columns = (
             'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag',

+ 4 - 2
netbox/dcim/tables/power.py

@@ -31,6 +31,7 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
         url_params={'power_panel_id': 'pk'},
         verbose_name='Feeds'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='dcim:powerpanel_list'
     )
@@ -38,7 +39,8 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = PowerPanel
         fields = (
-            'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'description', 'comments', 'tags',
+            'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
 
@@ -77,7 +79,7 @@ class PowerFeedTable(CableTerminationTable):
         fields = (
             'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
             'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power',
-            'comments', 'tags', 'created', 'last_updated',
+            'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable',

+ 5 - 4
netbox/dcim/tables/racks.py

@@ -90,8 +90,8 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
         fields = (
             'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial',
             'asset_tag', 'type', 'u_height', 'width', 'outer_width', 'outer_depth', 'mounting_depth', 'weight',
-            'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'contacts', 'tags', 'created',
-            'last_updated',
+            'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'description', 'contacts', 'tags',
+            'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
@@ -123,6 +123,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
         orderable=False,
         verbose_name='Units'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='dcim:rackreservation_list'
     )
@@ -130,7 +131,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = RackReservation
         fields = (
-            'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant', 'tenant_group', 'description', 'tags',
-            'actions', 'created', 'last_updated',
+            'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant',
+            'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
         )
         default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description')

+ 21 - 18
netbox/ipam/api/serializers.py

@@ -31,8 +31,8 @@ class ASNSerializer(NetBoxModelSerializer):
     class Meta:
         model = ASN
         fields = [
-            'id', 'url', 'display', 'asn', 'rir', 'tenant', 'description', 'site_count', 'provider_count', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'asn', 'rir', 'tenant', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated', 'site_count', 'provider_count',
         ]
 
 
@@ -61,8 +61,9 @@ class VRFSerializer(NetBoxModelSerializer):
     class Meta:
         model = VRF
         fields = [
-            'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets',
-            'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', 'prefix_count',
+            'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments',
+            'import_targets', 'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count',
+            'prefix_count',
         ]
 
 
@@ -77,7 +78,8 @@ class RouteTargetSerializer(NetBoxModelSerializer):
     class Meta:
         model = RouteTarget
         fields = [
-            'id', 'url', 'display', 'name', 'tenant', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'name', 'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created',
+            'last_updated',
         ]
 
 
@@ -106,8 +108,8 @@ class AggregateSerializer(NetBoxModelSerializer):
     class Meta:
         model = Aggregate
         fields = [
-            'id', 'url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'comments',
+            'tags', 'custom_fields', 'created', 'last_updated',
         ]
         read_only_fields = ['family']
 
@@ -123,8 +125,8 @@ class FHRPGroupSerializer(NetBoxModelSerializer):
     class Meta:
         model = FHRPGroup
         fields = [
-            'id', 'name', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_addresses',
-            'tags', 'custom_fields', 'created', 'last_updated',
+            'id', 'name', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'comments',
+            'tags', 'custom_fields', 'created', 'last_updated', 'ip_addresses',
         ]
 
 
@@ -215,7 +217,7 @@ class VLANSerializer(NetBoxModelSerializer):
         model = VLAN
         fields = [
             'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description',
-            'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
+            'comments', 'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
         ]
 
 
@@ -273,7 +275,8 @@ class PrefixSerializer(NetBoxModelSerializer):
         model = Prefix
         fields = [
             'id', 'url', 'display', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool',
-            'mark_utilized', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'children', '_depth',
+            'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
+            '_depth',
         ]
         read_only_fields = ['family']
 
@@ -342,7 +345,7 @@ class IPRangeSerializer(NetBoxModelSerializer):
         model = IPRange
         fields = [
             'id', 'url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 'status', 'role',
-            'description', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
+            'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
         ]
         read_only_fields = ['family']
 
@@ -371,8 +374,8 @@ class IPAddressSerializer(NetBoxModelSerializer):
         model = IPAddress
         fields = [
             'id', 'url', 'display', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'assigned_object_type',
-            'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 'dns_name', 'description', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 'dns_name', 'description', 'comments',
+            'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
     @swagger_serializer_method(serializer_or_field=serializers.JSONField)
@@ -415,8 +418,8 @@ class ServiceTemplateSerializer(NetBoxModelSerializer):
     class Meta:
         model = ServiceTemplate
         fields = [
-            'id', 'url', 'display', 'name', 'ports', 'protocol', 'description', 'tags', 'custom_fields', 'created',
-            'last_updated',
+            'id', 'url', 'display', 'name', 'ports', 'protocol', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated',
         ]
 
 
@@ -436,7 +439,7 @@ class ServiceSerializer(NetBoxModelSerializer):
         model = Service
         fields = [
             'id', 'url', 'display', 'device', 'virtual_machine', 'name', 'ports', 'protocol', 'ipaddresses',
-            'description', 'tags', 'custom_fields', 'created', 'last_updated',
+            'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 #
@@ -465,7 +468,7 @@ class L2VPNSerializer(NetBoxModelSerializer):
         model = L2VPN
         fields = [
             'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
-            'description', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
+            'description', 'comments', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
         ]
 
 

+ 67 - 23
netbox/ipam/forms/bulk_edit.py

@@ -8,8 +8,8 @@ from ipam.models import ASN
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
 from utilities.forms import (
-    add_blank_choice, BulkEditNullBooleanSelect, DynamicModelChoiceField, NumericArrayField, StaticSelect,
-    DynamicModelMultipleChoiceField,
+    add_blank_choice, BulkEditNullBooleanSelect, CommentField, DynamicModelChoiceField, NumericArrayField,
+    SmallTextarea, StaticSelect, DynamicModelMultipleChoiceField,
 )
 
 __all__ = (
@@ -43,15 +43,19 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
         label='Enforce unique space'
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = VRF
     fieldsets = (
         (None, ('tenant', 'enforce_unique', 'description')),
     )
-    nullable_fields = ('tenant', 'description')
+    nullable_fields = ('tenant', 'description', 'comments')
 
 
 class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
@@ -63,12 +67,16 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
         max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = RouteTarget
     fieldsets = (
         (None, ('tenant', 'description')),
     )
-    nullable_fields = ('tenant', 'description')
+    nullable_fields = ('tenant', 'description', 'comments')
 
 
 class RIRBulkEditForm(NetBoxModelBulkEditForm):
@@ -103,15 +111,19 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = ASN
     fieldsets = (
         (None, ('sites', 'rir', 'tenant', 'description')),
     )
-    nullable_fields = ('date_added', 'description')
+    nullable_fields = ('date_added', 'description', 'comments')
 
 
 class AggregateBulkEditForm(NetBoxModelBulkEditForm):
@@ -128,15 +140,19 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Aggregate
     fieldsets = (
         (None, ('rir', 'tenant', 'date_added', 'description')),
     )
-    nullable_fields = ('date_added', 'description')
+    nullable_fields = ('date_added', 'description', 'comments')
 
 
 class RoleBulkEditForm(NetBoxModelBulkEditForm):
@@ -206,9 +222,13 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
         label='Treat as 100% utilized'
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Prefix
     fieldsets = (
@@ -217,7 +237,7 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
         ('Addressing', ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
     )
     nullable_fields = (
-        'site', 'vrf', 'tenant', 'role', 'description',
+        'site', 'vrf', 'tenant', 'role', 'description', 'comments',
     )
 
 
@@ -241,16 +261,20 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = IPRange
     fieldsets = (
         (None, ('status', 'role', 'vrf', 'tenant', 'description')),
     )
     nullable_fields = (
-        'vrf', 'tenant', 'role', 'description',
+        'vrf', 'tenant', 'role', 'description', 'comments',
     )
 
 
@@ -285,9 +309,13 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
         label='DNS name'
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = IPAddress
     fieldsets = (
@@ -295,7 +323,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
         ('Addressing', ('vrf', 'mask_length', 'dns_name')),
     )
     nullable_fields = (
-        'vrf', 'role', 'tenant', 'dns_name', 'description',
+        'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
     )
 
 
@@ -329,13 +357,17 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
         max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = FHRPGroup
     fieldsets = (
         (None, ('protocol', 'group_id', 'name', 'description')),
         ('Authentication', ('auth_type', 'auth_key')),
     )
-    nullable_fields = ('auth_type', 'auth_key', 'name', 'description')
+    nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
 
 
 class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
@@ -405,9 +437,13 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = VLAN
     fieldsets = (
@@ -415,7 +451,7 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
         ('Site & Group', ('region', 'site_group', 'site', 'group')),
     )
     nullable_fields = (
-        'site', 'group', 'tenant', 'role', 'description',
+        'site', 'group', 'tenant', 'role', 'description', 'comments',
     )
 
 
@@ -433,15 +469,19 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = ServiceTemplate
     fieldsets = (
         (None, ('protocol', 'ports', 'description')),
     )
-    nullable_fields = ('description',)
+    nullable_fields = ('description', 'comments')
 
 
 class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
@@ -459,15 +499,19 @@ class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
         required=False
     )
     description = forms.CharField(
-        max_length=100,
+        max_length=200,
         required=False
     )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = L2VPN
     fieldsets = (
-        (None, ('type', 'description', 'tenant')),
+        (None, ('type', 'tenant', 'description')),
     )
-    nullable_fields = ('tenant', 'description',)
+    nullable_fields = ('tenant', 'description', 'comments')
 
 
 class L2VPNTerminationBulkEditForm(NetBoxModelBulkEditForm):

+ 12 - 12
netbox/ipam/forms/bulk_import.py

@@ -41,7 +41,7 @@ class VRFCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = VRF
-        fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description')
+        fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments')
 
 
 class RouteTargetCSVForm(NetBoxModelCSVForm):
@@ -54,7 +54,7 @@ class RouteTargetCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = RouteTarget
-        fields = ('name', 'description', 'tenant')
+        fields = ('name', 'tenant', 'description', 'comments')
 
 
 class RIRCSVForm(NetBoxModelCSVForm):
@@ -83,7 +83,7 @@ class AggregateCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = Aggregate
-        fields = ('prefix', 'rir', 'tenant', 'date_added', 'description')
+        fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments')
 
 
 class ASNCSVForm(NetBoxModelCSVForm):
@@ -101,7 +101,7 @@ class ASNCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = ASN
-        fields = ('asn', 'rir', 'tenant', 'description')
+        fields = ('asn', 'rir', 'tenant', 'description', 'comments')
         help_texts = {}
 
 
@@ -159,7 +159,7 @@ class PrefixCSVForm(NetBoxModelCSVForm):
         model = Prefix
         fields = (
             'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
-            'description',
+            'description', 'comments',
         )
 
     def __init__(self, data=None, *args, **kwargs):
@@ -204,7 +204,7 @@ class IPRangeCSVForm(NetBoxModelCSVForm):
     class Meta:
         model = IPRange
         fields = (
-            'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description',
+            'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments',
         )
 
 
@@ -257,7 +257,7 @@ class IPAddressCSVForm(NetBoxModelCSVForm):
         model = IPAddress
         fields = [
             'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
-            'dns_name', 'description',
+            'dns_name', 'description', 'comments',
         ]
 
     def __init__(self, data=None, *args, **kwargs):
@@ -326,7 +326,7 @@ class FHRPGroupCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = FHRPGroup
-        fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description')
+        fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments')
 
 
 class VLANGroupCSVForm(NetBoxModelCSVForm):
@@ -389,7 +389,7 @@ class VLANCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = VLAN
-        fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description')
+        fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments')
         help_texts = {
             'vid': 'Numeric VLAN ID (1-4094)',
             'name': 'VLAN name',
@@ -404,7 +404,7 @@ class ServiceTemplateCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = ServiceTemplate
-        fields = ('name', 'protocol', 'ports', 'description')
+        fields = ('name', 'protocol', 'ports', 'description', 'comments')
 
 
 class ServiceCSVForm(NetBoxModelCSVForm):
@@ -427,7 +427,7 @@ class ServiceCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = Service
-        fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description')
+        fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments')
 
 
 class L2VPNCSVForm(NetBoxModelCSVForm):
@@ -443,7 +443,7 @@ class L2VPNCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = L2VPN
-        fields = ('identifier', 'name', 'slug', 'type', 'description')
+        fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments')
 
 
 class L2VPNTerminationCSVForm(NetBoxModelCSVForm):

+ 33 - 17
netbox/ipam/forms/model_forms.py

@@ -11,7 +11,7 @@ from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from utilities.exceptions import PermissionsViolation
 from utilities.forms import (
-    add_blank_choice, BootstrapMixin, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
+    add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
     DynamicModelMultipleChoiceField, NumericArrayField, SlugField, StaticSelect, StaticSelectMultiple,
 )
 from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
@@ -49,6 +49,7 @@ class VRFForm(TenancyForm, NetBoxModelForm):
         queryset=RouteTarget.objects.all(),
         required=False
     )
+    comments = CommentField()
 
     fieldsets = (
         ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
@@ -59,8 +60,8 @@ class VRFForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = VRF
         fields = [
-            'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
-            'tags',
+            'name', 'rd', 'enforce_unique', 'import_targets', 'export_targets', 'tenant_group', 'tenant', 'description',
+            'comments', 'tags',
         ]
         labels = {
             'rd': "RD",
@@ -75,11 +76,12 @@ class RouteTargetForm(TenancyForm, NetBoxModelForm):
         ('Route Target', ('name', 'description', 'tags')),
         ('Tenancy', ('tenant_group', 'tenant')),
     )
+    comments = CommentField()
 
     class Meta:
         model = RouteTarget
         fields = [
-            'name', 'description', 'tenant_group', 'tenant', 'tags',
+            'name', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
         ]
 
 
@@ -104,6 +106,7 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
         queryset=RIR.objects.all(),
         label='RIR'
     )
+    comments = CommentField()
 
     fieldsets = (
         ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
@@ -113,7 +116,7 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = Aggregate
         fields = [
-            'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
+            'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
         ]
         help_texts = {
             'prefix': "IPv4 or IPv6 network",
@@ -134,6 +137,7 @@ class ASNForm(TenancyForm, NetBoxModelForm):
         label='Sites',
         required=False
     )
+    comments = CommentField()
 
     fieldsets = (
         ('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
@@ -143,7 +147,7 @@ class ASNForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = ASN
         fields = [
-            'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
+            'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
         ]
         help_texts = {
             'asn': "AS number",
@@ -235,6 +239,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
         queryset=Role.objects.all(),
         required=False
     )
+    comments = CommentField()
 
     fieldsets = (
         ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
@@ -245,8 +250,8 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = Prefix
         fields = [
-            'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
-            'tenant_group', 'tenant', 'tags',
+            'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'tenant_group', 'tenant',
+            'description', 'comments', 'tags',
         ]
         widgets = {
             'status': StaticSelect(),
@@ -263,6 +268,7 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
         queryset=Role.objects.all(),
         required=False
     )
+    comments = CommentField()
 
     fieldsets = (
         ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
@@ -272,7 +278,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = IPRange
         fields = [
-            'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
+            'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'description',
+            'comments', 'tags',
         ]
         widgets = {
             'status': StaticSelect(),
@@ -394,13 +401,14 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
         required=False,
         label='Make this the primary IP for the device/VM'
     )
+    comments = CommentField()
 
     class Meta:
         model = IPAddress
         fields = [
-            'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
-            'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
-            'tags',
+            'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_site', 'nat_rack', 'nat_device',
+            'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant', 'description',
+            'comments', 'tags',
         ]
         widgets = {
             'status': StaticSelect(),
@@ -535,6 +543,7 @@ class FHRPGroupForm(NetBoxModelForm):
         required=False,
         label='Status'
     )
+    comments = CommentField()
 
     fieldsets = (
         ('FHRP Group', ('protocol', 'group_id', 'name', 'description', 'tags')),
@@ -545,7 +554,8 @@ class FHRPGroupForm(NetBoxModelForm):
     class Meta:
         model = FHRPGroup
         fields = (
-            'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
+            'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'ip_vrf', 'ip_address', 'ip_status', 'description',
+            'comments', 'tags',
         )
 
     def save(self, *args, **kwargs):
@@ -767,11 +777,13 @@ class VLANForm(TenancyForm, NetBoxModelForm):
         queryset=Role.objects.all(),
         required=False
     )
+    comments = CommentField()
 
     class Meta:
         model = VLAN
         fields = [
-            'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
+            'site', 'group', 'vid', 'name', 'status', 'role', 'tenant_group', 'tenant', 'description', 'comments',
+            'tags',
         ]
         help_texts = {
             'site': "Leave blank if this VLAN spans multiple sites",
@@ -794,6 +806,7 @@ class ServiceTemplateForm(NetBoxModelForm):
         ),
         help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
     )
+    comments = CommentField()
 
     fieldsets = (
         ('Service Template', (
@@ -803,7 +816,7 @@ class ServiceTemplateForm(NetBoxModelForm):
 
     class Meta:
         model = ServiceTemplate
-        fields = ('name', 'protocol', 'ports', 'description', 'tags')
+        fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags')
         widgets = {
             'protocol': StaticSelect(),
         }
@@ -834,11 +847,12 @@ class ServiceForm(NetBoxModelForm):
             'virtual_machine_id': '$virtual_machine',
         }
     )
+    comments = CommentField()
 
     class Meta:
         model = Service
         fields = [
-            'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
+            'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
         ]
         help_texts = {
             'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
@@ -899,6 +913,7 @@ class L2VPNForm(TenancyForm, NetBoxModelForm):
         queryset=RouteTarget.objects.all(),
         required=False
     )
+    comments = CommentField()
 
     fieldsets = (
         ('L2VPN', ('name', 'slug', 'type', 'identifier', 'description', 'tags')),
@@ -909,7 +924,8 @@ class L2VPNForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = L2VPN
         fields = (
-            'name', 'slug', 'type', 'identifier', 'description', 'import_targets', 'export_targets', 'tenant', 'tags'
+            'name', 'slug', 'type', 'identifier', 'import_targets', 'export_targets', 'tenant', 'description',
+            'comments', 'tags'
         )
         widgets = {
             'type': StaticSelect(),

+ 73 - 0
netbox/ipam/migrations/0063_standardize_description_comments.py

@@ -0,0 +1,73 @@
+# Generated by Django 4.1.2 on 2022-11-03 18:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ipam', '0062_unique_constraints'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='aggregate',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='asn',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='fhrpgroup',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='ipaddress',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='iprange',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='l2vpn',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='prefix',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='routetarget',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='service',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='servicetemplate',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='vlan',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+        migrations.AddField(
+            model_name='vrf',
+            name='comments',
+            field=models.TextField(blank=True),
+        ),
+    ]

+ 2 - 6
netbox/ipam/models/fhrp.py

@@ -4,7 +4,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.urls import reverse
 
-from netbox.models import ChangeLoggedModel, NetBoxModel
+from netbox.models import ChangeLoggedModel, PrimaryModel
 from netbox.models.features import WebhooksMixin
 from ipam.choices import *
 from ipam.constants import *
@@ -15,7 +15,7 @@ __all__ = (
 )
 
 
-class FHRPGroup(NetBoxModel):
+class FHRPGroup(PrimaryModel):
     """
     A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
     """
@@ -41,10 +41,6 @@ class FHRPGroup(NetBoxModel):
         blank=True,
         verbose_name='Authentication key'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     ip_addresses = GenericRelation(
         to='ipam.IPAddress',
         content_type_field='assigned_object_type',

+ 6 - 26
netbox/ipam/models/ip.py

@@ -9,7 +9,7 @@ from django.utils.functional import cached_property
 
 from dcim.fields import ASNField
 from dcim.models import Device
-from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models import OrganizationalModel, PrimaryModel
 from ipam.choices import *
 from ipam.constants import *
 from ipam.fields import IPNetworkField, IPAddressField
@@ -76,7 +76,7 @@ class RIR(OrganizationalModel):
         return reverse('ipam:rir', args=[self.pk])
 
 
-class ASN(NetBoxModel):
+class ASN(PrimaryModel):
     """
     An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
     one or more ASNs assigned to it.
@@ -86,10 +86,6 @@ class ASN(NetBoxModel):
         verbose_name='ASN',
         help_text='32-bit autonomous system number'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     rir = models.ForeignKey(
         to='ipam.RIR',
         on_delete=models.PROTECT,
@@ -139,7 +135,7 @@ class ASN(NetBoxModel):
             return self.asn
 
 
-class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
+class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
     """
     An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
     the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR.
@@ -162,10 +158,6 @@ class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
         blank=True,
         null=True
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     clone_fields = (
         'rir', 'tenant', 'date_added', 'description',
@@ -264,7 +256,7 @@ class Role(OrganizationalModel):
         return reverse('ipam:role', args=[self.pk])
 
 
-class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
+class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
     """
     A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
     VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
@@ -327,10 +319,6 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
         default=False,
         help_text="Treat as 100% utilized"
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     # Cached depth & child counts
     _depth = models.PositiveSmallIntegerField(
@@ -545,7 +533,7 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
         return min(utilization, 100)
 
 
-class IPRange(NetBoxModel):
+class IPRange(PrimaryModel):
     """
     A range of IP addresses, defined by start and end addresses.
     """
@@ -587,10 +575,6 @@ class IPRange(NetBoxModel):
         null=True,
         help_text='The primary function of this range'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     clone_fields = (
         'vrf', 'tenant', 'status', 'role', 'description',
@@ -740,7 +724,7 @@ class IPRange(NetBoxModel):
         return int(float(child_count) / self.size * 100)
 
 
-class IPAddress(NetBoxModel):
+class IPAddress(PrimaryModel):
     """
     An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
     configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
@@ -813,10 +797,6 @@ class IPAddress(NetBoxModel):
         verbose_name='DNS Name',
         help_text='Hostname or FQDN (not case-sensitive)'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     objects = IPAddressManager()
 

+ 2 - 6
netbox/ipam/models/l2vpn.py

@@ -8,7 +8,7 @@ from django.utils.functional import cached_property
 
 from ipam.choices import L2VPNTypeChoices
 from ipam.constants import L2VPN_ASSIGNMENT_MODELS
-from netbox.models import NetBoxModel
+from netbox.models import NetBoxModel, PrimaryModel
 
 __all__ = (
     'L2VPN',
@@ -16,7 +16,7 @@ __all__ = (
 )
 
 
-class L2VPN(NetBoxModel):
+class L2VPN(PrimaryModel):
     name = models.CharField(
         max_length=100,
         unique=True
@@ -43,10 +43,6 @@ class L2VPN(NetBoxModel):
         related_name='exporting_l2vpns',
         blank=True
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         on_delete=models.PROTECT,

+ 3 - 7
netbox/ipam/models/services.py

@@ -6,7 +6,7 @@ from django.urls import reverse
 
 from ipam.choices import *
 from ipam.constants import *
-from netbox.models import NetBoxModel
+from netbox.models import PrimaryModel
 from utilities.utils import array_to_string
 
 
@@ -30,10 +30,6 @@ class ServiceBase(models.Model):
         ),
         verbose_name='Port numbers'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     class Meta:
         abstract = True
@@ -46,7 +42,7 @@ class ServiceBase(models.Model):
         return array_to_string(self.ports)
 
 
-class ServiceTemplate(ServiceBase, NetBoxModel):
+class ServiceTemplate(ServiceBase, PrimaryModel):
     """
     A template for a Service to be applied to a device or virtual machine.
     """
@@ -62,7 +58,7 @@ class ServiceTemplate(ServiceBase, NetBoxModel):
         return reverse('ipam:servicetemplate', args=[self.pk])
 
 
-class Service(ServiceBase, NetBoxModel):
+class Service(ServiceBase, PrimaryModel):
     """
     A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
     optionally be tied to one or more specific IPAddresses belonging to its parent.

+ 2 - 12
netbox/ipam/models/vlans.py

@@ -8,12 +8,10 @@ from django.urls import reverse
 from dcim.models import Interface
 from ipam.choices import *
 from ipam.constants import *
-from ipam.models import L2VPNTermination
 from ipam.querysets import VLANQuerySet
-from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models import OrganizationalModel, PrimaryModel
 from virtualization.models import VMInterface
 
-
 __all__ = (
     'VLAN',
     'VLANGroup',
@@ -63,10 +61,6 @@ class VLANGroup(OrganizationalModel):
         ),
         help_text='Highest permissible ID of a child VLAN'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     class Meta:
         ordering = ('name', 'pk')  # Name may be non-unique
@@ -120,7 +114,7 @@ class VLANGroup(OrganizationalModel):
         return None
 
 
-class VLAN(NetBoxModel):
+class VLAN(PrimaryModel):
     """
     A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
     to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
@@ -172,10 +166,6 @@ class VLAN(NetBoxModel):
         blank=True,
         null=True
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
 
     l2vpn_terminations = GenericRelation(
         to='ipam.L2VPNTermination',

+ 3 - 11
netbox/ipam/models/vrfs.py

@@ -2,7 +2,7 @@ from django.db import models
 from django.urls import reverse
 
 from ipam.constants import *
-from netbox.models import NetBoxModel
+from netbox.models import PrimaryModel
 
 
 __all__ = (
@@ -11,7 +11,7 @@ __all__ = (
 )
 
 
-class VRF(NetBoxModel):
+class VRF(PrimaryModel):
     """
     A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
     table). Prefixes and IPAddresses can optionally be assigned to VRFs. (Prefixes and IPAddresses not assigned to a VRF
@@ -40,10 +40,6 @@ class VRF(NetBoxModel):
         verbose_name='Enforce unique space',
         help_text='Prevent duplicate prefixes/IP addresses within this VRF'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     import_targets = models.ManyToManyField(
         to='ipam.RouteTarget',
         related_name='importing_vrfs',
@@ -73,7 +69,7 @@ class VRF(NetBoxModel):
         return reverse('ipam:vrf', args=[self.pk])
 
 
-class RouteTarget(NetBoxModel):
+class RouteTarget(PrimaryModel):
     """
     A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
     """
@@ -82,10 +78,6 @@ class RouteTarget(NetBoxModel):
         unique=True,
         help_text='Route target value (formatted in accordance with RFC 4360)'
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         on_delete=models.PROTECT,

+ 2 - 2
netbox/ipam/tables/fhrp.py

@@ -20,7 +20,6 @@ class FHRPGroupTable(NetBoxTable):
     group_id = tables.Column(
         linkify=True
     )
-    comments = columns.MarkdownColumn()
     ip_addresses = tables.TemplateColumn(
         template_code=IPADDRESSES,
         orderable=False,
@@ -29,6 +28,7 @@ class FHRPGroupTable(NetBoxTable):
     member_count = tables.Column(
         verbose_name='Members'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:fhrpgroup_list'
     )
@@ -36,7 +36,7 @@ class FHRPGroupTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = FHRPGroup
         fields = (
-            'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'ip_addresses',
+            'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'comments', 'ip_addresses',
             'member_count', 'tags', 'created', 'last_updated',
         )
         default_columns = (

+ 16 - 10
netbox/ipam/tables/ip.py

@@ -120,6 +120,7 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable):
         linkify_item=True,
         verbose_name='Sites'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:asn_list'
     )
@@ -127,8 +128,8 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = ASN
         fields = (
-            'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description', 'sites', 'tags',
-            'created', 'last_updated', 'actions',
+            'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description',
+            'comments', 'sites', 'tags', 'created', 'last_updated', 'actions',
         )
         default_columns = ('pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant')
 
@@ -153,6 +154,7 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
         accessor='get_utilization',
         orderable=False
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:aggregate_list'
     )
@@ -160,8 +162,8 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Aggregate
         fields = (
-            'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added', 'description', 'tags',
-            'created', 'last_updated',
+            'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added',
+            'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
 
@@ -278,6 +280,7 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
         accessor='get_utilization',
         orderable=False
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:prefix_list'
     )
@@ -285,8 +288,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Prefix
         fields = (
-            'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group', 'site',
-            'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group',
+            'site', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'comments', 'tags',
+            'created', 'last_updated',
         )
         default_columns = (
             'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
@@ -317,6 +321,7 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
         accessor='utilization',
         orderable=False
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:iprange_list'
     )
@@ -324,8 +329,8 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = IPRange
         fields = (
-            'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'description',
-            'utilization', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group',
+            'utilization', 'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
@@ -378,6 +383,7 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
         linkify=lambda record: record.assigned_object.get_absolute_url(),
         verbose_name='Assigned'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:ipaddress_list'
     )
@@ -385,8 +391,8 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = IPAddress
         fields = (
-            'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside', 'assigned', 'dns_name', 'description',
-            'tags', 'created', 'last_updated',
+            'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside',
+            'assigned', 'dns_name', 'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',

+ 6 - 2
netbox/ipam/tables/l2vpn.py

@@ -29,12 +29,16 @@ class L2VPNTable(TenancyColumnsMixin, NetBoxTable):
         template_code=L2VPN_TARGETS,
         orderable=False
     )
+    comments = columns.MarkdownColumn()
+    tags = columns.TagColumn(
+        url_name='ipam:prefix_list'
+    )
 
     class Meta(NetBoxTable.Meta):
         model = L2VPN
         fields = (
-            'pk', 'name', 'slug', 'identifier', 'type', 'description', 'import_targets', 'export_targets', 'tenant', 'tenant_group',
-            'actions',
+            'pk', 'name', 'slug', 'identifier', 'type', 'import_targets', 'export_targets', 'tenant', 'tenant_group',
+            'description', 'comments', 'tags', 'created', 'last_updated', 'actions',
         )
         default_columns = ('pk', 'name', 'identifier', 'type', 'description', 'actions')
 

+ 7 - 3
netbox/ipam/tables/services.py

@@ -17,13 +17,16 @@ class ServiceTemplateTable(NetBoxTable):
         accessor=tables.A('port_list'),
         order_by=tables.A('ports'),
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:servicetemplate_list'
     )
 
     class Meta(NetBoxTable.Meta):
         model = ServiceTemplate
-        fields = ('pk', 'id', 'name', 'protocol', 'ports', 'description', 'tags')
+        fields = (
+            'pk', 'id', 'name', 'protocol', 'ports', 'description', 'comments', 'tags', 'created', 'last_updated',
+        )
         default_columns = ('pk', 'name', 'protocol', 'ports', 'description')
 
 
@@ -39,6 +42,7 @@ class ServiceTable(NetBoxTable):
         accessor=tables.A('port_list'),
         order_by=tables.A('ports'),
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:service_list'
     )
@@ -46,7 +50,7 @@ class ServiceTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Service
         fields = (
-            'pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags', 'created',
-            'last_updated',
+            'pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
+            'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')

+ 2 - 1
netbox/ipam/tables/vlans.py

@@ -121,6 +121,7 @@ class VLANTable(TenancyColumnsMixin, NetBoxTable):
         orderable=False,
         verbose_name='Prefixes'
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:vlan_list'
     )
@@ -129,7 +130,7 @@ class VLANTable(TenancyColumnsMixin, NetBoxTable):
         model = VLAN
         fields = (
             'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'tenant_group', 'status', 'role',
-            'description', 'tags', 'l2vpn', 'created', 'last_updated',
+            'description', 'comments', 'tags', 'l2vpn', 'created', 'last_updated',
         )
         default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
         row_attrs = {

+ 7 - 3
netbox/ipam/tables/vrfs.py

@@ -38,6 +38,7 @@ class VRFTable(TenancyColumnsMixin, NetBoxTable):
         template_code=VRF_TARGETS,
         orderable=False
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:vrf_list'
     )
@@ -45,8 +46,8 @@ class VRFTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = VRF
         fields = (
-            'pk', 'id', 'name', 'rd', 'tenant', 'tenant_group', 'enforce_unique', 'description', 'import_targets', 'export_targets',
-            'tags', 'created', 'last_updated',
+            'pk', 'id', 'name', 'rd', 'tenant', 'tenant_group', 'enforce_unique', 'import_targets', 'export_targets',
+            'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
 
@@ -59,11 +60,14 @@ class RouteTargetTable(TenancyColumnsMixin, NetBoxTable):
     name = tables.Column(
         linkify=True
     )
+    comments = columns.MarkdownColumn()
     tags = columns.TagColumn(
         url_name='ipam:vrf_list'
     )
 
     class Meta(NetBoxTable.Meta):
         model = RouteTarget
-        fields = ('pk', 'id', 'name', 'tenant', 'tenant_group', 'description', 'tags', 'created', 'last_updated',)
+        fields = (
+            'pk', 'id', 'name', 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated',
+        )
         default_columns = ('pk', 'name', 'tenant', 'description')

+ 19 - 2
netbox/netbox/models/__init__.py

@@ -10,8 +10,9 @@ from netbox.models.features import *
 __all__ = (
     'ChangeLoggedModel',
     'NestedGroupModel',
-    'OrganizationalModel',
     'NetBoxModel',
+    'OrganizationalModel',
+    'PrimaryModel',
 )
 
 
@@ -58,7 +59,7 @@ class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, models.Model)
 
 class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
     """
-    Primary models represent real objects within the infrastructure being modeled.
+    Base model for most object types. Suitable for use by plugins.
     """
     objects = RestrictedQuerySet.as_manager()
 
@@ -66,6 +67,22 @@ class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
         abstract = True
 
 
+class PrimaryModel(NetBoxModel):
+    """
+    Primary models represent real objects within the infrastructure being modeled.
+    """
+    description = models.CharField(
+        max_length=200,
+        blank=True
+    )
+    comments = models.TextField(
+        blank=True
+    )
+
+    class Meta:
+        abstract = True
+
+
 class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
     """
     Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest

+ 4 - 0
netbox/templates/circuits/provider.html

@@ -33,6 +33,10 @@
                         <th scope="row">Account</th>
                         <td>{{ object.account|placeholder }}</td>
                     </tr>
+                    <tr>
+                        <th scope="row">Description</th>
+                        <td>{{ object.description|placeholder }}</td>
+                    </tr>
                     <tr>
                         <th scope="row">Circuits</th>
                         <td>

+ 5 - 0
netbox/templates/dcim/cable.html

@@ -32,6 +32,10 @@
               <th scope="row">Label</th>
               <td>{{ object.label|placeholder }}</td>
             </tr>
+            <tr>
+              <th scope="row">Description</th>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
             <tr>
               <th scope="row">Color</th>
               <td>
@@ -57,6 +61,7 @@
       </div>
       {% include 'inc/panels/custom_fields.html' %}
       {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">

+ 15 - 8
netbox/templates/dcim/cable_edit.html

@@ -80,6 +80,7 @@
               {% render_field form.tenant_group %}
               {% render_field form.tenant %}
               {% render_field form.label %}
+              {% render_field form.description %}
               {% render_field form.color %}
               <div class="row mb-3">
                 <label class="col-sm-3 col-form-label text-lg-end">{{ form.length.label }}</label>
@@ -92,16 +93,22 @@
                 <div class="invalid-feedback"></div>
               </div>
               {% render_field form.tags %}
-              {% if form.custom_fields %}
-                <div class="field-group">
-                  <div class="row mb-3">
-                    <h5 class="offset-sm-3">Custom Fields</h5>
-                  </div>
-                  {% render_custom_fields form %}
-                </div>
-              {% endif %}
             </div>
           </div>
+            <div class="card">
+              <h5 class="card-header text-center">Comments</h5>
+              <div class="card-body">
+              {% render_field form.comments %}
+              </div>
+            </div>
+          {% if form.custom_fields %}
+            <div class="card">
+              <h5 class="card-header offset-sm-3">Custom Fields</h5>
+              <div class="card-body">
+                {% render_custom_fields form %}
+              </div>
+            </div>
+          {% endif %}
         </div>
       </div>
       <div class="row my-3">

+ 5 - 1
netbox/templates/dcim/device.html

@@ -94,7 +94,11 @@
                             </td>
                         </tr>
                         <tr>
-                            <td>Airflow</td>
+                            <th scope="row">Description</th>
+                            <td>{{ object.description|placeholder }}</td>
+                        </tr>
+                        <tr>
+                            <th scope="row">Airflow</th>
                             <td>
                                 {{ object.get_airflow_display|placeholder }}
                             </td>

+ 1 - 0
netbox/templates/dcim/device_edit.html

@@ -10,6 +10,7 @@
       </div>
       {% render_field form.name %}
       {% render_field form.device_role %}
+      {% render_field form.description %}
       {% render_field form.tags %}
     </div>
     

+ 4 - 0
netbox/templates/dcim/devicetype.html

@@ -27,6 +27,10 @@
                             <td>Part Number</td>
                             <td>{{ object.part_number|placeholder }}</td>
                         </tr>
+                        <tr>
+                            <td>Description</td>
+                            <td>{{ object.description|placeholder }}</td>
+                        </tr>
                         <tr>
                             <td>Height (U)</td>
                             <td>{{ object.u_height|floatformat }}</td>

+ 4 - 0
netbox/templates/dcim/module.html

@@ -62,6 +62,10 @@
             <th scope="row">Module Type</th>
             <td>{{ object.module_type|linkify }}</td>
           </tr>
+          <tr>
+            <th scope="row">Description</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
           <tr>
             <th scope="row">Serial Number</th>
             <td class="font-monospace">{{ object.serial|placeholder }}</td>

+ 4 - 0
netbox/templates/dcim/moduletype.html

@@ -22,6 +22,10 @@
               <td>Part Number</td>
               <td>{{ object.part_number|placeholder }}</td>
             </tr>
+            <tr>
+              <th scope="row">Description</th>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
             <tr>
                 <td>Weight</td>
                 <td>

+ 4 - 0
netbox/templates/dcim/powerfeed.html

@@ -38,6 +38,10 @@
                         <th scope="row">Status</th>
                         <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
                     </tr>
+                    <tr>
+                        <th scope="row">Description</th>
+                        <td>{{ object.description|placeholder }}</td>
+                    </tr>
                     <tr>
                         <th scope="row">Connected Device</th>
                         <td>

+ 22 - 19
netbox/templates/dcim/powerpanel.html

@@ -14,26 +14,29 @@
 {% block content %}
 <div class="row">
 	<div class="col col-md-6">
-        <div class="card">
-            <h5 class="card-header">
-                Power Panel
-            </h5>
-            <div class="card-body">
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">Site</th>
-                        <td>{{ object.site|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Location</th>
-                        <td>{{ object.location|linkify|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-        </div>
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_left_page object %}
+    <div class="card">
+      <h5 class="card-header">Power Panel</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">Site</th>
+            <td>{{ object.site|linkify }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Location</th>
+            <td>{{ object.location|linkify|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Description</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
+        </table>
+      </div>
     </div>
+    {% include 'inc/panels/tags.html' %}
+    {% include 'inc/panels/comments.html' %}
+    {% plugin_left_page object %}
+  </div>
 	<div class="col col-md-6">
         {% include 'inc/panels/custom_fields.html' %}
         {% include 'inc/panels/contacts.html' %}

+ 4 - 0
netbox/templates/dcim/rack.html

@@ -78,6 +78,10 @@
                         <th scope="row">Role</th>
                         <td>{{ object.role|linkify|placeholder }}</td>
                     </tr>
+                    <tr>
+                        <th scope="row">Description</th>
+                        <td>{{ object.description|placeholder }}</td>
+                    </tr>
                     <tr>
                         <th scope="row">Serial Number</th>
                         <td class="font-monospace">{{ object.serial|placeholder }}</td>

+ 1 - 0
netbox/templates/dcim/rack_edit.html

@@ -13,6 +13,7 @@
         {% render_field form.name %}
         {% render_field form.status %}
         {% render_field form.role %}
+        {% render_field form.description %}
         {% render_field form.tags %}
     </div>
 

+ 1 - 0
netbox/templates/dcim/rackreservation.html

@@ -73,6 +73,7 @@
         </div>
         {% include 'inc/panels/custom_fields.html' %}
         {% include 'inc/panels/tags.html' %}
+        {% include 'inc/panels/comments.html' %}
         {% plugin_left_page object %}
 	</div>
     <div class="col col-12 col-xl-7">

+ 6 - 1
netbox/templates/dcim/virtualchassis.html

@@ -27,11 +27,15 @@
             <th scope="row">Master</th>
             <td>{{ object.master|linkify }}</td>
           </tr>
+          <tr>
+            <th scope="row">Description</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
         </table>
       </div>
     </div>
-    {% include 'inc/panels/custom_fields.html' %}
     {% include 'inc/panels/tags.html' %}
+    {% include 'inc/panels/custom_fields.html' %}
     {% plugin_left_page object %}
     </div>
     <div class="col col-md-8">
@@ -73,6 +77,7 @@
           </div>
         {% endif %}
       </div>
+      {% include 'inc/panels/comments.html' %}
       {% plugin_right_page object %}
 	</div>
 </div>

+ 7 - 1
netbox/templates/dcim/virtualchassis_edit.html

@@ -17,12 +17,18 @@
           </div>
           {% render_field vc_form.name %}
           {% render_field vc_form.domain %}
+          {% render_field vc_form.description %}
           {% render_field vc_form.master %}
           {% render_field vc_form.tags %}
         </div>
 
+        <div class="field-group my-5">
+          <h5 class="text-center">Comments</h5>
+          {% render_field vc_form.comments %}
+        </div>
+
         {% if vc_form.custom_fields %}
-          <div class="field-group my-5">
+          <div class="field-group mb-5">
             <div class="row mb-2">
               <h5 class="offset-sm-3">Custom Fields</h5>
             </div>

+ 1 - 0
netbox/templates/ipam/aggregate.html

@@ -51,6 +51,7 @@
     <div class="col col-md-6">
       {% include 'inc/panels/custom_fields.html' %}
       {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_right_page object %}
     </div>
   </div>

+ 1 - 0
netbox/templates/ipam/asn.html

@@ -67,6 +67,7 @@
     <div class="col col-md-6">
       {% include 'inc/panels/custom_fields.html' %}
       {% include 'inc/panels/tags.html' with tags=object.tags.all url='ipam:asn_list' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_right_page object %}
     </div>
   </div>

+ 1 - 0
netbox/templates/ipam/fhrpgroup.html

@@ -42,6 +42,7 @@
         </div>
       </div>
       {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">

+ 9 - 2
netbox/templates/ipam/fhrpgroup_edit.html

@@ -13,7 +13,7 @@
     {% render_field form.tags %}
   </div>
 
-  <div class="field-group my-5">
+  <div class="field-group mb-5">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Authentication</h5>
     </div>
@@ -22,7 +22,7 @@
   </div>
 
   {% if not form.instance.pk %}
-    <div class="field-group my-5">
+    <div class="field-group mb-5">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Virtual IP Address</h5>
       </div>
@@ -32,6 +32,13 @@
     </div>
   {% endif %}
 
+  <div class="field-group mb-5">
+    <div class="row mb-2">
+      <h5 class="offset-sm-3">Comments</h5>
+    </div>
+    {% render_field form.comments %}
+  </div>
+
   {% if form.custom_fields %}
     <div class="row mb-2">
       <h5 class="offset-sm-3">Custom Fields</h5>

+ 1 - 0
netbox/templates/ipam/ipaddress.html

@@ -108,6 +108,7 @@
       </div>
       {% include 'inc/panels/tags.html' %}
       {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_left_page object %}
 	</div>
 	<div class="col col-md-8">

+ 7 - 0
netbox/templates/ipam/ipaddress_edit.html

@@ -138,6 +138,13 @@
       </div>
     </div>
 
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="text-center">Comments</h5>
+      </div>
+      {% render_field form.comments %}
+    </div>
+
     {% if form.custom_fields %}
       <div class="field-group my-5">
         <div class="row mb-2">

+ 4 - 3
netbox/templates/ipam/iprange.html

@@ -70,9 +70,10 @@
         {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">
-        {% include 'inc/panels/tags.html' %}
-        {% include 'inc/panels/custom_fields.html' %}
-        {% plugin_right_page object %}
+      {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/comments.html' %}
+      {% plugin_right_page object %}
     </div>
 </div>
 <div class="row">

+ 1 - 0
netbox/templates/ipam/l2vpn.html

@@ -39,6 +39,7 @@
 	<div class="col col-md-6">
       {% include 'inc/panels/contacts.html' %}
       {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_right_page object %}
     </div>
 </div>

+ 1 - 0
netbox/templates/ipam/prefix.html

@@ -155,6 +155,7 @@
     </div>
     {% include 'inc/panels/custom_fields.html' %}
     {% include 'inc/panels/tags.html' %}
+    {% include 'inc/panels/comments.html' %}
     {% plugin_right_page object %}
   </div>
 </div>

+ 1 - 0
netbox/templates/ipam/routetarget.html

@@ -26,6 +26,7 @@
       </div>
       {% include 'inc/panels/tags.html' %}
       {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">

+ 4 - 3
netbox/templates/ipam/service.html

@@ -58,9 +58,10 @@
         {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_right_page object %}
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
+      {% plugin_right_page object %}
     </div>
 </div>
 <div class="row mb-3">

+ 7 - 0
netbox/templates/ipam/service_create.html

@@ -65,6 +65,13 @@
     {% render_field form.tags %}
   </div>
 
+  <div class="field-group my-5">
+    <div class="row mb-2">
+      <h5 class="text-center">Comments</h5>
+    </div>
+    {% render_field form.comments %}
+  </div>
+
   {% if form.custom_fields %}
     <div class="row mb-2">
       <h5 class="offset-sm-3">Custom Fields</h5>

+ 7 - 0
netbox/templates/ipam/service_edit.html

@@ -52,6 +52,13 @@
     {% render_field form.tags %}
   </div>
 
+  <div class="field-group my-5">
+    <div class="row mb-2">
+      <h5 class="text-center">Comments</h5>
+    </div>
+    {% render_field form.comments %}
+  </div>
+
   {% if form.custom_fields %}
     <div class="row mb-2">
       <h5 class="offset-sm-3">Custom Fields</h5>

+ 7 - 6
netbox/templates/ipam/servicetemplate.html

@@ -31,12 +31,13 @@
         </div>
       </div>
       {% plugin_left_page object %}
-      </div>
-      <div class="col col-md-6">
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_right_page object %}
-      </div>
+    </div>
+    <div class="col col-md-6">
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
+      {% plugin_right_page object %}
+    </div>
   </div>
   <div class="row mb-3">
     <div class="col col-md-12">

+ 4 - 3
netbox/templates/ipam/vlan.html

@@ -74,9 +74,10 @@
             {% plugin_left_page object %}
         </div>
         <div class="col col-md-6">
-            {% include 'inc/panels/custom_fields.html' %}
-            {% include 'inc/panels/tags.html' %}
-            {% plugin_right_page object %}
+          {% include 'inc/panels/custom_fields.html' %}
+          {% include 'inc/panels/tags.html' %}
+          {% include 'inc/panels/comments.html' %}
+          {% plugin_right_page object %}
         </div>
     </div>
     <div class="row">

+ 7 - 0
netbox/templates/ipam/vlan_edit.html

@@ -55,6 +55,13 @@
     {% endwith %}
   </div>
 
+  <div class="field-group my-5">
+    <div class="row mb-2">
+      <h5 class="text-center">Comments</h5>
+    </div>
+    {% render_field form.comments %}
+  </div>
+
   {% if form.custom_fields %}
     <div class="field-group my-5">
       <div class="row mb-2">

+ 1 - 0
netbox/templates/ipam/vrf.html

@@ -55,6 +55,7 @@
   <div class="col col-md-6">
     {% include 'inc/panels/tags.html' %}
     {% include 'inc/panels/custom_fields.html' %}
+    {% include 'inc/panels/comments.html' %}
     {% plugin_right_page object %}
 	</div>
 </div>

+ 4 - 0
netbox/templates/tenancy/contact.html

@@ -63,6 +63,10 @@
                 {% endif %}
               </td>
             </tr>
+            <tr>
+              <th scope="row">Description</th>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
             <tr>
               <th scope="row">Assignments</th>
               <td>{{ assignment_count }}</td>

+ 4 - 0
netbox/templates/virtualization/cluster.html

@@ -23,6 +23,10 @@
                   <th scope="row">Status</th>
                   <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
                 </tr>
+                <tr>
+                  <th scope="row">Description</th>
+                  <td>{{ object.description|placeholder }}</td>
+                </tr>
                 <tr>
                     <th scope="row">Group</th>
                     <td>{{ object.group|linkify|placeholder }}</td>

+ 4 - 0
netbox/templates/virtualization/virtualmachine.html

@@ -29,6 +29,10 @@
                         <th scope="row">Platform</th>
                         <td>{{ object.platform|linkify|placeholder }}</td>
                     </tr>
+                    <tr>
+                      <th scope="row">Description</th>
+                      <td>{{ object.description|placeholder }}</td>
+                    </tr>
                     <tr>
                         <th scope="row">Tenant</th>
                         <td>

+ 1 - 0
netbox/templates/wireless/wirelesslan.html

@@ -39,6 +39,7 @@
       </div>
     </div>
     {% include 'inc/panels/tags.html' %}
+    {% include 'inc/panels/comments.html' %}
     {% plugin_left_page object %}
   </div>
   <div class="col col-md-6">

+ 1 - 0
netbox/templates/wireless/wirelesslink.html

@@ -40,6 +40,7 @@
         </div>
       </div>
       {% include 'inc/panels/tags.html' %}
+      {% include 'inc/panels/comments.html' %}
       {% plugin_left_page object %}
     </div>
     <div class="col col-md-6">

+ 6 - 0
netbox/templates/wireless/wirelesslink_edit.html

@@ -22,6 +22,12 @@
       </div>
     </div>
   </div>
+  <div class="field-group my-5">
+    <div class="row mb-2">
+      <h5 class="offset-sm-3">Comments</h5>
+    </div>
+    {% render_field form.comments %}
+  </div>
   {% if form.custom_fields %}
     <div class="field-group my-5">
       <div class="row mb-2">

+ 2 - 2
netbox/tenancy/api/serializers.py

@@ -85,8 +85,8 @@ class ContactSerializer(NetBoxModelSerializer):
     class Meta:
         model = Contact
         fields = [
-            'id', 'url', 'display', 'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'comments', 'tags',
-            'custom_fields', 'created', 'last_updated',
+            'id', 'url', 'display', 'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description',
+            'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 

+ 11 - 3
netbox/tenancy/forms/bulk_edit.py

@@ -2,7 +2,7 @@ from django import forms
 
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import *
-from utilities.forms import DynamicModelChoiceField
+from utilities.forms import CommentField, DynamicModelChoiceField, SmallTextarea
 
 __all__ = (
     'ContactBulkEditForm',
@@ -101,9 +101,17 @@ class ContactBulkEditForm(NetBoxModelBulkEditForm):
     link = forms.URLField(
         required=False
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = Contact
     fieldsets = (
-        (None, ('group', 'title', 'phone', 'email', 'address', 'link')),
+        (None, ('group', 'title', 'phone', 'email', 'address', 'link', 'description')),
     )
-    nullable_fields = ('group', 'title', 'phone', 'email', 'address', 'link', 'comments')
+    nullable_fields = ('group', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments')

+ 1 - 1
netbox/tenancy/forms/bulk_import.py

@@ -79,4 +79,4 @@ class ContactCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = Contact
-        fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'comments')
+        fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments')

+ 2 - 2
netbox/tenancy/forms/model_forms.py

@@ -103,13 +103,13 @@ class ContactForm(NetBoxModelForm):
     comments = CommentField()
 
     fieldsets = (
-        ('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'link', 'tags')),
+        ('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'tags')),
     )
 
     class Meta:
         model = Contact
         fields = (
-            'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'comments', 'tags',
+            'group', 'name', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments', 'tags',
         )
         widgets = {
             'address': SmallTextarea(attrs={'rows': 3}),

+ 18 - 0
netbox/tenancy/migrations/0009_standardize_description_comments.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2022-11-03 18:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tenancy', '0008_unique_constraints'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='contact',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+    ]

+ 2 - 6
netbox/tenancy/models/contacts.py

@@ -2,9 +2,8 @@ from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.urls import reverse
-from mptt.models import TreeForeignKey
 
-from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, NetBoxModel
+from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
 from netbox.models.features import WebhooksMixin
 from tenancy.choices import *
 
@@ -41,7 +40,7 @@ class ContactRole(OrganizationalModel):
         return reverse('tenancy:contactrole', args=[self.pk])
 
 
-class Contact(NetBoxModel):
+class Contact(PrimaryModel):
     """
     Contact information for a particular object(s) in NetBox.
     """
@@ -73,9 +72,6 @@ class Contact(NetBoxModel):
     link = models.URLField(
         blank=True
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     clone_fields = (
         'group', 'name', 'title', 'phone', 'email', 'address', 'link',

+ 2 - 10
netbox/tenancy/models/tenants.py

@@ -1,9 +1,8 @@
 from django.contrib.contenttypes.fields import GenericRelation
 from django.db import models
 from django.urls import reverse
-from mptt.models import TreeForeignKey
 
-from netbox.models import NestedGroupModel, NetBoxModel
+from netbox.models import NestedGroupModel, PrimaryModel
 
 __all__ = (
     'Tenant',
@@ -31,7 +30,7 @@ class TenantGroup(NestedGroupModel):
         return reverse('tenancy:tenantgroup', args=[self.pk])
 
 
-class Tenant(NetBoxModel):
+class Tenant(PrimaryModel):
     """
     A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal
     department.
@@ -51,13 +50,6 @@ class Tenant(NetBoxModel):
         blank=True,
         null=True
     )
-    description = models.CharField(
-        max_length=200,
-        blank=True
-    )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     contacts = GenericRelation(

+ 2 - 2
netbox/tenancy/tables/contacts.py

@@ -65,8 +65,8 @@ class ContactTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Contact
         fields = (
-            'pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'link', 'comments', 'assignment_count', 'tags',
-            'created', 'last_updated',
+            'pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'link', 'description', 'comments',
+            'assignment_count', 'tags', 'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'group', 'assignment_count', 'title', 'phone', 'email')
 

+ 4 - 4
netbox/virtualization/api/serializers.py

@@ -58,8 +58,8 @@ class ClusterSerializer(NetBoxModelSerializer):
     class Meta:
         model = Cluster
         fields = [
-            'id', 'url', 'display', 'name', 'type', 'group', 'status', 'tenant', 'site', 'comments', 'tags',
-            'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
+            'id', 'url', 'display', 'name', 'type', 'group', 'status', 'tenant', 'site', 'description', 'comments',
+            'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
         ]
 
 
@@ -84,8 +84,8 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
         model = VirtualMachine
         fields = [
             'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform',
-            'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data',
-            'tags', 'custom_fields', 'created', 'last_updated',
+            'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments',
+            'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
         validators = []
 

+ 13 - 5
netbox/virtualization/forms/bulk_edit.py

@@ -84,6 +84,10 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
             'group_id': '$site_group',
         }
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
     comments = CommentField(
         widget=SmallTextarea,
         label='Comments'
@@ -91,11 +95,11 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
 
     model = Cluster
     fieldsets = (
-        (None, ('type', 'group', 'status', 'tenant',)),
-        ('Site', ('region', 'site_group', 'site',)),
+        (None, ('type', 'group', 'status', 'tenant', 'description')),
+        ('Site', ('region', 'site_group', 'site')),
     )
     nullable_fields = (
-        'group', 'site', 'comments', 'tenant',
+        'group', 'site', 'tenant', 'description', 'comments',
     )
 
 
@@ -153,6 +157,10 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Disk (GB)'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
     comments = CommentField(
         widget=SmallTextarea,
         label='Comments'
@@ -160,11 +168,11 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
 
     model = VirtualMachine
     fieldsets = (
-        (None, ('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform')),
+        (None, ('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform', 'description')),
         ('Resources', ('vcpus', 'memory', 'disk'))
     )
     nullable_fields = (
-        'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
+        'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'description', 'comments',
     )
 
 

+ 2 - 2
netbox/virtualization/forms/bulk_import.py

@@ -63,7 +63,7 @@ class ClusterCSVForm(NetBoxModelCSVForm):
 
     class Meta:
         model = Cluster
-        fields = ('name', 'type', 'group', 'status', 'site', 'comments')
+        fields = ('name', 'type', 'group', 'status', 'site', 'description', 'comments')
 
 
 class VirtualMachineCSVForm(NetBoxModelCSVForm):
@@ -114,7 +114,7 @@ class VirtualMachineCSVForm(NetBoxModelCSVForm):
         model = VirtualMachine
         fields = (
             'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
-            'comments',
+            'description', 'comments',
         )
 
 

+ 6 - 4
netbox/virtualization/forms/model_forms.py

@@ -90,7 +90,7 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
     comments = CommentField()
 
     fieldsets = (
-        ('Cluster', ('name', 'type', 'group', 'status', 'tags')),
+        ('Cluster', ('name', 'type', 'group', 'status', 'description', 'tags')),
         ('Site', ('region', 'site_group', 'site')),
         ('Tenancy', ('tenant_group', 'tenant')),
     )
@@ -98,7 +98,8 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
     class Meta:
         model = Cluster
         fields = (
-            'name', 'type', 'group', 'status', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
+            'name', 'type', 'group', 'status', 'tenant', 'region', 'site_group', 'site', 'description', 'comments',
+            'tags',
         )
         widgets = {
             'status': StaticSelect(),
@@ -220,9 +221,10 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         required=False,
         label=''
     )
+    comments = CommentField()
 
     fieldsets = (
-        ('Virtual Machine', ('name', 'role', 'status', 'tags')),
+        ('Virtual Machine', ('name', 'role', 'status', 'description', 'tags')),
         ('Site/Cluster', ('site', 'cluster_group', 'cluster', 'device')),
         ('Tenancy', ('tenant_group', 'tenant')),
         ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
@@ -234,7 +236,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
         model = VirtualMachine
         fields = [
             'name', 'status', 'site', 'cluster_group', 'cluster', 'device', 'role', 'tenant_group', 'tenant',
-            'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags',
+            'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'tags',
             'local_context_data',
         ]
         help_texts = {

+ 23 - 0
netbox/virtualization/migrations/0034_standardize_description_comments.py

@@ -0,0 +1,23 @@
+# Generated by Django 4.1.2 on 2022-11-03 18:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('virtualization', '0033_unique_constraints'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='cluster',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+        migrations.AddField(
+            model_name='virtualmachine',
+            name='description',
+            field=models.CharField(blank=True, max_length=200),
+        ),
+    ]

+ 3 - 9
netbox/virtualization/models.py

@@ -10,7 +10,7 @@ from dcim.models import BaseInterface, Device
 from extras.models import ConfigContextModel
 from extras.querysets import ConfigContextModelQuerySet
 from netbox.config import get_config
-from netbox.models import OrganizationalModel, NetBoxModel
+from netbox.models import NetBoxModel, OrganizationalModel, PrimaryModel
 from utilities.fields import NaturalOrderingField
 from utilities.ordering import naturalize_interface
 from utilities.query_functions import CollateAsChar
@@ -64,7 +64,7 @@ class ClusterGroup(OrganizationalModel):
 # Clusters
 #
 
-class Cluster(NetBoxModel):
+class Cluster(PrimaryModel):
     """
     A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
     """
@@ -102,9 +102,6 @@ class Cluster(NetBoxModel):
         blank=True,
         null=True
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relations
     vlan_groups = GenericRelation(
@@ -165,7 +162,7 @@ class Cluster(NetBoxModel):
 # Virtual machines
 #
 
-class VirtualMachine(NetBoxModel, ConfigContextModel):
+class VirtualMachine(PrimaryModel, ConfigContextModel):
     """
     A virtual machine which runs inside a Cluster.
     """
@@ -262,9 +259,6 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
         null=True,
         verbose_name='Disk (GB)'
     )
-    comments = models.TextField(
-        blank=True
-    )
 
     # Generic relation
     contacts = GenericRelation(

+ 2 - 2
netbox/virtualization/tables/clusters.py

@@ -86,7 +86,7 @@ class ClusterTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Cluster
         fields = (
-            'pk', 'id', 'name', 'type', 'group', 'status', 'tenant', 'tenant_group', 'site', 'comments', 'device_count',
-            'vm_count', 'contacts', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'name', 'type', 'group', 'status', 'tenant', 'tenant_group', 'site', 'description', 'comments',
+            'device_count', 'vm_count', 'contacts', 'tags', 'created', 'last_updated',
         )
         default_columns = ('pk', 'name', 'type', 'group', 'status', 'tenant', 'site', 'device_count', 'vm_count')

+ 2 - 2
netbox/virtualization/tables/virtualmachines.py

@@ -75,8 +75,8 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
         model = VirtualMachine
         fields = (
             'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'platform',
-            'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'contacts', 'tags',
-            'created', 'last_updated',
+            'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments',
+            'contacts', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',

+ 2 - 2
netbox/wireless/api/serializers.py

@@ -42,7 +42,7 @@ class WirelessLANSerializer(NetBoxModelSerializer):
         model = WirelessLAN
         fields = [
             'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher',
-            'auth_psk', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
+            'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
 
 
@@ -59,5 +59,5 @@ class WirelessLinkSerializer(NetBoxModelSerializer):
         model = WirelessLink
         fields = [
             'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'tenant', 'auth_type',
-            'auth_cipher', 'auth_psk', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
+            'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
         ]

+ 19 - 9
netbox/wireless/forms/bulk_edit.py

@@ -4,7 +4,7 @@ from dcim.choices import LinkStatusChoices
 from ipam.models import VLAN
 from netbox.forms import NetBoxModelBulkEditForm
 from tenancy.models import Tenant
-from utilities.forms import add_blank_choice, DynamicModelChoiceField
+from utilities.forms import add_blank_choice, CommentField, DynamicModelChoiceField, SmallTextarea
 from wireless.choices import *
 from wireless.constants import SSID_MAX_LENGTH
 from wireless.models import *
@@ -52,9 +52,6 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         queryset=Tenant.objects.all(),
         required=False
     )
-    description = forms.CharField(
-        required=False
-    )
     auth_type = forms.ChoiceField(
         choices=add_blank_choice(WirelessAuthTypeChoices),
         required=False
@@ -67,6 +64,14 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Pre-shared key'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = WirelessLAN
     fieldsets = (
@@ -74,7 +79,7 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     nullable_fields = (
-        'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+        'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
     )
 
 
@@ -92,9 +97,6 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         queryset=Tenant.objects.all(),
         required=False
     )
-    description = forms.CharField(
-        required=False
-    )
     auth_type = forms.ChoiceField(
         choices=add_blank_choice(WirelessAuthTypeChoices),
         required=False
@@ -107,6 +109,14 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         label='Pre-shared key'
     )
+    description = forms.CharField(
+        max_length=200,
+        required=False
+    )
+    comments = CommentField(
+        widget=SmallTextarea,
+        label='Comments'
+    )
 
     model = WirelessLink
     fieldsets = (
@@ -114,5 +124,5 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk'))
     )
     nullable_fields = (
-        'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+        'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
     )

Некоторые файлы не были показаны из-за большого количества измененных файлов