Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I have made a user interface to fetch data from a MySQL table and visualize it. It is running on a bokeh server. My users connect remotely to the server using their browser (firefox). This works perfectly fine: I simply import the table into a pandas dataframe.

My users also need to download the table as excel. This means I cannot use the export_csv example which is pure javascript.

I have no experience with JavaScript. All I want is to transfer a file from the directory where my main.py is to the client side.

The technique I have tried so far is to join a normal on_click callback to a button, export the information I need to 'output.xls', then change a parameter from a dummy glyph which in turn runs a Javascript code. I got the idea from Bokeh widgets call CustomJS and Python callback for single event? . Note I haven't set the alpha to 0, so that I can see if the circle is really growing upon clicking the download button.

At the bottom of my message you can find my code. You can see I have tried with both XMLHttpRequest and with Fetch directly. In the former case, nothing happens. In the latter case I obtain a file named "mydata.xlsx" as expected, however it contains only this raw text: <html><title>404: Not Found</title><body>404: Not Found</body></html>.

Code:

p = figure(title='mydata')
#download button
download_b = Button(label="Download", button_type="success")
download_b.on_click(download)

#dummy idea from https://stackoverflow.com/questions/44212250/bokeh-widgets-call-customjs-and-python-callback-for-single-event
dummy = p.circle([1], [1],name='dummy')

JScode_xhr = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);


var xhr = new XMLHttpRequest();
xhr.open('GET', '/output.xlsx', true);

xhr.responseType = 'blob';

xhr.onload = function(e) {
if (this.status == 200) {
    var blob = this.response;
    alert('seems to work...');
    if (navigator.msSaveBlob) {
                        navigator.msSaveBlob(blob, filename);
                    }

    else {
        var link = document.createElement("a");
        link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        window.open(link.href, '_blank');

        link.download = filename;
        link.target = "_blank";
        link.style.visibility = 'hidden';
        link.dispatchEvent(new MouseEvent('click'));
        URL.revokeObjectURL(url);
    }
  }
 else {
     alert('Ain't working!');
 }
};

"""


JScode_fetch = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);


fetch('/output.xlsx').then(response => response.blob())
                    .then(blob => {
                        alert(filename);
                        //addresses IE
                        if (navigator.msSaveBlob) {
                            navigator.msSaveBlob(blob, filename);
                        }

                        else {
                            var link = document.createElement("a");
                            link = document.createElement('a')
                            link.href = URL.createObjectURL(blob);
                            window.open(link.href, '_blank');

                            link.download = filename
                            link.target = "_blank";
                            link.style.visibility = 'hidden';
                            link.dispatchEvent(new MouseEvent('click'))
                            URL.revokeObjectURL(url);
                        }
                        return response.text();
                    });


"""


dummy.glyph.js_on_change('size', CustomJS(args=dict(p=p),
                                                  code=JScode_fetch))


plot_tab = Panel(child=row(download_b,p),
                         title="Plot",
                         closable=True,
                         name=str(self.test))


def download():
        writer = pd.ExcelWriter('output.xlsx')
        data.to_excel(writer,'data')
        infos.to_excel(writer,'info')

        dummy = p.select(name='dummy')[0]            
        dummy.glyph.size = dummy.glyph.size +1
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
179 views
Welcome To Ask or Share your Answers For Others

1 Answer

Trying out Eugene Pakhomov's answer, I found what was the issue.

The javascript code I named JScode_fetch is almost correct, however I get a 404 because it is not pointing correctly to the right path.

I made my application in the directory format: I changed my .py file to main.py, placed it into a folder called app, and changed this one line of code in JScode_fetch:

fetch('/app/static/output.xlsx', {cache: "no-store"}).then(response => response.blob())
[...]

You can see the problem was that it was trying to access localhost:5006/output.xlsx, instead of localhost:5006/app/output.xlsx. As it is in directory format, the right link is now localhost:5006/app/static/output.xlsx to count for the static directory.

I also changed a few lines in the download function:

def download():    
    dirpath = os.path.join(os.path.dirname(__file__),'static')
    writer = pd.ExcelWriter(os.path.join(dirpath,'output.xlsx'))
    writer = pd.ExcelWriter('output.xlsx')
    data.to_excel(writer,'data')
    infos.to_excel(writer,'info')

    dummy = p.select(name='dummy')[0]            
    dummy.glyph.size = dummy.glyph.size +1

Now it is working flawlessly!

edit: I have added , {cache: "no-store"} within the fetch() function. Otherwise the browser thinks the file is the same if you have to download a different dataframe excel while using the same output.xlsx filename. More info here.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...