Flask 3

Processing from data

  • Get user by extracting variables from request URLs
  • But more common for users to provide input to a web application by filling in a HTML form and submitting it
  • When a HTML form is submitted, the browser collects all the input as key-value pairs and encodes it into a single string. This string is sent to server using a HTTP request. Depending on how the HTML form is configured, either a GET or POST request is made
  • GET Requests
  • Default behaviour for HTML forms
  • Encoded form data is visible in the query portion of the request URL
  • E.g. URL http://example.com/hello?name=bala&age=18 contains query with 2 key-value pairs: one for the key ‘name’ & another for ‘age’
  • Parsing key-value pairs encoded in query strings can be tedious and error-prone
  • If a query string is present, Flask does this parsing for us and lets us access it as a dictionary from a request object that can be imported from the flask module
template 8: templates/form_with_get.html
<!DOCTYPE html>
<html>
<head><title>GET Form</title></head>
<body>
<form action="{{ url_for('process_with_get') }}"> # generates path that the form data will be submitted to
<p>Input s: <input name="s"></p>
<p><input type="submit"</p>
</form>
</body>
</html>
template 9: templates/analysis_results.html
To display the results of processing
In this case, display no. of vowels & no. of words in the submitted name
<!DOCTYPE html>
<html>
<head><title>String Analysis</title></head>
<body>
<p>You entered s: {{ s }}</p>
<p>This string has
{{ num_vowels }} vowel(s) and
{{ num_words }} word(s).
</p>
</body>
</html>
p19_analysis_with_get.py
E.g.

Can also manually visit http://127.0.0.1:5000/process/ without filling in the form ⇒ then request.args dictionary will not include a value for ‘s’ and the error message “No form data found!” is returned instead
import flask
from flask import render_template, request
VOWELS = ['a', 'e', 'i', 'o', 'u']
app = flask.Flask(__name__)
@app.route('/')
def form():
return render_template('form_with_get.html')
@app.route('/process/')
def process_with_get():
# the submitted value of s is accessed via request.args dictionary
if 's' in request.args:
s = request.args['s']
lower_s = s.lower()
num_vowels = 0
for vowel in VOWELS:
num_vowels += lower_s.count(vowel)
num_words = len(s.split())
return render_template('analysis_results.html',
s=s, num_vowels=num_vowels, num_words=num_words)
return 'No form data found!'
if __name__ == "__main__":
app.run()
  • Disadvantages of submitting form using a GET request
  • The submitted form data is recorded in the resulting URL ⇒ anyone can view browser history to obtain form data sent
  • GET requests are not supposed to make change to server’s data ⇒ if use data submitted with a GET request to add, delete, or update data from a database, we are not following the HTTP standard
  • Some browsers and server software limit the length of URLs ⇒ overly long form data submitting using GET may risk getting truncated
  • POST Requests
  • Set method attribute of the <form> tag to “post” or “POST”
  • Using a diff HTTP method also lets us distinguish between requests from users clicking a link or entering the URL in an address bar, vs requests from users submitting a form ⇒ use the same URL for both displaying & processing the form
template 10: template/form_with_post.html
Removed action attribute so the form is submitted to the same URL used to generate the form
<!DOCTYPE html>
<html>
<head><title>POST Form</title></head>
<body>
<form method="post">
<p>Input s: <input name="s"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
p20_analysis_with_post.py
Combines both routes and distinguish between the 2 situations by looking at the HTTP method used, determined via request.method
Value of s entered is NOT visible in the resulting URL; URL displayed in address bar remains the same
import flask
from flask import render_template, request
VOWELS = ['a', 'e', 'i', 'o', 'u']
app = flask.Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('form_with_post.html')
# handle POST requests with incomplete / invalid form data (error msg)
if 's' in request.form:
s = request.form['s']
lower_s = s.lower()
num_vowels = 0
for vowel in VOWELS:
num_vowels += lower_s.count(vowel)
num_words = len(s.split())
return render_template('analysis_results.html',
s=s, num_vowels=num_vowels, num_words=num_words)
return 'No form data found!'
if __name__ == "__main__":
app.run()

File and image uploads

  • <input> tag can have its type attribute set to “file” ⇒ upload files for submission with the form
  • The enclosing <form> must also be configured to use POST and include an additional enctype attribute that is set to “multipart/form-data” (i.e. >=1 sets of data are combined in a single body)
