Browse Source

Standardized declaration of csv_headers on models

Jeremy Stretch 7 years ago
parent
commit
12e6fe1d50

+ 2 - 2
netbox/circuits/forms.py

@@ -43,7 +43,7 @@ class ProviderCSVForm(forms.ModelForm):
 
     class Meta:
         model = Provider
-        fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'comments']
+        fields = Provider.csv_headers
         help_texts = {
             'name': 'Provider name',
             'asn': '32-bit autonomous system number',
@@ -89,7 +89,7 @@ class CircuitTypeCSVForm(forms.ModelForm):
 
     class Meta:
         model = CircuitType
-        fields = ['name', 'slug']
+        fields = CircuitType.csv_headers
         help_texts = {
             'name': 'Name of circuit type',
         }

+ 14 - 2
netbox/circuits/models.py

@@ -28,7 +28,7 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
     comments = models.TextField(blank=True)
     custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
 
-    csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url']
+    csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
 
     class Meta:
         ordering = ['name']
@@ -46,6 +46,9 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
             self.asn,
             self.account,
             self.portal_url,
+            self.noc_contact,
+            self.admin_contact,
+            self.comments,
         )
 
 
@@ -58,6 +61,8 @@ class CircuitType(models.Model):
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
 
+    csv_headers = ['name', 'slug']
+
     class Meta:
         ordering = ['name']
 
@@ -67,6 +72,12 @@ class CircuitType(models.Model):
     def get_absolute_url(self):
         return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+        )
+
 
 @python_2_unicode_compatible
 class Circuit(CreatedUpdatedModel, CustomFieldModel):
@@ -85,7 +96,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
     comments = models.TextField(blank=True)
     custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
 
-    csv_headers = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description']
+    csv_headers = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments']
 
     class Meta:
         ordering = ['provider', 'cid']
@@ -106,6 +117,7 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
             self.install_date,
             self.commit_rate,
             self.description,
+            self.comments,
         )
 
     def _get_termination(self, side):

+ 13 - 26
netbox/dcim/forms.py

@@ -72,9 +72,7 @@ class RegionCSVForm(forms.ModelForm):
 
     class Meta:
         model = Region
