3

I'm trying to load and save data from a HandsOnTable using Flask. I'm following these instructions for loading and saving (retrieving and sending) data using ajax. I have managed to get data to load into the table from a URL that returns a JSON dictionary, but I haven't figured out how to send data do store in my database.

Here is what the relevant part of the javascript looks like:

Handsontable.Dom.addEvent(save, 'click', function() {
    // save all cell's data
    ajax('/json/save.json', 'GET', JSON.stringify({data: hot.getData()}), function (res) {
      var response = JSON.parse(res.response);

      if (response.result === 'ok') {
        exampleConsole.innerText = 'Data saved';
      }
      else {
        exampleConsole.innerText = 'Save error';
      }
    });
  });

So hopefully that is taking the data from the HandsOnTable, turning it into a big JSON table of this format:

{'data' : [[row 1], [row 2],...]}

And here is what the relevant Flask view function looks like:

@app.route('/json/save.json', methods = ['GET', 'POST'])
@login_required
def jsonSave():
    data = request.form['data']
    #Do stuff to load data into database
    return 'ok'

With irrelevant parts removed. Basically my question is how do I make the data = request.form['data'] part of the save function work, and turn it into a simple list of rows?

Incidentally, part of why this is difficult is that I can't see what exactly is being sent to the view function with the ajax call. Is there a way I can see that so I can more easily debug issues like this? Print statements don't seem to work in view functions (I can't see them in the console).

Thanks a lot, Alex

Update Changed (again) as per ZekeDroid's instructions to:

Handsontable.Dom.addEvent(save, 'click', function() {
// save all cell's data
console.log(JSON.stringify({data: hot.getData()}));
ajax('/json/save/{{well['id']}}', 'POST', JSON.stringify({data: hot.getData()}), function (res) {
  var response = JSON.parse(res.response);

  if (response.result === 'ok') {
    exampleConsole.innerText = 'Data saved';
  }
  else {
    exampleConsole.innerText = 'Save error';
  }
});

});

and

@app.route('/json/save/<int:well_id>', methods = ['GET', 'POST'])
@login_required
def jsonSave(well_id):
    jsonData = request.get_json()
    print 'jsonData:', jsonData
    data = jsonData['data']
    print 'data:', data
    #Insert table into database
    print 'saving', well_id
    return json.dumps(True)

Debug output: Basically, it looks like Flask is not loading a json object when it calls jsonData = request.get_json(). The console.log(JSON.stringify({data: hot.getData()})); looks ok, however.

Here is the output from the browser and Flask consoles:

Browser:

{"data":[["01/01/15",100,300,200,96],["01/02/15",200,500,300,50],["01/03/15",300,600,400,80],["01/01/15",100,300,200,96],["01/02/15",200,500,300,50],["01/03/15",300,600,400,80],["01/01/15",100,300,200,96],["01/02/15",200,500,300,50],["01/03/15",300,600,400,80],[null,null,null,null,null]]}
samples.js:94 POST http://127.0.0.1:5000/json/save/1 500 (INTERNAL SERVER ERROR)

Flask:

jsonData: None
127.0.0.1 - - [13/May/2015 11:41:31] "POST /json/save/1 HTTP/1.1" 500 -
Traceback (most recent call last):
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1817, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask\app.py", line
1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\aschmitt\Envs\PetroTools\lib\site-packages\flask_login.py", lin
e 758, in decorated_view
    return func(*args, **kwargs)
  File "C:\Users\aschmitt\Dropbox\Python\PetroTools\app\views.py", line 236, in
jsonSave
    data = jsonData['data']
TypeError: 'NoneType' object has no attribute '__getitem__'
Alex S
  • 4,726
  • 7
  • 39
  • 67

2 Answers2

3

This is a good question and great job so far! Now, let's start with the obvious. You're sending data to Flask using a GET request but you're actually trying to POST data which is why Python won't have anything in request. Correct me if I'm wrong because this may not be the case, but here is how you debug it:

You should be running flask on a shell somewhere. Here you will see any print statements, so, in jsonSave() add the line print data right after you define it. Also, you are retrieving the data wrongly for two reasons. First the syntax is as follows:

jsonData = request.get_json()
data = jsonData["data"]

Also, if you are expecting a status response, it might be worth it to respond with JSON, and a boolean at that:

return json.dumps(True)

Now the front-end. Before sending the data to the server, simply print it to the console. Add this line immediately above the ajax call:

console.log(JSON.stringify({data: hot.getData()}));

This should be enough for you to debug both sides. After seeing that and making the changes I suggested you should have a much better understanding of what's wrong but if not, please update your question with the outputs you think are relevant (for example, if python is printing an empty line when trying to print data).

EDIT:

To be able to receive parameters the way you're setting your route is not correct. Also why do you have save.json? Instead, try this:

@app.route('/json/save/<well_id>', methods = ['GET', 'POST'])
@login_required
def jsonSave(well_id):
    jsonData = request.get_json()
    data = jsonData['data'] # this will fail if it is not a POST request
    print 'data:', data
    #Do stuff to load data into Database
    print 'saving', well_id
    return json.dumps(True)

And in your ajax call:

ajax('/json/save/{{well['id']}}', 'POST', JSON.stringify({data: hot.getData()}), function (res) {})

Also, please please post the console logs and print statements. We're still in the dark here.

ZekeDroid
  • 7,089
  • 5
  • 33
  • 59
  • Thanks a lot. I've made your changes but I'm still doing something wrong. When I first load the page it gives me two errors, `GET http://127.0.0.1:5000/index.html 404 (NOT FOUND)` and `Uncaught TypeError: Cannot read property 'querySelectorAll' of null`. When I try to save data, it says `GET http://127.0.0.1:5000/json/save.json/1 500 (INTERNAL SERVER ERROR)` and `Uncaught SyntaxError: Unexpected token <`. Could this have to do with the samples.js file that I copied from an old version of the HandsOnTable website because it was no longer hosted there? – Alex S May 12 '15 at 19:48
  • I was using [this jsFiddle](http://jsfiddle.net/api/post/library/pure/) as a point of reference, you can see in there that it includes ``. That file is no longer there, but I got an archived version from the way back machine and used that. Not sure if that could be the source of some of the problems? – Alex S May 12 '15 at 20:40
  • ok let's see. First off, from the updated question you're doing two things wrong. One is that your ajax call is still doing a GET request so the third argument is ignored, and second, in your Flask code you're not actually getting any parameters, see my updated response. – ZekeDroid May 13 '15 at 00:35
  • The first error, the 404, is a Flask error. You're doing something wrong because there's no route for '/index.html'. The querySelectorAll error I have no idea because I don't see anywhere in your code where you're using it. And the syntax error for the < token is related to theway you're loading the handsontable javascript; most likely you're loading it in the wrong place. – ZekeDroid May 13 '15 at 00:37
  • Hmm ok. I've added the console outputs in the question. Basically it seems like `jsonData` is set to None which causes the function to fail. – Alex S May 13 '15 at 17:50
  • Yeah, so you found the bug, now you gotta figure out how to fix it. Start by trying to send something simpler to the backend. Maybe just JSON.stringify({"test":"hello"}). I think you might be having a json coding issue. – ZekeDroid May 13 '15 at 18:06
2

To solve this problem, I started over. I included these javascript files:

<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<link rel="stylesheet" media="screen" href="http://handsontable.com/dist/handsontable.full.css">
<script src="http://handsontable.com/dist/handsontable.full.js"></script>

I removed the samples.js file that I got from the old jsfiddle. I think it was messing things up.

As per ZekeDroid's suggestion, I changed the flask save function to this:

@app.route('/json/save/<int:well_id>', methods = ['GET', 'POST'])
@login_required
def jsonSave(well_id):
    w = Well.query.get(well_id)
    jsonData = request.get_json()
    print 'jsonData:', jsonData
    data = jsonData['data']
    print 'data:', data

    #Load Data dictionary into the database.

    return json.dumps(True)

I replaced the jquery save function with this:

Handsontable.Dom.addEvent(save, 'click', function() {
    // save all cell's data
    var arr = { data: hot.getData()};
    $.ajax({
    url: '/json/save/{{well['id']}}',
    type: 'POST',
    data: JSON.stringify(arr),
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    async: true,
    success: function(msg) {
        console.log(msg);
      }
      });
});

I couldn't get the save function from the handsontable tutorial (here) to work, so I used the accepted answer here instead. I used POST because if I understand correctly, it doesn't have a size limit like GET does, and I'm going to have some pretty big arrays being sent.

Removing the samples.js file broke the load function as well, so I replaced that with this:

Handsontable.Dom.addEvent(load, 'click', function() {
    var arr = { data: []};
    $.ajax({
    url: '/json/load/{{well['id']}}',
    type: 'GET',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(arr),    
    dataType: 'json',
    async: true,
    success: function(msg) {
        hot.loadData(msg.data);
        console.log('Loaded:');
        console.log(msg.data);
    }
    });
});

Which is obviously very similar to the save function. Thanks for all the help ZekeDroid.

Alex

Community
  • 1
  • 1
Alex S
  • 4,726
  • 7
  • 39
  • 67