template 11: templates/form_with_file_upload.html
Form for uploading photos
Link for seeing all uploaded photos
<!DOCTYPE html>
<html>
<head><title>Photo Upload</title></head>
<body>
<form method="post" enctype="multipart/form-data">
<p>Photo: <input name="photo" type="file"></p>
<p><input type="submit"></p>
</form>
<p><a href=" {{ url_for('view') }}">View photos</a></p>
</body>
</html>
template 12: templates/view_file_uploads.html
To view the uploaded photos
<!DOCTYPE html>
<html>
<head><title>View Photos</title></head>
<body>
{% for photo in photos %}
<img src="{{ url_for('get_file', filename=photo) }}"
alt="{{ photo }}">
{% endfor %}
<p><a href="{{ url_for('home') }}">Home</a></p>
</body>
</html>
# p21_file_uploads.py
3 routes:
For uploading photos
For viewing all uploading photos
Generates HTML document that displays all photos on a single page
For retrieving the image data of an uploaded photo given its filename
Use send_from_directory() to avoid security issues from using paths or filenames provided by users
Call it with the name of a subfolder that the requested file must be stored in, & the file’s filename
Return the result of this call directly
Initialise and use a simple SQLite database to keep track of the photos uploaded
Before running the program, create an empty uploads subfolder to store the uploaded photos
Files are not accessed from request.form but from a separate request.files dictionary ⇒ each value in this dictionary is a file object with a filename attribute + a save() method that accepts a file path and writes the submitted file onto the server’s file system using the provided file path
secure_filename() returns a modified filename that replaces all special characters so it can be safely treated like a normal filename
Prevent user from using special folder names as file paths
E.g. .. to access parent folders containing source code or the server’s configuration files
Alternative to configuring a new route to access each uploaded photo: save our uploads in the static subfolder & make use of the existing ‘static’ route that Flask provides by default
But don’t let users overwrite files that they are not supposed to
import flask, os, sqlite3
from flask import render_template, request
from flask import send_from_directory
from werkzeug.utils import secure_filename
if not os.path.isfile('db.sqlite3'):
db = sqlite3.connect('db.sqlite3')
db.execute(‘CREATE TABLE photos(photo TEXT)’)
db.commit()
db.close()
app = flask.Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method 'POST' and \\
request.files and 'photo' in request.files:
`# save file`
`photo = request.files['photo']`
`filename = secure_filename(photo.filename)`
`# form a file path that is guaranteed to be in our uploads subfolder`
`path = os.path.join('uploads', filename)`
photo.save(path)
`# add filename to database`
`db = sqlite3.connect('db.sqlite3')`
db.execute('INSERT INTO photos(photo) VALUES(?)',
(filename,))
db.commit()
db.close()
`return render_template('form_with_file_upload.html')`
`@app.route('/view')`
`def view():`
`db = sqlite3.connect('db.sqlite3')`
`cur = db.execute('SELECT photo FROM photos')`
`photos = []`
`for row in cur:`
photos.append(row\[0\])
db.close()
`return render_template('view_file_uploads.html',`
`photos=photos)`
`@app.route('/photos/')`
`def get_file(filename):`
`return send_from_directory('uploads', filename)`
`if __name__
main’:<br> app.run()<br># try to change port using app.run(port=5001) if doesn’t work`
Attributerequest.argsrequest.formrequest.files
ContentsDictionary of field names & their associated values from query portion of URLDictionary of field names & their associated valuesDictionary of file upload names & their associated FileStorage objects
HTTP methodUsually GET; Also works with POST if URL has query portionPOST onlyPOST only
Typical useReading form data submitted using GETReading from data submitted using POSTSaving files submitted using POST
OtherForm must specify enctype=”multipart/form-data”
# p22_server_with_flask.py
Same function as p01_server_without_flask.py
import random
import flask
app = flask.Flask(__name__)
import random
import socket
# list of possible colours
COLOURS = [
‘red’, ‘orange’, ‘blue’, ‘purple’,
‘#C0C000’, # dark yellow
‘#00C000’ # dark green
]
# list of possible messages
MESSAGES = ['Hello, World!', 'Computing is Fun', 'Jinjjaaa']
@app.route('/CSS')
def css():
border_colour = random.choice(COLOURS)
text_colour = random.choice(COLOURS)
return(
flask.render_template(‘example.css’,
border_colour=border_colour,
text_colour=text_colour),
{ ‘Content-Type’: ‘text/css’ }
)
@app.route('/')
def html():
msg = random.choice(MESSAGES)
return flask.render_template('example.html', msg=msg)
if __name__ == '__main__':
app.run()
template 13: templates/example.html
<!DOCTYPE html>
<html>
<head>
<title>{{ msg }}</title>
<link rel="stylesheet" href="{{ url_for('css') }}">
</head>
<body>
<p>{{ msg }}</p>
</body>
</html>
template 14: templates/example.css
p {
border: 5px solid {{ border_colour }};
color: {{ text_colour }};
font-size: 72px;
padding: 20px;
}