From 7ad2ec48f0e7ebddb6000dff94b5c21c305ca608 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 12 Feb 2017 16:08:29 -0600 Subject: [PATCH] Add master view for generic exports --- tailbone/views/exports.py | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tailbone/views/exports.py diff --git a/tailbone/views/exports.py b/tailbone/views/exports.py new file mode 100644 index 00000000..98b074aa --- /dev/null +++ b/tailbone/views/exports.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2017 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Rattail. If not, see . +# +################################################################################ +""" +Master class for generic export history views +""" + +from __future__ import unicode_literals, absolute_import + +import os + +from rattail.db import model + +import formalchemy as fa +from pyramid.response import FileResponse + +from tailbone import forms +from tailbone.views import MasterView + + +class ExportMasterView(MasterView): + """ + Master class for generic export history views + """ + creatable = False + editable = False + export_has_file = False + + def get_export_key(self): + if hasattr(self, 'export_key'): + return self.export_key + return + + def get_file_path(self, export, makedirs=False): + return self.rattail_config.export_filepath(self.export_key, + export.uuid, + export.filename, + makedirs=makedirs) + + def _preconfigure_grid(self, g): + g.filters['id'].label = "ID" + g.id.set(label="ID", renderer=forms.renderers.BatchIDFieldRenderer) + g.joiners['created_by'] = lambda q: q.join(model.User) + g.sorters['created_by'] = g.make_sorter(model.User.username) + g.filters['created_by'] = g.make_filter('created_by', model.User.username, + label="Created by") + g.created_by.set(label="Created by", renderer=forms.renderers.UserFieldRenderer) + g.default_sortkey = 'created' + g.default_sortdir = 'desc' + + def configure_grid(self, g): + g.configure( + include=[ + g.id, + g.created, + g.created_by, + g.record_count, + ], + readonly=True) + + def _preconfigure_fieldset(self, fs): + fs.id.set(label="ID", renderer=forms.renderers.BatchIDFieldRenderer) + fs.created_by.set(label="Created by", renderer=forms.renderers.UserFieldRenderer, + attrs={'hyperlink': True}) + if self.export_has_file and self.viewing: + download = forms.renderers.FileFieldRenderer.new( + self, storage_path=self.rattail_config.export_filedir(self.export_key), + file_path=self.get_file_path(fs.model), download_url=self.get_download_url) + fs.append(fa.Field('download', renderer=download)) + + def configure_fieldset(self, fs): + fields = [ + fs.id, + fs.created, + fs.created_by, + fs.record_count, + ] + if self.export_has_file and self.viewing: + fields.append(fs.download) + fs.configure(include=fields) + + def get_download_url(self, filename): + uuid = self.request.matchdict['uuid'] + return self.request.route_url('{}.download'.format(self.get_route_prefix()), uuid=uuid) + + def download(self): + """ + View for downloading the export file. + """ + export = self.get_instance() + path = self.get_file_path(export) + response = FileResponse(path, request=self.request) + response.headers[b'Content-Length'] = str(os.path.getsize(path)) + response.headers[b'Content-Disposition'] = b'attachment; filename="{}"'.format(export.filename) + return response + + def delete_instance(self, export): + """ + Delete the export file also, if it exists. + """ + if self.export_has_file: + path = self.get_file_path(export) + if os.path.exists(path): + os.remove(path) + os.rmdir(os.path.dirname(path)) + super(ExportMasterView, self).delete_instance(export) + + @classmethod + def defaults(cls, config): + """ + Provide default configuration for a master view. + """ + cls._defaults(config) + cls._export_defaults(config) + + @classmethod + def _export_defaults(cls, config): + route_prefix = cls.get_route_prefix() + url_prefix = cls.get_url_prefix() + permission_prefix = cls.get_permission_prefix() + model_key = cls.get_model_key() + model_title = cls.get_model_title() + + # download export file + if cls.export_has_file: + config.add_route('{}.download'.format(route_prefix), '{}/{{{}}}/download'.format(url_prefix, model_key)) + config.add_view(cls, attr='download', route_name='{}.download'.format(route_prefix), + permission='{}.download'.format(permission_prefix)) + config.add_tailbone_permission(permission_prefix, '{}.download'.format(permission_prefix), + "Download {} data file".format(model_title))