On Tue, May 30, 2017 at 05:25:06PM +1000, Dan Callaghan wrote:
Hi Dan,
This is V2 of my patch. Things move farther along. But now I can't actually 'send_file'. It doesn't return anything. There is data added to the temp file and the function returns a flask response. Do I have to convert the flask response to a cherrypy one or something here?
Let me inline my issues in the patch.
Cheers, Don
diff --git a/Server/assets/csv-export.js b/Server/assets/csv-export.js new file mode 100644 index 0000000..09a3517 --- /dev/null +++ b/Server/assets/csv-export.js @@ -0,0 +1,43 @@ + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. + +;(function () { + +var CSVExportSelection = Backbone.Model.extend({ +}); + +window.CSVExport = Backbone.View.extend({ + template: JST['csv-export'], + events: { + 'submit form': 'submit', + 'change .csv-fields input': 'update_selection', + }, + initialize: function (options) { + this.csv_types = options.options['csv_types'] + this.selection = new CSVExportSelection({ + csv_type: this.csv_types[0] + }); + this.render(); + }, + render: function () { + this.$el.html(this.template(this)); + }, + update_selection: function (evt) { + var elem = evt.currentTarget; + this.selection.set(elem.name, $(elem).val()); + }, + submit: function (evt) { + evt.preventDefault(); + var xhr = $.ajax({ + url: 'csv/action_export', + type: 'POST', + data: this.selection.attributes, + traditional: true, + }); + }, +}); + +})(); diff --git a/Server/assets/jst/csv-export.html b/Server/assets/jst/csv-export.html new file mode 100644 index 0000000..2fd0366 --- /dev/null +++ b/Server/assets/jst/csv-export.html @@ -0,0 +1,20 @@ +<form class="form-horizontal"> + <fieldset class="csv-fields"> + <div class="control-group"> + <label class="control-label">CSV Type</label> + <div class="controls"> + <% _.each(csv_types, function (csv) { %> + <label class="radio"> + <input type="radio" name="csv_type" value="<%- csv.toLowerCase() %>" + <% if (csv == 'system' ) { %>checked<% } %> + /> + <%- csv %> + </label> + <% }) %> + </div> + </div> + <div class="form-actions"> + <button class="btn btn-primary" type="submit">Export CSV</button> + </div> + </fieldset> +</form> diff --git a/Server/bkr/server/CSV_import_export.py b/Server/bkr/server/CSV_import_export.py index 41fcb26..24ad6e2 100644 --- a/Server/bkr/server/CSV_import_export.py +++ b/Server/bkr/server/CSV_import_export.py @@ -10,7 +10,11 @@ from bkr.server import identity from bkr.server.xmlrpccontroller import RPCRoot from tempfile import NamedTemporaryFile -from cherrypy.lib.cptools import serve_file +#from cherrypy.lib.cptools import serve_file +from flask import send_file, request +from bkr.server.flask_util import render_tg_template, auth_required, admin_auth_required + +from bkr.server.app import app from bkr.server.model import (System, SystemType, Activity, SystemActivity, User, Group, LabController, LabInfo, OSMajor, OSVersion, @@ -70,6 +74,52 @@ def line_num(self): def fieldnames(self): return self.reader.fieldnames
+# For XMLRPC methods in this class. +exposed = False + +export_help_text = XML(u'<span>Refer to the <a href="http://beaker-project.org/docs/' + 'admin-guide/interface.html#export" target="_blank">' + 'documentation</a> to learn more about the exported data.</span>').expand() +import_help_text = XML(u'<span>Refer to the <a href="http://beaker-project.org/docs/' + 'admin-guide/interface.html#import" target="_blank">' + 'documentation</a> for details about the supported CSV format.</span>').expand() +
Not sure how to add these correctly. Through the 'options' in the render_tg_template call?
+@app.route('/csv', methods=['GET']) +@auth_required +def index(): + options = {} + options['csv_types'] = ('system', 'system_id', 'labinfo', 'power', + 'exclude', 'install', 'keyvalue', 'system_pool', + 'user_group')
I need to create a better map here, ie options['csv_types'] = { 'system': 'System', ...}???
+ #return render_template('assets.jst.csv_export.html', options=options) + return render_tg_template('bkr.server.templates.csv_export', { + 'title' : u'CSV Export Don', + 'options' : options + })
I tried to use the 'backgrid' template here, but it came falling because I didn't define a 'grid_collection_type'. I don't think I need that and couldn't figure out the javascript to 'if undefined skip..' logic.
So instead I use a new csv_export.kid file.
+ +@app.route('/csv/action_export', methods=['POST']) +@auth_required +def action_export(): + log = [] + csv_type = request.form.get('csv_type') + + + file = NamedTemporaryFile() + logger.debug("DON: request: %s, file %s" % (csv_type, file.name)) + if csv_type in csv_types: + csv_types[csv_type]._to_csv(file) + else: + log.append("Invalid csv_type %s" % csv_type) + file.seek(0) + + logger.debug('DON CSV export : %s', file) + filename = "%s.csv" % csv_type + ret = send_file(file.name, mimetype="text/csv", + as_attachment=True, + attachment_filename=filename)
This returns a flask response. Not sure if it is valid, it barfs with I try to print it. Regardless, nothing happens as a result, no pop up download window, no failure, nothing. The csv_type data is right though.
That's it for issues...
+ + return ret + class CSV(RPCRoot): # For XMLRPC methods in this class. exposed = False @@ -110,8 +160,6 @@ class CSV(RPCRoot): submit_text = _(u'Export CSV'), )
- @expose(template='bkr.server.templates.form') - @identity.require(identity.not_anonymous()) def index(self, **kw): return dict( form = self.exportform, @@ -121,8 +169,6 @@ def index(self, **kw): value = kw, )
- @expose(template='bkr.server.templates.form-post') - @identity.require(identity.in_group('admin')) def csv_import(self, **kw): return dict( form = self.importform, @@ -132,13 +178,12 @@ def csv_import(self, **kw): value = kw, )
- @expose() - @identity.require(identity.not_anonymous()) def action_export(self, csv_type, *args, **kw): file = NamedTemporaryFile() log = self.to_csv(file, csv_type) file.seek(0)
+ logger.debug('DON CSV export with send_file type: %s', csv_type) return serve_file(file.name, contentType="text/csv", disposition="attachment", name="%s.csv" % csv_type) @@ -181,8 +226,6 @@ def _import_row(self, data, log): raise ValueError('Invalid csv_type %s or missing required fields' % data['csv_type'])
- @expose(template='bkr.server.templates.csv_import') - @identity.require(identity.in_group('admin')) def action_import(self, csv_file, *args, **kw): """ TurboGears method to import data from csv @@ -247,9 +290,11 @@ def _to_csv(cls, file): header = csv.writer(file) header.writerow(['csv_type'] + cls.csv_keys) writer = csv.DictWriter(file, ['csv_type'] + cls.csv_keys) + logger.debug("DON: %s" % cls.csv_keys) for item in cls.query(): for data in cls.to_datastruct(item): data['csv_type'] = cls.csv_type + logger.debug("DON: data is %s" % data) # XXX remove encoding in Python 3... writer.writerow(dict((k, unicode(v).encode('utf8')) for k, v in data.iteritems()))
diff --git a/Server/bkr/server/assets.py b/Server/bkr/server/assets.py index 0c5a117..71747f4 100644 --- a/Server/bkr/server/assets.py +++ b/Server/bkr/server/assets.py @@ -90,6 +90,7 @@ def _create_env(source_dir, output_dir, **kwargs): 'pools.js', 'query-builder.js', 'reserve-workflow.js', + 'csv-export.js', 'installation-model.js', 'task-library-model.js', 'scheduler-model.js', diff --git a/Server/bkr/server/controllers.py b/Server/bkr/server/controllers.py index cece8db..936cdc2 100644 --- a/Server/bkr/server/controllers.py +++ b/Server/bkr/server/controllers.py @@ -146,7 +146,7 @@ class Root(RPCRoot): users = Users() arches = Arches() auth = Auth() - csv = CSV() + #csv = CSV() jobs = Jobs() recipesets = RecipeSets() recipes = Recipes() diff --git a/Server/bkr/server/templates/csv_export.kid b/Server/bkr/server/templates/csv_export.kid new file mode 100644 index 0000000..16f7487 --- /dev/null +++ b/Server/bkr/server/templates/csv_export.kid @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" + py:extends="'master.kid'"> +<head> +<title>$title</title> +</head> +<body> +<div class="page-header"> + <h1>$title</h1> +</div> +<div class="csv_export"></div> +<script type="text/javascript"> +$(function () { + new CSVExport({ + el: '.csv_export', + options: ${tg.to_json(options)}, + }); +}); +</script> +</body> +</html>