How to create a webapp with Python?

Christina
Level 1
How to create a webapp with Python?

Hello,

 

I want to build a webapp that calls a model i have trained and displays the result on the HTML fornt end. I have semi-built the HTML  and Python components but I am unsure how to connect these two via JavaScript.

Any help would be much appreciate.

 

0 Kudos
14 Replies
lpkronek
Dataiker

Hello Christina, 

We have an introductory tutorial here on the topic here:  https://academy.dataiku.com/latest/tutorial/webapps/standard.html#load-data-in-the-python-backend. 

 

Do not hesitate to come back with additional questions if needed. 

LPK
0 Kudos
Christina
Level 1
Author

Hello 

Thank you very much for your reply. I have been reading the tutorial but still my code does not work ๐Ÿ˜ž
 
What I am trying to do is to have a simple input text where the user types some text and they press the button, the text is sent to the Python code which calls a model to get a prediction on that text. I know how to call the model using Dataiku API (I am ok with Python) and do the HTML but i struggle to link these two via JavaScript.
 
Here is my HTML code:
<link rel="stylesheet" href="/static/public/styles/1.0.0/dku-styles.css" />
<h1>Prediction demo</h1>
<div class="fetch-dataset-container">
        <section class="fetch-dataset-form">
            <form id="form-prediction" novalidate>
                <div class="field">
                    <label for="description">Item's description</i></label>
                    <input type="text" name="description" placeholder="this is a test" id="description">
                </div>
                <button type="button" class="main-blue-button" id="predict-button">Predict</button>
            </form>
        </section>
    </div>
<div>
    <section class="fetch-prediction-results">
       <span id="message">Results will appear here.</span>
    </section>
</div>
 
Here is the JavaScript code:
let predictButton = document.getElementById('predict-button');
let descriptionText = document.getElementById('description');
let messageContainer = document.getElementById('message');
let selectedDescription = {};

function displayMessage(messageText, messageClassname) {
    messageContainer.innerHTML = messageText;
    messageContainer.className = '';
    if (messageClassname && messageClassname.length > 0) {
        messageContainer.className = messageClassname;
    }
}

function clearMessage() {
    displayMessage('');
}

function displayFailure() {
    displayMessage('Error occured', 'error-message');
}

function displayPrediction(dataFrame) {
    let columnsNames = dataFrame.getColumnNames();
    let line = '------------------------------';
    let text = selectedDescription + '\n'
        + line + '\n'
        + dataFrame.getNbRows() + ' Rows\n'
        + columnsNames.length + ' Columns\n'
        + '\n' + line + '\n'
        + 'Columns names: \n';
    columnsNames.forEach(function(columnName) {
        text += columnName + ', ';
    });
    displayMessage(text);
}

