1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Fixes #764: Encapsulate in double quotes values containing commas when exporting to CSV

This commit is contained in:
Jeremy Stretch
2017-01-04 10:47:00 -05:00
parent 8154ae3685
commit 52567c4ade
5 changed files with 80 additions and 61 deletions

View File

@ -5,6 +5,7 @@ from django.db import models
from dcim.fields import ASNField from dcim.fields import ASNField
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel, CustomFieldValue
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.utils import csv_format
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
@ -57,10 +58,10 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
return reverse('circuits:provider', args=[self.slug]) return reverse('circuits:provider', args=[self.slug])
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.name, self.name,
self.slug, self.slug,
str(self.asn) if self.asn else '', self.asn,
self.account, self.account,
self.portal_url, self.portal_url,
]) ])
@ -68,7 +69,7 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
class CircuitType(models.Model): class CircuitType(models.Model):
""" """
Circuits can be orgnanized by their functional role. For example, a user might wish to define CircuitTypes named Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
"Long Haul," "Metro," or "Out-of-Band". "Long Haul," "Metro," or "Out-of-Band".
""" """
name = models.CharField(max_length=50, unique=True) name = models.CharField(max_length=50, unique=True)
@ -110,13 +111,13 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
return reverse('circuits:circuit', args=[self.pk]) return reverse('circuits:circuit', args=[self.pk])
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.cid, self.cid,
self.provider.name, self.provider.name,
self.type.name, self.type.name,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.install_date.isoformat() if self.install_date else '', self.install_date.isoformat() if self.install_date else None,
str(self.commit_rate) if self.commit_rate else '', self.commit_rate,
]) ])
def _get_termination(self, side): def _get_termination(self, side):

View File

