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 flaskfrom flask import render_template, requestVOWELS = ['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 dictionaryif 's' in request.args:s = request.args['s']lower_s = s.lower()num_vowels = 0for 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 flaskfrom flask import render_template, requestVOWELS = ['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 = 0for 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.py3 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, sqlite3from flask import render_template, requestfrom flask import send_from_directoryfrom 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__ <br> app.run()<br># try to change port using app.run(port=5001) if doesn’t work` |
|---|
| Attribute | request.args | request.form | request.files |
|---|---|---|---|
| Contents | Dictionary of field names & their associated values from query portion of URL | Dictionary of field names & their associated values | Dictionary of file upload names & their associated FileStorage objects |
| HTTP method | Usually GET; Also works with POST if URL has query portion | POST only | POST only |
| Typical use | Reading form data submitted using GET | Reading from data submitted using POST | Saving files submitted using POST |
| Other | Form must specify enctype=”multipart/form-data” |
# p22_server_with_flask.pySame function as p01_server_without_flask.py import randomimport flaskapp = flask.Flask(__name__)import randomimport socket# list of possible coloursCOLOURS = [‘red’, ‘orange’, ‘blue’, ‘purple’, ‘#C0C000’, # dark yellow ‘#00C000’ # dark green ] # list of possible messagesMESSAGES = ['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.cssp {border: 5px solid {{ border_colour }};color: {{ text_colour }};font-size: 72px;padding: 20px;} |