predictButton.addEventListener('click', function(event) {
    $.getJSON(getWebAppBackendUrl('/first_api_call'), function(data) {
        clearMessage();
        selectedDescription.name = document.getElementById('description').value;
        dataiku.fetch(selectedDescription.name, function(dataFrame) {
            selectedDescription.dataFrame = dataFrame;
            displayPrediction(dataFrame);
        }, function() {
            displayFailure();
        });
        const output = $('<pre />').text('Backend reply: ' + JSON.stringify(data));
       $('body').append(output)
    };
    return false;
});
 
And this is my Python code, it is only a test at the moment just trying to call a dataset which I have enabled from the Settings:
@app.route('/First_api_call')
def first_api_call():
    max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500
    mydataset = dataiku.Dataset("test_dataset")
    mydataset_df = mydataset.get_dataframe(sampling='head', limit=max_rows)
    # Pandas dataFrames are not directly JSON serializable, use to_json()
    data = mydataset_df.to_json()
    return json.dumps({"status": "ok", "data": data})
 
 
Please any help would be much appreicated.
 
Many thanks
Christina
0 Kudos
lpkronek
Dataiker

Hi Christina, 

 

I quickly tested your code and I think I have spotted 2 small typos in your JS  code : 

  •  The parameter of the getWebBackendUrl function needs to match the parameter @app.route instruction in the python code
  • You have a missing ")"  in your addEventListener (before the return false) 

Here is what I get : 

predictButton.addEventListener('click', function(event) {
    $.getJSON(getWebAppBackendUrl('/First_api_call'), function(data) {
        clearMessage();
        selectedDescription.name = document.getElementById('description').value;
        dataiku.fetch(selectedDescription.name, function(dataFrame) {
            selectedDescription.dataFrame = dataFrame;
            displayPrediction(dataFrame);
        }, function() {
            displayFailure();
        });
        const output = $('<pre />').text('Backend reply: ' + JSON.stringify(data));
       $('body').append(output)
    });
    return false;
});

 

 

 

In order to debug your javascript tool, you can use the developer tools of your browser where you will be able to see the error message in the console and much more. 

 

 

LPK
0 Kudos
Christina
Level 1
Author
Hi  lpkronek
 
Thank you very much for your reply. I made the 2 changes you mention but my code still does not work ๐Ÿ˜ž
 
Any help would be very much appreciated.
 
Many thanks
Christina
0 Kudos
lpkronek
Dataiker

Can you share more about the error that you see ? 

LPK
0 Kudos
Christina
Level 1
Author

So I changed my Python code to the dollowing:

@app.route('/First_api_call')
def first_api_call():
max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500

data = {'First Column Name': ['First value', 'Second value'],
'Second Column Name': ['First value', 'Second value']
}
mydataset_df = pd.DataFrame (data, columns = ['First Column Name','Second Column Name'])
data = mydataset_df.to_json()
return json.dumps({"status": "ok", "data": data})

 

The method creates and returns a simple dataframe which I would like to see it displayed on the front end when i click the Predict button. However when i click the button i get the following error:

405 (Request method 'POST' not supported)

please see error.png for more details

 

And when i click on this error i get what you see on error2.png

 

am i doing something wrong?

0 Kudos
lpkronek
Dataiker

The error that you are seeing is coming from the JS code. 

it contains a call to :

 dataiku.fetch(selectedDescription.name, function(dataFrame) {
            selectedDescription.dataFrame = dataFrame;
            displayPrediction(dataFrame);
        }, function() {
            displayFailure();
        });

 

This call is trying to get the data from the dataset that has been inputted in the text box. 

You probably want to remove this call has your goal seems to render the data return by the python code. 

Also, when using datasets in a  webapp, you need to grant access to them. You will find a security button on the settings of the webapp. 

 

 

LPK
0 Kudos
Christina
Level 1
Author

Thank you lpkronek,

I have removed these lines as you suggested.

Now when i click on the Predict button, although i dont see any error on the Console, i dont see the dataframe displayed on the page. The python code is executed though as i see the following on the Console tab:

2020-02-21 15:08:26,863 INFO 127.0.0.1 - - [21/Feb/2020 15:08:26] "GET /First_api_call HTTP/1.1" 200 -

 

I attach my code again. Your help is much appreciated

 

HTML

<div class="fetch-dataset-container">
<section class="fetch-dataset-form">
<form id="form-dataset" novalidate>
<div class="field">
<label for="description">Item's description</i></label>
<input type="text" name="description" placeholder="i.e. Testing of new circuits as per specification" id="description">
</div>
<button type="button" class="main-blue-button" id="predict-button">Predict</button>
</form>
</section>
</div>
<div>
<section class="fetch-dataset-results">
<span id="message">Results will appear here.</span>
</section>
</div>

 

JS

let predictButton = document.getElementById('predict-button');
let descriptionText = document.getElementById('description');
let messageContainer = document.getElementById('message');
let selectedDescription = {};

function displayMessage(messageText, messageClassname) {
messageContainer.innerHTML = messageText;
messageContainer.className = '';
if (messageClassname && messageClassname.length > 0) {
messageContainer.className = messageClassname;
}
}

function clearMessage() {
displayMessage('');
}

function displayFailure() {
displayMessage('Error occured', 'error-message');
}

function displayPrediction(dataFrame) {
let columnsNames = dataFrame.getColumnNames();
let line = '------------------------------';
let text = selectedDescription + '\n'
+ line + '\n'
+ dataFrame.getNbRows() + ' Rows\n'
+ columnsNames.length + ' Columns\n'
+ '\n' + line + '\n'
+ 'Columns names: \n';
columnsNames.forEach(function(columnName) {
text += columnName + ', ';
});
displayMessage(text);
}

predictButton.addEventListener('click', function(event) {
$.getJSON(getWebAppBackendUrl('/First_api_call'), function(data) {
clearMessage();
displayPrediction(data);
});
return false;
});

 

Python

@app.route('/First_api_call')
def first_api_call():
max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500data = {'First Column Name': ['First value', 'Second value'],
'Second Column Name': ['First value', 'Second value']
}
mydataset_df = pd.DataFrame (data, columns = ['First Column Name','Second Column Name'])
data = mydataset_df.to_json()
print (mydataset_df)
return json.dumps({"status": "ok", "data": data})

0 Kudos
Christina
Level 1
Author

An update....

 

 I am calling the displayPrediction() method from the predictButton.addEventListener() and the error i get says:

dataFrame.getColumnNames is not a function at displayPrediction

 

Is it something wrong with Dataiku API?

0 Kudos
lpkronek
Dataiker

Hello Christina, 

 

The data that you receives in your event listener is the json document that your python code return from first_api_call()  and not a dataiku object.  You need to adapt the displayPrediction function to process the json document. 

LPK
0 Kudos
Christina
Level 1
Author

Hello,

 

I managed to display what i get from Python ๐Ÿ™‚

Do you know how can i pass a value from the front end to my Python code via Javascript?

 

Many thanks

Christina

0 Kudos
Christina
Level 1
Author

Hello @lpkronek 

 

Apologies for asking so many questions. I read the Dataiku literature and i have modified my code as per below

 

JS

let descriptionText = document.getElementById('description');

function displayMessage(messageText, messageClassname) {
messageContainer.innerHTML = messageText;
messageContainer.className = '';
if (messageClassname && messageClassname.length > 0) {
messageContainer.className = messageClassname;
}
}

function clearMessage() {
displayMessage('');
}

predictButton.addEventListener('click', function(event) {
$.getJSON(getWebAppBackendUrl('First_api_call'), {description:descriptionText})
.done(
function(data) {
clearMessage();
displayMessage(data.description);
}
);
return false;
});

 

Python

from flask import request

@app.route('/First_api_call')
def first_api_call():
userdescription = str(request.args.get("description"))
max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500return json.dumps({"status":"ok",  "description":userdescription})

 

And in my HTML page i have got an input with id='description'

 

When i click the predict button i get the following error:

Uncaught RangeError: Maximum call stack size exceeded as per photo attached

 

Any help is much appreciated

 

Kind regards,

Christina

0 Kudos
lpkronek
Dataiker

Hello, 

It looks like a JS problem. Do not hesitate to look at dedicated resources on the topic to get more accurate support.

I'm under the impression that in your variable descriptionText you have the whole element and not the text value. In addition, you need to add quotes around "description".  Your  call to your python backend should look something like this : 

$.getJSON(getWebAppBackendUrl('First_api_call'), {"description" : descriptionText.value})

 

$.getJSON(getWebAppBackendUrl('First_api_call'), {"description" : descriptionText.value})

LPK
0 Kudos
Christina
Level 1
Author

Hi @lpkronek ,

 

That was the probelm, thank you so much for your help.

I think i can take it from here.

 

again, thank you very much for your support!

 

Kind regards,

Christina

0 Kudos