ソースを参照

Closes #19739: Add a user preference for CSV delimiter in table exports (#19824)

* Closes #19739: Add a user preference for CSV delimiter in table exports

* Pass custom delimiter when exporting entire table
Jeremy Stretch 7 ヶ月 前
コミット
90e8a61670

+ 7 - 0
netbox/netbox/constants.py

@@ -44,3 +44,10 @@ CENSOR_TOKEN_CHANGED = '***CHANGED***'
 
 # Placeholder text for empty tables
 EMPTY_TABLE_TEXT = 'No results found'
+
+# CSV delimiters
+CSV_DELIMITERS = {
+    'comma': ',',
+    'semicolon': ';',
+    'pipe': '|',
+}

+ 10 - 0
netbox/netbox/preferences.py

@@ -72,6 +72,16 @@ PREFERENCES = {
         ),
         description=_('The preferred syntax for displaying generic data within the UI')
     ),
+    'csv_delimiter': UserPreference(
+        label=_('CSV delimiter'),
+        choices=(
+            ('comma', 'Comma (,)'),
+            ('semicolon', 'Semicolon (;)'),
+            ('pipe', 'Pipe (|)'),
+        ),
+        default='comma',
+        description=_('The character used to separate fields in CSV data')
+    ),
 
 }
 

+ 9 - 5
netbox/netbox/views/generic/bulk_views.py

@@ -15,7 +15,6 @@ from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext as _
-from django_tables2.export import TableExport
 from mptt.models import MPTTModel
 
 from core.models import ObjectType
@@ -25,6 +24,7 @@ from extras.models import CustomField, ExportTemplate
 from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
 from utilities.error_handlers import handle_protectederror
 from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
+from utilities.export import TableExport
 from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields
 from utilities.forms.bulk_import import BulkImportForm
 from utilities.htmx import htmx_partial
@@ -77,7 +77,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
 
         return '---\n'.join(yaml_data)
 
-    def export_table(self, table, columns=None, filename=None):
+    def export_table(self, table, columns=None, filename=None, delimiter=None):
         """
         Export all table data in CSV format.
 
@@ -86,6 +86,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
             columns: A list of specific columns to include. If None, all columns will be exported.
             filename: The name of the file attachment sent to the client. If None, will be determined automatically
                 from the queryset model name.
+            delimiter: The character used to separate columns (a comma is used by default)
         """
         exclude_columns = {'pk', 'actions'}
         if columns:
@@ -96,7 +97,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
         exporter = TableExport(
             export_format=TableExport.CSV,
             table=table,
-            exclude_columns=exclude_columns
+            exclude_columns=exclude_columns,
+            delimiter=delimiter,
         )
         return exporter.response(
             filename=filename or f'netbox_{self.queryset.model._meta.verbose_name_plural}.csv'
@@ -159,7 +161,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
             if request.GET['export'] == 'table':
                 table = self.get_table(self.queryset, request, has_table_actions)
                 columns = [name for name, _ in table.selected_columns]
-                return self.export_table(table, columns)
+                delimiter = request.user.config.get('csv_delimiter')
+                return self.export_table(table, columns, delimiter=delimiter)
 
             # Render an ExportTemplate
             elif request.GET['export']:
@@ -176,7 +179,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
             # Fall back to default table/YAML export
             else:
                 table = self.get_table(self.queryset, request, has_table_actions)
-                return self.export_table(table)
+                delimiter = request.user.config.get('csv_delimiter')
+                return self.export_table(table, delimiter=delimiter)
 
         # Render the objects table
         table = self.get_table(self.queryset, request, has_table_actions)

+ 1 - 1
netbox/users/forms/model_forms.py

@@ -62,7 +62,7 @@ class UserConfigForm(forms.ModelForm, metaclass=UserConfigFormMetaclass):
             'ui.tables.striping',
             name=_('User Interface')
         ),
-        FieldSet('data_format', name=_('Miscellaneous')),
+        FieldSet('data_format', 'csv_delimiter', name=_('Miscellaneous')),
     )
     # List of clearable preferences
     pk = forms.MultipleChoiceField(

+ 26 - 0
netbox/utilities/export.py

@@ -0,0 +1,26 @@
+from django.utils.translation import gettext_lazy as _
+from django_tables2.export import TableExport as TableExport_
+
+from netbox.constants import CSV_DELIMITERS
+
+__all__ = (
+    'TableExport',
+)
+
+
+class TableExport(TableExport_):
+    """
+    A subclass of django-tables2's TableExport class which allows us to specify a delimiting
+    characters for CSV exports.
+    """
+    def __init__(self, *args, delimiter=None, **kwargs):
+        if delimiter and delimiter not in CSV_DELIMITERS.keys():
+            raise ValueError(_("Invalid delimiter name: {name}").format(name=delimiter))
+        self.delimiter = delimiter or 'comma'
+        super().__init__(*args, **kwargs)
+
+    def export(self):
+        if self.format == self.CSV and self.delimiter is not None:
+            delimiter = CSV_DELIMITERS[self.delimiter]
+            return self.dataset.export(self.format, delimiter=delimiter)
+        return super().export()