Commit 8f775371 authored by Paul Brown's avatar Paul Brown

Merge pull request #1203 from iurisilvio/export_xlsx

Tablib export
parents baf191d1 7cf4f4f9
import warnings import warnings
import re import re
import csv import csv
import mimetypes
import time import time
from werkzeug import secure_filename from werkzeug import secure_filename
...@@ -8,6 +9,10 @@ from werkzeug import secure_filename ...@@ -8,6 +9,10 @@ from werkzeug import secure_filename
from flask import (request, redirect, flash, abort, json, Response, from flask import (request, redirect, flash, abort, json, Response,
get_flashed_messages, stream_with_context) get_flashed_messages, stream_with_context)
from jinja2 import contextfunction from jinja2 import contextfunction
try:
import tablib
except ImportError:
tablib = None
from wtforms.fields import HiddenField from wtforms.fields import HiddenField
from wtforms.fields.core import UnboundField from wtforms.fields.core import UnboundField
from wtforms.validators import ValidationError, InputRequired from wtforms.validators import ValidationError, InputRequired
...@@ -683,6 +688,15 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -683,6 +688,15 @@ class BaseModelView(BaseView, ActionsMixin):
Unlimited by default. Uses `page_size` if set to `None`. Unlimited by default. Uses `page_size` if set to `None`.
""" """
export_types = ['csv']
"""
A list of available export filetypes. `csv` only is default, but any
filetypes supported by tablib can be used.
Check tablib for https://github.com/kennethreitz/tablib/bloab/master/README.rst
for supported types.
"""
# Various settings # Various settings
page_size = 20 page_size = 20
""" """
...@@ -1696,12 +1710,13 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1696,12 +1710,13 @@ class BaseModelView(BaseView, ActionsMixin):
self.column_type_formatters_export, self.column_type_formatters_export,
) )
def get_export_name(self): def get_export_name(self, export_type='csv'):
""" """
:return: The exported csv file name. :return: The exported csv file name.
""" """
filename = '%s_%s.csv' % (self.name, filename = '%s_%s.%s' % (self.name,
time.strftime("%Y-%m-%d_%H-%M-%S")) time.strftime("%Y-%m-%d_%H-%M-%S"),
export_type)
return filename return filename
# AJAX references # AJAX references
...@@ -2001,17 +2016,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -2001,17 +2016,7 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
return self.handle_action() return self.handle_action()
@expose('/export/csv/') def _export_data(self):
def export_csv(self):
"""
Export a CSV of records.
"""
return_url = get_redirect_target() or self.get_url('.index_view')
if not self.can_export:
flash(gettext('Permission denied.'))
return redirect(return_url)
# Macros in column_formatters are not supported. # Macros in column_formatters are not supported.
# Macros will have a function name 'inner' # Macros will have a function name 'inner'
# This causes non-macro functions named 'inner' not work. # This causes non-macro functions named 'inner' not work.
...@@ -2040,6 +2045,27 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -2040,6 +2045,27 @@ class BaseModelView(BaseView, ActionsMixin):
view_args.search, view_args.filters, view_args.search, view_args.filters,
page_size=self.export_max_rows) page_size=self.export_max_rows)
return count, data
@expose('/export/<export_type>/')
def export(self, export_type):
return_url = get_redirect_target() or self.get_url('.index_view')
if not self.can_export or (export_type not in self.export_types):
flash(gettext('Permission denied.'))
return redirect(return_url)
if export_type == 'csv':
return self._export_csv(return_url)
else:
return self._export_tablib(export_type, return_url)
def _export_csv(self, return_url):
"""
Export a CSV of records as a stream.
"""
count, data = self._export_data()
# https://docs.djangoproject.com/en/1.8/howto/outputting-csv/ # https://docs.djangoproject.com/en/1.8/howto/outputting-csv/
class Echo(object): class Echo(object):
""" """
...@@ -2065,7 +2091,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -2065,7 +2091,7 @@ class BaseModelView(BaseView, ActionsMixin):
for c in self._export_columns] for c in self._export_columns]
yield writer.writerow(vals) yield writer.writerow(vals)
filename = self.get_export_name() filename = self.get_export_name(export_type='csv')
disposition = 'attachment;filename=%s' % (secure_filename(filename),) disposition = 'attachment;filename=%s' % (secure_filename(filename),)
...@@ -2075,6 +2101,48 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -2075,6 +2101,48 @@ class BaseModelView(BaseView, ActionsMixin):
mimetype='text/csv' mimetype='text/csv'
) )
def _export_tablib(self, export_type, return_url):
"""
Exports a variety of formats using the tablib library.
"""
if tablib is None:
flash(gettext('Tablib dependency not installed.'))
return redirect(return_url)
filename = self.get_export_name(export_type)
disposition = 'attachment;filename=%s' % (secure_filename(filename),)
mimetype, encoding = mimetypes.guess_type(filename)
if not mimetype:
mimetype = 'application/octet-stream'
if encoding:
mimetype = '%s; charset=%s' % (mimetype, encoding)
ds = tablib.Dataset(headers=[c[1] for c in self._export_columns])
count, data = self._export_data()
for row in data:
vals = [self.get_export_value(row, c[0]) for c in self._export_columns]
ds.append(vals)
try:
try:
response_data = ds.export(format=export_type)
except AttributeError:
response_data = getattr(ds, export_type)
except (AttributeError, tablib.UnsupportedFormat):
flash(gettext('Export type "%(type)s not supported.',
type=export_type))
return redirect(return_url)
return Response(
response_data,
headers={'Content-Disposition': disposition},
mimetype=mimetype,
)
@expose('/ajax/lookup/') @expose('/ajax/lookup/')
def ajax_lookup(self): def ajax_lookup(self):
name = request.args.get('name') name = request.args.get('name')
......
...@@ -11,6 +11,27 @@ ...@@ -11,6 +11,27 @@
</ul> </ul>
{% endmacro %} {% endmacro %}
{% macro export_options(btn_class='dropdown-toggle') %}
{% if admin_view.export_types|length > 1 %}
<li class="dropdown">
<a class="{{ btn_class }}" data-toggle="dropdown" href="javascript:void(0)">
{{ _gettext('Export') }}<b class="caret"></b>
</a>
<ul class="dropdown-menu field-filters">
{% for export_type in admin_view.export_types %}
<li>
<a href="{{ get_url('.export', export_type=export_type, **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') + ' ' + export_type|upper }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>
<a href="{{ get_url('.export', export_type=admin_view.export_types[0], **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') }}</a>
</li>
{% endif %}
{% endmacro %}
{% macro filter_form() %} {% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}"> <form id="filter_form" method="GET" action="{{ return_url }}">
<div class="pull-right"> <div class="pull-right">
......
...@@ -27,9 +27,7 @@ ...@@ -27,9 +27,7 @@
{% endif %} {% endif %}
{% if admin_view.can_export %} {% if admin_view.can_export %}
<li> {{ model_layout.export_options() }}
<a href="{{ get_url('.export_csv', **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') }}</a>
</li>
{% endif %} {% endif %}
{% block model_menu_bar_before_filters %}{% endblock %} {% block model_menu_bar_before_filters %}{% endblock %}
......
...@@ -11,6 +11,27 @@ ...@@ -11,6 +11,27 @@
</ul> </ul>
{% endmacro %} {% endmacro %}
{% macro export_options(btn_class='dropdown-toggle') %}
{% if admin_view.export_types|length > 1 %}
<li class="dropdown">
<a class="{{ btn_class }}" data-toggle="dropdown" href="javascript:void(0)">
{{ _gettext('Export') }}<b class="caret"></b>
</a>
<ul class="dropdown-menu field-filters">
{% for export_type in admin_view.export_types %}
<li>
<a href="{{ get_url('.export', export_type=export_type, **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') + ' ' + export_type|upper }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>
<a href="{{ get_url('.export', export_type=admin_view.export_types[0], **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') }}</a>
</li>
{% endif %}
{% endmacro %}
{% macro filter_form() %} {% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}"> <form id="filter_form" method="GET" action="{{ return_url }}">
<div class="pull-right"> <div class="pull-right">
......
...@@ -27,9 +27,7 @@ ...@@ -27,9 +27,7 @@
{% endif %} {% endif %}
{% if admin_view.can_export %} {% if admin_view.can_export %}
<li> {{ model_layout.export_options() }}
<a href="{{ get_url('.export_csv', **request.args) }}" title="{{ _gettext('Export') }}">{{ _gettext('Export') }}</a>
</li>
{% endif %} {% endif %}
{% block model_menu_bar_before_filters %}{% endblock %} {% block model_menu_bar_before_filters %}{% endblock %}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment