Skip to content

Commit

Permalink
Export preview as CSV, JSONL or XLSX
Browse files Browse the repository at this point in the history
  • Loading branch information
morlandi committed Nov 12, 2023
1 parent 5ae6dc8 commit e58e9af
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
History
=======

v1.2.2
------
* export preview as CSV, JSONL or XLSX

v1.2.1
------
* comment out "signal" from sitecopy management command
Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ App settings
QUERY_INSPECTOR_QUERY_SUPERUSER_ONLY = True
QUERY_INSPECTOR_QUERY_DEFAULT_LIMIT = 0
QUERY_INSPECTOR_QUERY_STOCK_QUERIES = []
DEFAULT_CSV_FIELD_DELIMITER = ';'
QUERY_INSPECTOR_SQL_BLACKLIST = (
'ALTER',
'RENAME ',
Expand Down
22 changes: 22 additions & 0 deletions query_inspector/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from .models import Query
from .sql import perform_query
from .sql import reload_stock_queries
from .views import normalized_export_filename
from .views import export_any_dataset


@admin.register(Query)
Expand Down Expand Up @@ -140,6 +142,7 @@ def preview(self, request, object_id):
recordset = []
elapsed = None
if request.method == 'POST':

try:
start = time.perf_counter()

Expand All @@ -148,6 +151,18 @@ def preview(self, request, object_id):
sql += ' limit %d' % sql_limit

recordset = perform_query(sql, params, log=True, validate=True)
if 'btn-export-csv' in request.POST:
filename = normalized_export_filename(obj.slug, "csv")
response = export_any_dataset(request, "*", queryset=recordset, filename=filename)
return response
elif 'btn-export-jsonl' in request.POST:
filename = normalized_export_filename(obj.slug, "jsonl")
response = export_any_dataset(request, "*", queryset=recordset, filename=filename)
return response
elif 'btn-export-xlsx' in request.POST:
filename = normalized_export_filename(obj.slug, "xlsx")
response = export_any_dataset(request, "*", queryset=recordset, filename=filename)
return response

# Save default parameters
obj.default_parameters = params
Expand All @@ -160,6 +175,12 @@ def preview(self, request, object_id):
elapsed = ''
messages.error(request, str(e))

try:
import xlsxwriter
xlsxwriter_available = True
except ModuleNotFoundError as e:
xlsxwriter_available = False

return render(
request,
'admin/query_inspector/query/preview.html', {
Expand All @@ -177,5 +198,6 @@ def preview(self, request, object_id):
'recordset': recordset,
'elapsed': elapsed,
'sql_limit': sql_limit,
'xlsxwriter_available': xlsxwriter_available,
}
)
3 changes: 3 additions & 0 deletions query_inspector/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
QUERY_SUPERUSER_ONLY = getattr(settings, 'QUERY_INSPECTOR_QUERY_SUPERUSER_ONLY', True)
QUERY_DEFAULT_LIMIT = getattr(settings, 'QUERY_INSPECTOR_QUERY_DEFAULT_LIMIT', '0')
QUERY_STOCK_QUERIES = getattr(settings, 'QUERY_INSPECTOR_QUERY_STOCK_QUERIES', [])
DEFAULT_CSV_FIELD_DELIMITER = getattr(settings, 'QUERY_INSPECTOR_DEFAULT_CSV_FIELD_DELIMITER', ';')


SQL_BLACKLIST = getattr(
settings, 'QUERY_INSPECTOR_SQL_BLACKLIST',
(
Expand Down
26 changes: 24 additions & 2 deletions query_inspector/templates/admin/query_inspector/query/preview.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo;
<a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo;
{% if has_view_permission %}
<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
{% else %}
Expand Down Expand Up @@ -76,13 +74,37 @@ <h3>{% translate 'Query parameters' %}</h3>
class="btn"
type="submit"
value="{% blocktranslate %}Run query{% endblocktranslate %}"
name="btn-run"
/>
<input
class="btn"
style="background-color: #007bff;"
type="submit"
value="{% blocktranslate %}Export as CSV{% endblocktranslate %}"
name="btn-export-csv"
/>
<input
class="btn"
style="background-color: #007bff;"
type="submit"
value="{% blocktranslate %}Export as JSONL{% endblocktranslate %}"
name="btn-export-jsonl"
/>
<input
class="btn"
style="background-color: #007bff;"
type="submit"
value="{% blocktranslate %}Export as XLSX{% endblocktranslate %}"
name="btn-export-xlsx"
{% if not xlsxwriter_available %}disabled{% endif %}
/>
</form>

{% if elapsed != None %}
<br />
<b>{% translate 'Record count' %}: {{recordset|length}}</b>
{% translate 'Elapsed time' %}: {{elapsed}} <span> [s]</span>

<table id="recordset-table" class="simpletable smarttable">
{% render_queryset_as_table "*" queryset=recordset %}
</table>
Expand Down
15 changes: 12 additions & 3 deletions query_inspector/views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import io
import os
import csv
import json
from django.utils import timezone
from django.template.defaultfilters import slugify
from django.http import StreamingHttpResponse
from .exporters import open_xlsx_file, SpreadsheetQuerysetExporter
from .templatetags.query_inspector_tags import render_queryset_as_data
from .app_settings import DEFAULT_CSV_FIELD_DELIMITER


def normalized_export_filename(title, extension):
"""
Provides a default filename; "%Y-%m-%d_%H-%M-%S__TITLE.EXTENNSION"
Provides a default filename; "%Y-%m-%d_%H-%M-%S__TITLE.EXTENSION"
"""
#filename = timezone.localtime().strftime('%Y-%m-%d_%H-%M-%S__') + slugify(title)
filename = timezone.now().strftime('%Y-%m-%d_%H-%M-%S__') + slugify(title)
Expand All @@ -21,7 +23,7 @@ def normalized_export_filename(title, extension):
return filename


def export_any_queryset(request, queryset, filename, excluded_fields=[], included_fields=[], csv_field_delimiter = ";"):
def export_any_queryset(request, queryset, filename, excluded_fields=[], included_fields=[], csv_field_delimiter=DEFAULT_CSV_FIELD_DELIMITER):
"""
Export queryset using SpreadsheetQuerysetExporter()
"""
Expand Down Expand Up @@ -69,7 +71,7 @@ def export_any_queryset(request, queryset, filename, excluded_fields=[], include
return response


def export_any_dataset(request, *fields, queryset, filename, csv_field_delimiter = ";"):
def export_any_dataset(request, *fields, queryset, filename, csv_field_delimiter=DEFAULT_CSV_FIELD_DELIMITER):
"""
Export queryset using render_queryset_as_data()
"""
Expand All @@ -88,6 +90,13 @@ def export_any_dataset(request, *fields, queryset, filename, csv_field_delimiter
for row in rows:
writer.writerow(row)

elif file_format == "jsonl":
content_type = 'application/jsonl'
output = io.StringIO()
output.write(json.dumps(headers) + '\n')
for row in rows:
output.write(json.dumps(row) + '\n')

elif file_format == 'xlsx':
content_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
#content_type = 'application/vnd.ms-excel'
Expand Down

0 comments on commit e58e9af

Please sign in to comment.