@ -16,6 +16,7 @@ from tenancy.models import Tenant
from utilities.fields import ColorField, NullableCharField from utilities.fields import ColorField, NullableCharField
from utilities.managers import NaturalOrderByManager from utilities.managers import NaturalOrderByManager
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from utilities.utils import csv_format
from .fields import ASNField, MACAddressField from .fields import ASNField, MACAddressField
@ -263,12 +264,12 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
return reverse('dcim:site', args=[self.slug]) return reverse('dcim:site', args=[self.slug])
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.name, self.name,
self.slug, self.slug,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.facility, self.facility,
str(self.asn) if self.asn else '', self.asn,
self.contact_name, self.contact_name,
self.contact_phone, self.contact_phone,
self.contact_email, self.contact_email,
@ -398,17 +399,17 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
}) })
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.site.name, self.site.name,
self.group.name if self.group else '', self.group.name if self.group else None,
self.name, self.name,
self.facility_id or '', self.facility_id,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.role.name if self.role else '', self.role.name if self.role else None,
self.get_type_display() if self.type else '', self.get_type_display() if self.type else None,
str(self.width), self.width,
str(self.u_height), self.u_height,
'True' if self.desc_units else '', self.desc_units,
]) ])
@property @property
@ -910,19 +911,19 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
Device.objects.filter(parent_bay__device=self).update(rack=self.rack) Device.objects.filter(parent_bay__device=self).update(rack=self.rack)
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.name or '', self.name or '',
self.device_role.name, self.device_role.name,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.device_type.manufacturer.name, self.device_type.manufacturer.name,
self.device_type.model, self.device_type.model,
self.platform.name if self.platform else '', self.platform.name if self.platform else None,
self.serial, self.serial,
self.asset_tag if self.asset_tag else '', self.asset_tag,
self.rack.site.name, self.rack.site.name,
self.rack.name, self.rack.name,
str(self.position) if self.position else '', self.position,
self.get_face_display() or '', self.get_face_display(),
]) ])
@property @property
@ -991,9 +992,9 @@ class ConsolePort(models.Model):
# Used for connections export # Used for connections export
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.cs_port.device.identifier if self.cs_port else '', self.cs_port.device.identifier if self.cs_port else None,
self.cs_port.name if self.cs_port else '', self.cs_port.name if self.cs_port else None,
self.device.identifier, self.device.identifier,
self.name, self.name,
self.get_connection_status_display(), self.get_connection_status_display(),
@ -1055,10 +1056,10 @@ class PowerPort(models.Model):
return self.device.get_absolute_url() return self.device.get_absolute_url()
# Used for connections export # Used for connections export
def to_csv(self): def csv_format(self):
return ','.join([ return ','.join([
self.power_outlet.device.identifier if self.power_outlet else '', self.power_outlet.device.identifier if self.power_outlet else None,
self.power_outlet.name if self.power_outlet else '', self.power_outlet.name if self.power_outlet else None,
self.device.identifier, self.device.identifier,
self.name, self.name,
self.get_connection_status_display(), self.get_connection_status_display(),
@ -1196,7 +1197,7 @@ class InterfaceConnection(models.Model):
# Used for connections export # Used for connections export
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.interface_a.device.identifier, self.interface_a.device.identifier,
self.interface_a.name, self.interface_a.name,
self.interface_b.device.identifier, self.interface_b.device.identifier,

View File

@ -13,6 +13,7 @@ from extras.models import CustomFieldModel, CustomFieldValue
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from utilities.sql import NullsFirstQuerySet from utilities.sql import NullsFirstQuerySet
from utilities.utils import csv_format
from .fields import IPNetworkField, IPAddressField from .fields import IPNetworkField, IPAddressField
@ -95,11 +96,11 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
return reverse('ipam:vrf', args=[self.pk]) return reverse('ipam:vrf', args=[self.pk])
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.name, self.name,
self.rd, self.rd,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
'True' if self.enforce_unique else '', self.enforce_unique,
self.description, self.description,
]) ])
@ -183,10 +184,10 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
super(Aggregate, self).save(*args, **kwargs) super(Aggregate, self).save(*args, **kwargs)
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
str(self.prefix), self.prefix,
self.rir.name, self.rir.name,
self.date_added.isoformat() if self.date_added else '', self.date_added.isoformat() if self.date_added else None,
self.description, self.description,
]) ])
@ -319,16 +320,16 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
super(Prefix, self).save(*args, **kwargs) super(Prefix, self).save(*args, **kwargs)
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
str(self.prefix), self.prefix,
self.vrf.rd if self.vrf else '', self.vrf.rd if self.vrf else None,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.site.name if self.site else '', self.site.name if self.site else None,
self.vlan.group.name if self.vlan and self.vlan.group else '', self.vlan.group.name if self.vlan and self.vlan.group else None,
str(self.vlan.vid) if self.vlan else '', self.vlan.vid if self.vlan else None,
self.get_status_display(), self.get_status_display(),
self.role.name if self.role else '', self.role.name if self.role else None,
'True' if self.is_pool else '', self.is_pool,
self.description, self.description,
]) ])
@ -432,14 +433,14 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
elif self.family == 6 and getattr(self, 'primary_ip6_for', False): elif self.family == 6 and getattr(self, 'primary_ip6_for', False):
is_primary = True is_primary = True
return ','.join([ return csv_format([
str(self.address), self.address,
self.vrf.rd if self.vrf else '', self.vrf.rd if self.vrf else None,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.get_status_display(), self.get_status_display(),
self.device.identifier if self.device else '', self.device.identifier if self.device else None,
self.interface.name if self.interface else '', self.interface.name if self.interface else None,
'True' if is_primary else '', is_primary,
self.description, self.description,
]) ])
@ -523,14 +524,14 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
}) })
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.site.name, self.site.name,
self.group.name if self.group else '', self.group.name if self.group else None,
str(self.vid), self.vid,
self.name, self.name,
self.tenant.name if self.tenant else '', self.tenant.name if self.tenant else None,
self.get_status_display(), self.get_status_display(),
self.role.name if self.role else '', self.role.name if self.role else None,
self.description, self.description,
]) ])

View File

@ -4,6 +4,7 @@ from django.db import models
from extras.models import CustomFieldModel, CustomFieldValue from extras.models import CustomFieldModel, CustomFieldValue
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from utilities.utils import csv_format
class TenantGroup(models.Model): class TenantGroup(models.Model):
@ -45,9 +46,9 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
return reverse('tenancy:tenant', args=[self.slug]) return reverse('tenancy:tenant', args=[self.slug])
def to_csv(self): def to_csv(self):
return ','.join([ return csv_format([
self.name, self.name,
self.slug, self.slug,
self.group.name if self.group else '', self.group.name if self.group else None,
self.description, self.description,
]) ])

15
netbox/utilities/utils.py Normal file
View File

@ -0,0 +1,15 @@
def csv_format(data):
"""
Encapsulate any data which contains a comma within double quotes.
"""
csv = []
for d in data:
if d in [None, False]:
csv.append(u'')
elif type(d) not in (str, unicode):
csv.append(u'{}'.format(d))
elif u',' in d:
csv.append(u'"{}"'.format(d))
else:
csv.append(d)
return u','.join(csv)