export.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. # TODO: Remove this module in NetBox v5.0
  2. import csv
  3. import warnings
  4. from django.http import StreamingHttpResponse
  5. from django.utils.encoding import force_str
  6. from django.utils.http import content_disposition_header
  7. from django.utils.translation import gettext_lazy as _
  8. from django_tables2.data import TableQuerysetData
  9. from django_tables2.export import TableExport as TableExport_
  10. from django_tables2.rows import BoundRow
  11. from utilities.constants import CSV_DELIMITERS
  12. __all__ = (
  13. 'TableExport',
  14. 'stream_table_csv_response',
  15. )
  16. warnings.warn(
  17. "utilities.export is deprecated and will be removed in NetBox v5.0. Register an exporter using "
  18. "netbox.exporters.register_exporter() instead.",
  19. DeprecationWarning,
  20. stacklevel=2,
  21. )
  22. EXPORT_CHUNK_SIZE = 1000
  23. class TableExport(TableExport_):
  24. """
  25. A subclass of django-tables2's TableExport class which allows us to specify a delimiting
  26. characters for CSV exports.
  27. """
  28. def __init__(self, *args, delimiter=None, **kwargs):
  29. if delimiter and delimiter not in CSV_DELIMITERS.keys():
  30. raise ValueError(_("Invalid delimiter name: {name}").format(name=delimiter))
  31. self.delimiter = delimiter or 'comma'
  32. super().__init__(*args, **kwargs)
  33. def export(self):
  34. if self.format == self.CSV and self.delimiter is not None:
  35. delimiter = CSV_DELIMITERS[self.delimiter]
  36. return self.dataset.export(self.format, delimiter=delimiter)
  37. return super().export()
  38. class _EchoBuffer:
  39. """
  40. File-like object whose write() simply returns the value written, so csv.writer output can be
  41. captured row-by-row and fed to a StreamingHttpResponse.
  42. """
  43. def write(self, value):
  44. return value
  45. def stream_table_csv_response(table, exclude_columns=None, filename=None, delimiter=None):
  46. """
  47. Return a StreamingHttpResponse that emits the given table's rows as CSV without first buffering
  48. the entire dataset in memory. Queryset-backed tables are iterated in chunks using
  49. QuerySet.iterator() to cap peak memory; prefetched relations are preserved under Django 4.1+.
  50. Args:
  51. table: The django-tables2 Table instance to export
  52. exclude_columns: Iterable of column names to omit from the export
  53. filename: If set, a Content-Disposition header is included in the HTTP response, indicating its treatment
  54. as a file attachment with the specified name.
  55. delimiter: Name of a delimiter in utilities.constants.CSV_DELIMITERS (defaults to 'comma')
  56. """
  57. if delimiter and delimiter not in CSV_DELIMITERS:
  58. raise ValueError(_("Invalid delimiter name: {name}").format(name=delimiter))
  59. csv_delimiter = CSV_DELIMITERS[delimiter or 'comma']
  60. exclude_columns = exclude_columns or set()
  61. columns = [
  62. column for column in table.columns.iterall()
  63. if not (column.column.exclude_from_export or column.name in exclude_columns)
  64. ]
  65. writer = csv.writer(_EchoBuffer(), delimiter=csv_delimiter)
  66. def iter_records():
  67. if isinstance(table.data, TableQuerysetData):
  68. yield from table.data.data.iterator(chunk_size=EXPORT_CHUNK_SIZE)
  69. else:
  70. yield from table.data
  71. def row_generator():
  72. yield writer.writerow([
  73. force_str(column.header, strings_only=True) for column in columns
  74. ])
  75. for record in iter_records():
  76. row = BoundRow(record, table=table)
  77. yield writer.writerow([
  78. force_str(row.get_cell_value(column.name), strings_only=True)
  79. for column in columns
  80. ])
  81. response = StreamingHttpResponse(row_generator(), content_type='text/csv; charset=utf-8')
  82. if filename is not None:
  83. response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
  84. return response