-        fields = [
-            'name', 'slug', 'parent',
-        ]
+        fields = Region.csv_headers
         help_texts = {
             'name': 'Region name',
             'slug': 'URL-friendly slug',
@@ -136,10 +134,7 @@ class SiteCSVForm(forms.ModelForm):
 
     class Meta:
         model = Site
-        fields = [
-            'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
-            'contact_name', 'contact_phone', 'contact_email', 'comments',
-        ]
+        fields = Site.csv_headers
         help_texts = {
             'name': 'Site name',
             'slug': 'URL-friendly slug',
@@ -196,9 +191,7 @@ class RackGroupCSVForm(forms.ModelForm):
 
     class Meta:
         model = RackGroup
-        fields = [
-            'site', 'name', 'slug',
-        ]
+        fields = RackGroup.csv_headers
         help_texts = {
             'name': 'Name of rack group',
             'slug': 'URL-friendly slug',
@@ -226,7 +219,7 @@ class RackRoleCSVForm(forms.ModelForm):
 
     class Meta:
         model = RackRole
-        fields = ['name', 'slug', 'color']
+        fields = RackRole.csv_headers
         help_texts = {
             'name': 'Name of rack role',
             'color': 'RGB color in hexadecimal (e.g. 00ff00)'
@@ -313,10 +306,7 @@ class RackCSVForm(forms.ModelForm):
 
     class Meta:
         model = Rack
-        fields = [
-            'site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'serial', 'type', 'width', 'u_height',
-            'desc_units',
-        ]
+        fields = Rack.csv_headers
         help_texts = {
             'name': 'Rack name',
             'u_height': 'Height in rack units',
@@ -444,9 +434,7 @@ class ManufacturerForm(BootstrapMixin, forms.ModelForm):
 class ManufacturerCSVForm(forms.ModelForm):
     class Meta:
         model = Manufacturer
-        fields = [
-            'name', 'slug'
-        ]
+        fields = Manufacturer.csv_headers
         help_texts = {
             'name': 'Manufacturer name',
             'slug': 'URL-friendly slug',
@@ -492,8 +480,7 @@ class DeviceTypeCSVForm(forms.ModelForm):
 
     class Meta:
         model = DeviceType
-        fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
-                  'is_pdu', 'is_network_device', 'subdevice_role', 'interface_ordering', 'comments']
+        fields = DeviceType.csv_headers
         help_texts = {
             'model': 'Model name',
             'slug': 'URL-friendly slug',
@@ -658,7 +645,7 @@ class DeviceRoleCSVForm(forms.ModelForm):
 
     class Meta:
         model = DeviceRole
-        fields = ['name', 'slug', 'color', 'vm_role']
+        fields = DeviceRole.csv_headers
         help_texts = {
             'name': 'Name of device role',
             'color': 'RGB color in hexadecimal (e.g. 00ff00)'
@@ -682,7 +669,7 @@ class PlatformCSVForm(forms.ModelForm):
 
     class Meta:
         model = Platform
-        fields = ['name', 'slug', 'napalm_driver']
+        fields = Platform.csv_headers
         help_texts = {
             'name': 'Platform name',
         }
@@ -932,7 +919,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
     class Meta(BaseDeviceCSVForm.Meta):
         fields = [
             'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
-            'site', 'rack_group', 'rack_name', 'position', 'face', 'cluster',
+            'site', 'rack_group', 'rack_name', 'position', 'face', 'cluster', 'comments',
         ]
 
     def clean(self):
@@ -981,7 +968,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
     class Meta(BaseDeviceCSVForm.Meta):
         fields = [
             'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
-            'parent', 'device_bay_name', 'cluster',
+            'parent', 'device_bay_name', 'cluster', 'comments',
         ]
 
     def clean(self):
@@ -1808,7 +1795,7 @@ class InterfaceConnectionCSVForm(forms.ModelForm):
 
     class Meta:
         model = InterfaceConnection
-        fields = ['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status']
+        fields = InterfaceConnection.csv_headers
 
     def clean_interface_a(self):
 
@@ -1951,7 +1938,7 @@ class InventoryItemCSVForm(forms.ModelForm):
 
     class Meta:
         model = InventoryItem
-        fields = ['device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description']
+        fields = InventoryItem.csv_headers
 
 
 class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):

+ 45 - 18
netbox/dcim/models.py

@@ -42,9 +42,7 @@ class Region(MPTTModel):
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
 
-    csv_headers = [
-        'name', 'slug', 'parent',
-    ]
+    csv_headers = ['name', 'slug', 'parent']
 
     class MPTTMeta:
         order_insertion_by = ['name']
@@ -97,7 +95,8 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
     objects = SiteManager()
 
     csv_headers = [
-        'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email',
+        'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'contact_name',
+        'contact_phone', 'contact_email', 'comments',
     ]
 
     class Meta:
@@ -117,9 +116,12 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
             self.tenant.name if self.tenant else None,
             self.facility,
             self.asn,
+            self.physical_address,
+            self.shipping_address,
             self.contact_name,
             self.contact_phone,
             self.contact_email,
+            self.comments,
         )
 
     @property
@@ -163,9 +165,7 @@ class RackGroup(models.Model):
     slug = models.SlugField()
     site = models.ForeignKey('Site', related_name='rack_groups', on_delete=models.CASCADE)
 
-    csv_headers = [
-        'site', 'name', 'slug',
-    ]
+    csv_headers = ['site', 'name', 'slug']
 
     class Meta:
         ordering = ['site', 'name']
@@ -197,6 +197,8 @@ class RackRole(models.Model):
     slug = models.SlugField(unique=True)
     color = ColorField()
 
+    csv_headers = ['name', 'slug', 'color']
+
     class Meta:
         ordering = ['name']
 
@@ -206,6 +208,13 @@ class RackRole(models.Model):
     def get_absolute_url(self):
         return "{}?role={}".format(reverse('dcim:rack_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.color,
+        )
+
 
 class RackManager(NaturalOrderByManager):
 
@@ -241,7 +250,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
 
     csv_headers = [
         'site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'type', 'serial', 'width', 'u_height',
-        'desc_units',
+        'desc_units', 'comments',
     ]
 
     class Meta:
@@ -303,6 +312,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
             self.width,
             self.u_height,
             self.desc_units,
+            self.comments,
         )
 
     @property
@@ -478,9 +488,7 @@ class Manufacturer(models.Model):
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
 
-    csv_headers = [
-        'name', 'slug',
-    ]
+    csv_headers = ['name', 'slug']
 
     class Meta:
         ordering = ['name']
@@ -538,7 +546,7 @@ class DeviceType(models.Model, CustomFieldModel):
 
     csv_headers = [
         'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
-        'is_pdu', 'is_network_device', 'subdevice_role', 'interface_ordering',
+        'is_pdu', 'is_network_device', 'subdevice_role', 'interface_ordering', 'comments',
     ]
 
     class Meta:
@@ -573,6 +581,7 @@ class DeviceType(models.Model, CustomFieldModel):
             self.is_network_device,
             self.get_subdevice_role_display() if self.subdevice_role else None,
             self.get_interface_ordering_display(),
+            self.comments,
         )
 
     def clean(self):
@@ -753,6 +762,8 @@ class DeviceRole(models.Model):
         help_text="Virtual machines may be assigned to this role"
     )
 
+    csv_headers = ['name', 'slug', 'color', 'vm_role']
+
     class Meta:
         ordering = ['name']
 
@@ -762,6 +773,14 @@ class DeviceRole(models.Model):
     def get_absolute_url(self):
         return "{}?role={}".format(reverse('dcim:device_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.color,
+            self.vm_role,
+        )
+
 
 @python_2_unicode_compatible
 class Platform(models.Model):
@@ -777,6 +796,8 @@ class Platform(models.Model):
     rpc_client = models.CharField(max_length=30, choices=RPC_CLIENT_CHOICES, blank=True,
                                   verbose_name='Legacy RPC client')
 
+    csv_headers = ['name', 'slug', 'napalm_driver']
+
     class Meta:
         ordering = ['name']
 
@@ -786,6 +807,13 @@ class Platform(models.Model):
     def get_absolute_url(self):
         return "{}?platform={}".format(reverse('dcim:device_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.napalm_driver,
+        )
+
 
 class DeviceManager(NaturalOrderByManager):
 
@@ -847,7 +875,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
 
     csv_headers = [
         'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
-        'site', 'rack_group', 'rack_name', 'position', 'face',
+        'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
     ]
 
     class Meta:
@@ -1003,6 +1031,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
             self.rack.name if self.rack else None,
             self.position,
             self.get_face_display(),
+            self.comments,
         )
 
     @property
@@ -1075,7 +1104,6 @@ class ConsolePort(models.Model):
     def __str__(self):
         return self.name
 
-    # Used for connections export
     def to_csv(self):
         return (
             self.cs_port.device.identifier if self.cs_port else None,
@@ -1152,7 +1180,6 @@ class PowerPort(models.Model):
     def __str__(self):
         return self.name
 
-    # Used for connections export
     def to_csv(self):
         return (
             self.power_outlet.device.identifier if self.power_outlet else None,
@@ -1381,7 +1408,6 @@ class InterfaceConnection(models.Model):
         except ObjectDoesNotExist:
             pass
 
-    # Used for connections export
     def to_csv(self):
         return (
             self.interface_a.device.identifier,
@@ -1452,7 +1478,7 @@ class InventoryItem(models.Model):
     description = models.CharField(max_length=100, blank=True)
 
     csv_headers = [
-        'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
+        'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
     ]
 
     class Meta:
@@ -1470,5 +1496,6 @@ class InventoryItem(models.Model):
             self.part_id,
             self.serial,
             self.asset_tag,
-            self.description
+            self.discovered,
+            self.description,
         )

+ 8 - 13
netbox/ipam/forms.py

@@ -57,7 +57,7 @@ class VRFCSVForm(forms.ModelForm):
 
     class Meta:
         model = VRF
-        fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
+        fields = VRF.csv_headers
         help_texts = {
             'name': 'VRF name',
         }
@@ -102,7 +102,7 @@ class RIRCSVForm(forms.ModelForm):
 
     class Meta:
         model = RIR
-        fields = ['name', 'slug', 'is_private']
+        fields = RIR.csv_headers
         help_texts = {
             'name': 'RIR name',
         }
@@ -144,7 +144,7 @@ class AggregateCSVForm(forms.ModelForm):
 
     class Meta:
         model = Aggregate
-        fields = ['prefix', 'rir', 'date_added', 'description']
+        fields = Aggregate.csv_headers
 
 
 class AggregateBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
@@ -185,7 +185,7 @@ class RoleCSVForm(forms.ModelForm):
 
     class Meta:
         model = Role
-        fields = ['name', 'slug']
+        fields = Role.csv_headers
         help_texts = {
             'name': 'Role name',
         }
@@ -299,9 +299,7 @@ class PrefixCSVForm(forms.ModelForm):
 
     class Meta:
         model = Prefix
-        fields = [
-            'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
-        ]
+        fields = Prefix.csv_headers
 
     def clean(self):
 
@@ -609,10 +607,7 @@ class IPAddressCSVForm(forms.ModelForm):
 
     class Meta:
         model = IPAddress
-        fields = [
-            'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary',
-            'description',
-        ]
+        fields = IPAddress.csv_headers
 
     def clean(self):
 
@@ -759,7 +754,7 @@ class VLANGroupCSVForm(forms.ModelForm):
 
     class Meta:
         model = VLANGroup
-        fields = ['site', 'name', 'slug']
+        fields = VLANGroup.csv_headers
         help_texts = {
             'name': 'Name of VLAN group',
         }
@@ -849,7 +844,7 @@ class VLANCSVForm(forms.ModelForm):
 
     class Meta:
         model = VLAN
-        fields = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
+        fields = VLAN.csv_headers
         help_texts = {
             'vid': 'Numeric VLAN ID (1-4095)',
             'name': 'VLAN name',

+ 27 - 0
netbox/ipam/models.py

@@ -74,6 +74,8 @@ class RIR(models.Model):
     is_private = models.BooleanField(default=False, verbose_name='Private',
                                      help_text='IP space managed by this RIR is considered private')
 
+    csv_headers = ['name', 'slug', 'is_private']
+
     class Meta:
         ordering = ['name']
         verbose_name = 'RIR'
@@ -85,6 +87,13 @@ class RIR(models.Model):
     def get_absolute_url(self):
         return "{}?rir={}".format(reverse('ipam:aggregate_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.is_private,
+        )
+
 
 @python_2_unicode_compatible
 class Aggregate(CreatedUpdatedModel, CustomFieldModel):
@@ -172,12 +181,21 @@ class Role(models.Model):
     slug = models.SlugField(unique=True)
     weight = models.PositiveSmallIntegerField(default=1000)
 
+    csv_headers = ['name', 'slug', 'weight']
+
     class Meta:
         ordering = ['weight', 'name']
 
     def __str__(self):
         return self.name
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.weight,
+        )
+
     @property
     def count_prefixes(self):
         return self.prefixes.count()
@@ -501,6 +519,8 @@ class VLANGroup(models.Model):
     slug = models.SlugField()
     site = models.ForeignKey('dcim.Site', related_name='vlan_groups', on_delete=models.PROTECT, blank=True, null=True)
 
+    csv_headers = ['name', 'slug', 'site']
+
     class Meta:
         ordering = ['site', 'name']
         unique_together = [
@@ -516,6 +536,13 @@ class VLANGroup(models.Model):
     def get_absolute_url(self):
         return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+            self.site.name if self.site else None,
+        )
+
     def get_next_available_vid(self):
         """
         Return the first available VLAN ID (1-4094) in the group.

+ 2 - 2
netbox/secrets/forms.py

@@ -47,7 +47,7 @@ class SecretRoleCSVForm(forms.ModelForm):
 
     class Meta:
         model = SecretRole
-        fields = ['name', 'slug']
+        fields = SecretRole.csv_headers
         help_texts = {
             'name': 'Name of secret role',
         }
@@ -98,7 +98,7 @@ class SecretCSVForm(forms.ModelForm):
 
     class Meta:
         model = Secret
-        fields = ['device', 'role', 'name', 'plaintext']
+        fields = Secret.csv_headers
         help_texts = {
             'name': 'Name or username',
         }

+ 8 - 0
netbox/secrets/models.py

@@ -239,6 +239,8 @@ class SecretRole(models.Model):
     users = models.ManyToManyField(User, related_name='secretroles', blank=True)
     groups = models.ManyToManyField(Group, related_name='secretroles', blank=True)
 
+    csv_headers = ['name', 'slug']
+
     class Meta:
         ordering = ['name']
 
@@ -248,6 +250,12 @@ class SecretRole(models.Model):
     def get_absolute_url(self):
         return "{}?role={}".format(reverse('secrets:secret_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+        )
+
     def has_member(self, user):
         """
         Check whether the given user has belongs to this SecretRole. Note that superusers belong to all roles.

+ 2 - 2
netbox/tenancy/forms.py

@@ -27,7 +27,7 @@ class TenantGroupCSVForm(forms.ModelForm):
 
     class Meta:
         model = TenantGroup
-        fields = ['name', 'slug']
+        fields = TenantGroup.csv_headers
         help_texts = {
             'name': 'Group name',
         }
@@ -60,7 +60,7 @@ class TenantCSVForm(forms.ModelForm):
 
     class Meta:
         model = Tenant
-        fields = ['name', 'slug', 'group', 'description', 'comments']
+        fields = Tenant.csv_headers
         help_texts = {
             'name': 'Tenant name',
             'comments': 'Free-form comments'

+ 9 - 0
netbox/tenancy/models.py

@@ -17,6 +17,8 @@ class TenantGroup(models.Model):
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
 
+    csv_headers = ['name', 'slug']
+
     class Meta:
         ordering = ['name']
 
@@ -26,6 +28,12 @@ class TenantGroup(models.Model):
     def get_absolute_url(self):
         return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+        )
+
 
 @python_2_unicode_compatible
 class Tenant(CreatedUpdatedModel, CustomFieldModel):
@@ -57,4 +65,5 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
             self.slug,
             self.group.name if self.group else None,
             self.description,
+            self.comments,
         )

+ 4 - 4
netbox/virtualization/forms.py

@@ -41,7 +41,7 @@ class ClusterTypeCSVForm(forms.ModelForm):
 
     class Meta:
         model = ClusterType
-        fields = ['name', 'slug']
+        fields = ClusterType.csv_headers
         help_texts = {
             'name': 'Name of cluster type',
         }
@@ -64,7 +64,7 @@ class ClusterGroupCSVForm(forms.ModelForm):
 
     class Meta:
         model = ClusterGroup
-        fields = ['name', 'slug']
+        fields = ClusterGroup.csv_headers
         help_texts = {
             'name': 'Name of cluster group',
         }
@@ -112,7 +112,7 @@ class ClusterCSVForm(forms.ModelForm):
 
     class Meta:
         model = Cluster
-        fields = ['name', 'type', 'group', 'site', 'comments']
+        fields = Cluster.csv_headers
 
 
 class ClusterBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
@@ -306,7 +306,7 @@ class VirtualMachineCSVForm(forms.ModelForm):
 
     class Meta:
         model = VirtualMachine
-        fields = ['name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
+        fields = VirtualMachine.csv_headers
 
 
 class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):

+ 19 - 4
netbox/virtualization/models.py

@@ -30,6 +30,8 @@ class ClusterType(models.Model):
         unique=True
     )
 
+    csv_headers = ['name', 'slug']
+
     class Meta:
         ordering = ['name']
 
@@ -39,6 +41,12 @@ class ClusterType(models.Model):
     def get_absolute_url(self):
         return "{}?type={}".format(reverse('virtualization:cluster_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+        )
+
 
 #
 # Cluster groups
@@ -57,6 +65,8 @@ class ClusterGroup(models.Model):
         unique=True
     )
 
+    csv_headers = ['name', 'slug']
+
     class Meta:
         ordering = ['name']
 
@@ -66,6 +76,12 @@ class ClusterGroup(models.Model):
     def get_absolute_url(self):
         return "{}?group={}".format(reverse('virtualization:cluster_list'), self.slug)
 
+    def to_csv(self):
+        return (
+            self.name,
+            self.slug,
+        )
+
 
 #
 # Clusters
@@ -108,9 +124,7 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel):
         object_id_field='obj_id'
     )
 
-    csv_headers = [
-        'name', 'type', 'group', 'site', 'comments',
-    ]
+    csv_headers = ['name', 'type', 'group', 'site', 'comments']
 
     class Meta:
         ordering = ['name']
@@ -229,7 +243,7 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
     )
 
     csv_headers = [
-        'name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
+        'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
     ]
 
     class Meta:
@@ -245,6 +259,7 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
         return (
             self.name,
             self.get_status_display(),
+            self.role.name if self.role else None,
             self.cluster.name,
             self.tenant.name if self.tenant else None,
             self.platform.name if self.platform else None,