Form Handling with Lambda-AWS - python

I am trying to build a Lambda that displays a simple html form, where you fill your name (Mary for example) and the output should be "welcome Mary" but I dont know how to do it without .php
some information:
1.i am using python.
2.the first if (==GET) works fine.
3.action="lambda's URL", omitted in the code below.
4.my problem is on the second if(==POST).I dont know how to collect form data after submitting my HTML form.
Thanks in advance;)
here is the code:
import json
def lambda_handler(event, context):
if event['requestContext']['http']['method'] == 'GET':
content='''
<html>
<body>
<form action="my lambda's URL here" method="POST">
Name: <input type="text" name="fname"><br>
<input type="submit">
</form>
</body>
</html>
'''
if event['requestContext']['http']['method'] == 'POST':
content='''
<html>
<body>
<p>
"I would like to see:"Welcome Mary" here but i don't know how!
</p>
</body>
</html>
'''
# TODO implement
response = {
"statusCode": 200,
"headers": {
'Content-Type': 'text/html',
},

If your code you're accessing parts of the request like the HTTP method.
The event actually holds a heap of different bits of information about the request. You can see an example in the Lambda developer guide.
I would suggest printing out the entire event to start with while you get used to the format. Then work out how to access the form data.

Related

I am getting a `KeyError` when I receive a request to my API

I am building a dictionary app with Flask where users can add new words, I am trying to request the word from the word input , I am having issues with the POST request, the error I am receiving on my terminal is this:
line 50, in add_word
word = req['word']
keyError:'word'
and this is how I wrote the code in my app.py file:
#app.route('/word', methods= ['POST'])
def add_word():
req = request.get_json()
word = req['word']
meaning = req['meaning']
conn = mysql.get_db()
cur = conn.cursor()
cur.execute('insert into word(word, meaning) VALUES (%s, %s)',(word, meaning))
conn.commit()
cur.close()
return json.dumps("success")
here is the json in my JavaScript file, I am posting to my flask app:
$('#word-form').submit(function() {
let word = $('word').val();
let meaning = $('meaning').val();
$.ajax({
url: '/word',
type: 'POST',
dataType: 'json',
data : JSON.stringify({
'word': word,
'meaning': meaning
}),
contentType: 'application/json, charset = UTF-8',
success: function(data) {
location.reload();
},
error: function(err) {
console.log(err);
}
})
here is the Html page:
<div class="div col-md-2 sidenav">
All words
Add New
<div>
<form action="javascript:0" id="word-form">
<div class="form-group">
<label for="word">Word:</label>
<input type="text"
class="form-control"
name="word"
id="word"
placeholder="Type in the word here:"
required>
</div>
<div class="form-group">
<label for="Meaning">Meaning:</label>
<textarea class="form-control" id="meaning"
placeholder="enter the meaning here: " required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block btn-lg" id="submit">Submit</button>
<button type="button" class="btn btn-warning btn-block btn-lg" id="cancel">Cancel</button>
</form>
</div>
</div>
<div class="div col-md-10 main">
<table style="border: 2px;">
<thead>
<tr>
<th>SN</th>
<th>Word</th>
<th>Meaning</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for word in words %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ word['word'] }}</td>
<td>{{ word['meaning'] }}</td>
<td><button class="btn btn-sm btn-success btn-block edit" id="{{word['id']}}">Edit</button></td>
<td><button class="btn btn-sm btn-danger btn-block delete" id="{{word['id']}}">Delete</button></td>
</tr>
{% else %}
<tr>
<td colspan="3">The dictionary has no words at the moment, please come bay later</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
Things seem to be in a confused state in the client code, and potentially the application architecture in general.
There are two general approaches to designing web apps that impacts how you create routes and build requests. One approach is the AJAX-based single page app architecture that loads one HTML skeleton, then uses JS (jQuery here) to make AJAX requests to a JSON API and injects the response data into the page using JS instead of page refreshes and navigations. Since you don't have a client-side router, this doesn't qualify as a SPA, but it's worth understanding to provide context on your design.
On the other hand, you can use HTML form submissions (<form action="/path/to/resource" method="POST">) and render_template to display new pages with a browser refresh for all form submissions.
The code here is somewhere in the middle, which is potentially fine (you can use AJAX for certain events and form submissions but mostly rely on full-navigation templates for routes). But it's important to be clear on the request-response workflow you're adopting so the design makes sense and can be debugged.
Here are few oddities/inconsistencies in your current design:
return json.dumps("success") is not really JSON as it seems like you want it to be--use jsonify and a dictionary or list, e.g. jsonify({"status": "success"}; it's customary for JSON routes to return JSON responses if they aren't rendering templates or redirecting.
The client ignores the JSON response and calls location.reload. If you're just planning on reloading and you have no special client processing to do, there's not much point in using AJAX or JSON here--just submit the form to the backend and redirect to the template or static HTML page you want to show next. No client-side JS involved. Redirect to an error page or render a template with errors shown on the form on error.
Links with href="#" are poor practice. Better to use buttons if you're adding JS to these handlers and you don't want them to trigger navigation. This is semantically more appropriate and doesn't hijack the URL.
<form action="javascript:0" id="word-form"> looks like it's trying to prevent the form submission, but all this does is replace the page content with the character "0". I can't imagine how this is useful or desirable. Submitting a form to a JSON route can produce the error you're seeing--another sign of confusion about which architecture you're following. Use event.preventDefault() (add the event parameter to the callback to .submit()) to prevent the form submission from refreshing the page.
After you've prevented the page refresh, you can debug the AJAX request.
When a route is complaining about missing keys, consider that objects with keys pointing to undefined disappear when serialized as JSON (undefined is not a thing in JSON):
const word = undefined;
const foo = 42;
const bar = "baz";
console.log({word, foo, bar}); /* =>
{
"word": undefined,
"foo": 42,
"bar": "baz"
}
*/
console.log(JSON.stringify({
word,
foo,
bar,
})); // => {"foo":42,"bar":"baz"}
If you add a console.log to see if your values are there (or print the JSON on the backend route before indexing into it), these values aren't defined:
let word = $('word').val();
let meaning = $('meaning').val();
console.log(word, meaning); // <-- undefined, undefined
Why? The reason is that these selectors are missing the # symbol prefix to denote an id. Without it, jQuery looks for <word></word> and <meaning></meaning> HTML elements that don't exist.
Change these lines to:
const word = $('#word').val();
const meaning = $('#meaning').val();
and now your request body should be ready to send.
Next problem: $.ajax's dataType key specifies the response type, not the request type. Use dataType: "json" to specify the appropriate request header to trigger the Flask handler to JSON parse the request body.
After these changes, things should work, with the caveat that it might be time for a rethink of your overall design, or at least a redesign of this route workflow.
A word of advice: work slowly and test all of your assuptions at each step. The code here shows many errors that are hard to debug because they're stacked on top of each other. Isolate and validate each behavior in your app. For example, when adding the jQuery submit handler and collecting the form values, print them to make sure they're actually there as you expected.
In case you're stuck, here's minimal, complete, runnable code you can reference.
app.py:
from flask import (
Flask, jsonify, render_template, request, url_for
)
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.post("/words/")
def words():
payload = request.get_json()
word = payload.get("word")
meaning = payload.get("meaning")
if word is None or meaning is None:
return (jsonify({
"error": "missing required keys `word` or `meaning`"
}), 400)
# handle db operation and report failure as above
return jsonify({"status": "success"})
if __name__ == "__main__":
app.run(debug=True)
templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<form id="word-form">
<div>
<label for="word">Word:</label>
<input
name="word"
id="word"
placeholder="Type in the word here:"
required
>
</div>
<div>
<label for="meaning">Meaning:</label>
<textarea
name="meaning"
id="meaning"
placeholder="enter the meaning here: "
required
></textarea>
</div>
<button type="submit">Submit</button>
</form>
<script>
$('#word-form').submit(function (e) {
e.preventDefault();
const word = $('#word').val();
const meaning = $('#meaning').val();
console.log(word, meaning);
$.ajax({
url: "{{url_for('words')}}",
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({word, meaning}),
success: function (data) {
console.log(data);
},
error: function (err) {
console.error(err);
}
});
});
</script>
</body>
</html>
See also: How to get POSTed JSON in Flask?
Here are a few additional notes that are somewhat tangential to the main issue but have to be mentioned.
You have <label for="Meaning"> but no name="meaning" element to shift focus to when clicked.
It's another antipattern to put ids on everything promiscuously. Only add ids to elements when they must have one because you're using it for something specific. Prefer classes, especially for styling.
On the backend, the code here is unsafe:
req = request.get_json()
word = req['word']
meaning = req['meaning']
If your client gives a bad request with missing values, you should detect that and return a 400/422 response (or similar) rather than crashing.
For example (from the above code snippet):
req = request.get_json()
word = req.get("word")
meaning = req.get("meaning")
if word is None or meaning is None:
return (jsonify({
"error": "missing required keys `word` or `meaning`"
}), 400)
Similarly, don't assume the database operation will succeed. Always check for errors and return an appropriate response to the client.
Resources are usually plural, not singular: words, users, posts.

Flask how to make html page show up?

Okay so I will just have some reading to do. I don't expect anyone to answer anymore. Thanks for everybody that took the effort to reply.
I have followed the following guide to create a simple python app: https://guillaumegenthial.github.io/serving.html
edit:
Okay retrospectively I should have read more stuff before asking this question. Let me try to rephrase what I am trying to do.
I am trying to access the below defined html page. There I want to input text in a textbox and then process that text in the api function defined in flask. The results I then want to output inside the html page.
I tried to find some more ressources on this, but I am new to html and flask and am struggeling.
Any tips would be appriciated.
My app.py file:
from flask import Flask, request, jsonify
from flask_cors import CORS
from serve import get_model_api
app2 = Flask(__name__)
CORS(app2) # needed for cross-domain requests, allow everything by default
model_api = get_model_api()
# default route
#app2.route('/')
def index():
return "Index API"
# HTTP Errors handlers
#app2.errorhandler(404)
def url_error(e):
return """
Wrong URL!
<pre>{}</pre>""".format(e), 404
#app2.errorhandler(500)
def server_error(e):
return """
An internal error occurred: <pre>{}</pre>
See logs for full stacktrace.
""".format(e), 500
# API route
#app2.route('/api', methods=['POST'])
def api():
input_data = request.json
output_data = model_api(input_data)
response = jsonify(output_data)
return response
if __name__ == '__main__':
app2.run(host='0.0.0.0', port=81, debug=True)
The html file:
<body>
<div class="container col-lg-6">
<div style="padding-bottom: 0.5cm">
<div class="card text-center bg-light">
<div class="card-body" style="padding-bottom: 0.2cm">
<input class="card-title form-control" type="text" id="input" name="input" placeholder="Input sentence"/>
<button class="card-text btn btn-outline-primary" id="btn">Find Entities</button>
<button class="card-text btn btn-outline-primary" id="btn2">Get random Tweet</button>
<div class="spinner" id="spinner" style="display: none">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
</div>
<div class="card-footer bg-white">
<pre class="card-text api-pre" style="padding-bottom: 0.2cm">
<div class="item" id="api_input">Nur der HSV </div>
<div class="item" id="api_output">0 0 B-ORG</div>
</pre>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
function api_call(input) {
// hide button and make the spinner appear
$('#btn').toggle();
$('#spinner').toggle();
$.ajax({
url: "0.0.0.0:81/api",
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(input),
success: function( data, textStatus, jQxhr ){
// toggle the spinner and button
$('#btn').toggle();
$('#spinner').toggle();
// fill the html for answer
$('#api_input').html( data.input );
$('#api_output').html( data.output );
$("#input").val("");
},
error: function( jqXhr, textStatus, errorThrown ){
$('#btn').toggle();
$('#spinner').toggle();
$('#api_input').html( "Sorry, the server is asleep..." );
$('#api_output').html( "Try again in a moment!" );
console.log( errorThrown );
},
timeout: 3000 // sets timeout to 10 seconds
});
}
$( document ).ready(function() {
// request when clicking on the button
$('#btn').click(function() {
// get the input data
var input = $("#input").val();
api_call(input);
input = "";
});
});
</script>
By adding /api you are only making a GET request and that is all that is defined:
#app2.route('/api', methods=['POST'])
def api():
input_data = request.json
output_data = model_api(input_data)
response = jsonify(output_data)
return response
If you want the information available for testing, you can just add the GET method like so:
#app2.route('/api', methods=['GET', 'POST'])
However, I think you may want to read up on HTML request methods to see what is the most appropriate type for what you are making the endpoint do.
I'm guessing the tutorial information is expecting a POST for the api, which is common. A simple example of a POST request can be done from most commandline with curl:
curl -X POST MYIP/api -d "{ \"myKey\": \"myValue\"}"
This should return whatever response that is given by the api method.

posting input data from html to python script with ajax

I am wanting to POST a user inputted data in an html file to a Python script with AJAX and have the Python script return it so that it shows up in a specific div in the html file.
HTML
<!DOCTYPE HTML>
<html>
<head>
<title>AJAX Test</title>
<script src="http://code.jquery.com/jquery-3.3.1.js"></script>
<script>
function test()
{
var message = $('input[name=message]').val();
$.ajax({
url: "/cgi-bin/hello.py",
type: "POST",
data: {"text" : message},
success: function(response){
$("#div").html(response);
}
});
};
</script>
</head>
<body>
<form>
Enter Message: <input type="text" name="message">
<input type="submit" value="submit" onclick="test()">
</form>
<div id="div">Default Message</div>
</body>
</html>
Python
#!/home/user/virtualenv/test/3.5/bin/python
import cgi, cgitb
cgitb.enable()
data = cgi.FieldStorage()
print "Content-Type: text/html\n"
print data
When I type a message into the input box and press the submit button, nothing happens. I am new to this so I feel like I am probably not understanding how this works. Any help would be appreciated!
Edit: Console is showing Uncaught TypeError: $.ajax is not a function
Edit 2: The first problem was due to using the slim version of jquery. After fixing that, nothing is happening on the page when I input and click submit.
The problem was that the form was submitting when I clicked the button. The solution was to change the input type to <button value="Submit" onclick="test()">.
The next problem was that python was returning FieldStorage(None, None, [MiniFieldStorage('text', 'blahblah')]). The solution was to access the value by using print (data["text"].value)

python autofill form in a webpage

I am trying to fill a form in a webpage that has a single text box and a send button the html looks like this
<form class="form-horizontal">
<div class="row">
<div class="col-md-12">
<div id="TextContainer" class="textarea-container">
<textarea id="Text" rows="5" maxlength="700" class="form-control remove-border" style="background:none;"></textarea>
</div><button id="Send" class="btn btn-primary-outline" type="button" onclick="SendMessage()" style="margin-top:10px" data-loading-text="Loading..."><span class="icon icon-pencil"></span> Send</button>
</div>
</div>
</form>
I tried to use mechanize to submit the form with this code
import re
from mechanize import Browser
br = Browser()
response=br.open("https://abcd.com/")
for f in br.forms():
if f.attrs['class'] == 'form-horizontal':
br.form = f
text = br.form.find_control(id="Text")
text.value = "something"
br.submit()
The code runs without an error, but no submission is happening , how do I do it?
Here is the SendMessage function
function SendMessage() {
var text = $('#Text').val();
var userId = $('#RecipientId').val();
if (text.trim() === "")
{
$('#TextContainer').css('border-color', 'red');
}
else if (new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+#)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?").test(text))
{
$('#TextContainer').css('border-color', 'red');
$('#message').html("Links are not allowed in messages");
}
else
{
$('#Send').button('loading');
$.ajax(
{
url: '/Messages/SendMessage',
type: 'POST',
cache: false,
data:
{
__RequestVerificationToken: $('<input name="__RequestVerificationToken" type="hidden" value="CfDJ8MQSRebrM95Pv2f7WNJmKQWGnVR66zie_VVqFsquOCZLDuYRRBPP1yzk_755VDntlD3u0L3P-YYR0-Aqqh1qIjd09HrBg8GNiN_AU48MMlrOtUKDyJyYCJrD918coQPG0dmgkLR3W85gV6P4zObdEMw" />').attr('value'),
userId: userId,
text: text
}
});
}
}
I suspect the issue is that the submit button in the HTML form is not of type=submit - so mechanise won't know what to do when you call br.submit(). The fix is to either change the button type on the HTML website, or tell Browser which button to use for submitting the form:
br.submit(type='button', id='Send')
The submit method takes the same arguments as the HTML Forms API, so I recommend taking a look at the documentation for more details.
Update
The problem here seems to be the JavaScript method attached to the button. Mechanize does not support calling JavaScript functions, hence you won't be able to just use the .submit() method to submit the form. Instead, the best option would probably be to read in the SendMessage() JavaScript function, which gets called if someone clicks on the Send button, and translate it to Python manually. In the best case it consists of a simple AJAX POST request which is very easy to implement in Python. Please look here for a related question.
Second Update
Given the new information in your question, in particular the JavaScript function, you can now manually implement the POST request inside your Python script. I suggest the use of the Requests module which will make the implementation much easier.
import requests
data = {
"__RequestVerificationToken": "CfDJ8MQSRebrM95Pv2f7WNJmKQWGnVR66zie_VVqFsquOCZLDuYRRBPP1yzk_755VDntlD3u0L3P-YYR0-Aqqh1qIjd09HrBg8GNiN_AU48MMlrOtUKDyJyYCJrD918coQPG0dmgkLR3W85gV6P4zObdEMw",
"userId": "something",
"text": "something else"
}
response = requests.post("https://example.com/Messages/SendMessage", data=data)
response will now consist of the response which you can use to check if the request was successfully made. Please note that you might need to read out the __RequestVerificationToken with mechanize as I suspect it is generated each time you open the website. You could just read out the HTML source with html_source = br.read() and then search for __RequestVerificationToken and try to extract the corresponding value.
You can give name attribute to your text area like:
<form class="form-horizontal">
<div class="row">
<div class="col-md-12">
<div id="TextContainer" class="textarea-container">
<textarea id="Text" name="sometext" rows="5" maxlength="700" class="form-control remove-border" style="background:none;"></textarea>
</div><button id="Send" class="btn btn-primary-outline" type="button" onclick="SendMessage()" style="margin-top:10px" data-loading-text="Loading..."><span class="icon icon-pencil"></span> Send</button>
</div>
</div>
</form>
Then try this out:
import re
from mechanize import Browser
br = mechanize.Browser()
br.open("https://abcd.com/")
br.select_form(nr=0) #in case of just single form you can select form passing nr=0
br["sometext"] = "something"
response = br.submit()
print(response.read())
If it successfully submits form then you can read your response body.

Problems of GET and POST methods in Python

File name mypage.py
Python code
form = cgi.FieldStorage()
name = form.getfirst('name', 'empty')
id = form.getfirst('id', 'empty')
print """\
Content-Type: text/html\n
<html>
<body>
<p>Name: "%s"</p>
<p>ID: "%s"</p>
</body>
</html>
""" % (name, id)
HTML inside the same file
<form name="frm" method="post" action="mypage.py?id=33">
<input type="text" name="name" value="MyName" />
<input type="Submit" id="btn" value="Send" />
After submitting the form (pressing Send button), I can see this URL with following output
localhost:8000/cgi-bin/mypage.py?id=33
Name: "empty"
ID: "33"
if I change the form method POST to GET
<form name="frm" method="get" action="mypage.py?id=33">
then I can see this URL with following output
localhost:8000/cgi-bin/mypage.py?name=MyName
Name: "MyName"
ID: "empty"
I don't understand why I am not receiving text field value with POST method ? And why I am unable to receive id value in query string with GET method ?
Its simple python page without any framework. BTW I am using "python-bugzilla 0.8.0" downloaded from here but i think my given code is just a simple page and has nothing to do with this package.
Any help would be appreciated.
Thanks in advance,
Your GET is proper:
<form name="frm" method="get" action="mypage.py?id=33">
But your POST is not:
<form name="frm" method="post" action="mypage.py?id=33">
You can't add a GET style variable (?id=33) to the action of your POST. It should be:
<form name="frm" method="post" action="mypage.py">
<input type="hidden" name="id" value="33">
See HTTP Methods: GET vs. POST: "Query strings (name/value pairs) are sent in the URL of a GET request" and "Query strings (name/value pairs) are sent in the HTTP message body of a POST request".
Not adhering to these rules would cause unexpected results such as you are seeing.
Technically, a POST target url should not have GET parameters, so the ?id=33 in the target is invalid. I'm also guessing that it's confusing to the FieldStorage module, that might be why you are getting unexpected results.
You should properly use POST and GET per my other answer. That said, I'm worried about your use of form.getfirst and variable names.
Per the documentation:
FieldStorage.getfirst(name[, default]) - This method always returns only one value associated with form field name. The method returns only the first value in case that more values were posted under such name.
You've named your name variable name which is a silly name. See, lots of names. And your form has a name. And it's a field. Same with ID. You should change your variable names as such:
<form name="MyForm" method="post" action="mypage.py">
<input type="text" name="FullName" value="MyName" />
<input type="text" name="FormID" value="33" />
<input type="Submit" id="btn" value="Send" />
and change your Python as follows:
form = cgi.FieldStorage()
FullName = form.getfirst('FullName', 'empty')
FormID = form.getfirst('FormID', 'empty')
print """\
Content-Type: text/html\n
<html>
<body>
<p>Name: "%s"</p>
<p>ID: "%s"</p>
</body>
</html>
""" % (FullName, FormID)
That's the code for a proper POST and printing of the variables. Does it work?
Thanks for the help.
problem was here.
form = cgi.FieldStorage()
As I've mentioned in comments of your answers that I've printed "form" and here is output: "FieldStorage(None, None, [])".
So, if FieldStorage doesn't have any value then it doesn't matter which function is being used to get the form value. But it was really good information and practical as well.
previously form = cgi.FieldStorage() was declared inside another function which was wrong, that's why FieldStorage was empty.
Here is WRONG code.
def myfunction ():
cgitb.enable()
form = cgi.FieldStorage()
Solution 1:
form = cgi.FieldStorage() shall define inside the run() function and pass this form as parameter of other function to get values of form.
i.e.
def run():
cgitb.enable()
form = cgi.FieldStorage()
myfunction(form)
Now its working
def myfunction (form):
name = form.getfirst('name', 'empty')
id = form.getfirst('id', 'empty')
Solution 2:
form = cgi.FieldStorage() shall define directly inside the main function then don't need to pass it as parameter .
i.e.
if __name__ == "__main__":
cgitb.enable()
form = cgi.FieldStorage()
Now its working too and form is accessible inside the myfunction.
def myfunction ():
name = form.getfirst('name', 'empty')
id = form.getfirst('id', 'empty')
Thanks everybody.

Categories

Resources