Paper 2 Web Applications Answers
These answers correspond to Paper 2 Web Applications Drills.
Verification note: the final model solutions were checked in isolated temporary project directories where practical.
Answer 1: Minimal Route
app.py:
from flask import Flask
app = Flask(__name__)
@app.route("/status")
def status():
return "OK"
if __name__ == "__main__":
app.run()Local test URL:
http://127.0.0.1:5000/statusExpected browser response:
OKMark points:
- imports and creates a Flask app;
- defines route
/status; - returns the exact text
OK; - includes local startup or local test evidence.
Common weak answer:
- returning
"OK"from a normal function without decorating it as a Flask route.
Answer 2: Render Template
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello")
def hello():
return render_template("hello.html", name="Asha")
if __name__ == "__main__":
app.run()templates/hello.html:
Hello {{ name }}Expected rendered text:
Hello AshaMark points:
- creates or uses the
templates/folder; - creates or uses
hello.html; - imports and uses
render_template; - defines route
/hello; - passes
name="Asha"to the template; - displays
Hello Asha.
Common weak answer:
- placing
hello.htmlinstatic/, which is not where Flask looks for templates.
Answer 3: Form Input
app.py:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/greet", methods=["GET", "POST"])
def greet():
if request.method == "POST":
name = request.form["name"]
return render_template("greet.html", name=name)
return render_template("greet.html", name=None)
if __name__ == "__main__":
app.run()templates/greet.html:
{% if name %}
<p>Hello {{ name }}</p>
{% else %}
<form method="post">
<label for="name">Name</label>
<input id="name" name="name" type="text">
<button type="submit">Greet</button>
</form>
{% endif %}Expected rendered text after submitting Asha:
Hello AshaMark points:
- route allows both
GETandPOST; - GET displays a form;
- form input uses
name="name"; - POST reads
request.form["name"]; - submitted value is passed to a template or response;
- result displays
Hello Asha; - field name and Flask key match;
- local test evidence is provided.
Common weak answer:
- using a form input named
usernamebut readingrequest.form["name"].
Answer 4: Validation
from flask import Flask, request
app = Flask(__name__)
@app.route("/join", methods=["POST"])
def join():
username = request.form["username"].strip()
if username == "":
return "Missing username"
return "Joined " + usernameExpected results:
username=" " -> Missing username
username="Chen" -> Joined ChenMark points:
- accepts POST requests;
- reads
username; - strips whitespace;
- rejects empty stripped value;
- returns a clear error message;
- accepts valid input;
- tests blank input;
- tests valid input.
Common weak answer:
- checking only
username == "", which fails to reject spaces such as" ".
Answer 5: Jinja Loop
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/scores")
def scores():
scores = [("Asha", 74), ("Ben", 49), ("Chen", 82)]
return render_template("scores.html", scores=scores)templates/scores.html:
<table>
<tr><th>Name</th><th>Mark</th></tr>
{% for name, mark in scores %}
<tr><td>{{ name }}</td><td>{{ mark }}</td></tr>
{% endfor %}
</table>Expected order:
Asha 74
Ben 49
Chen 82Mark points:
- passes the list of records to the template;
- uses
render_template(); - places the template in
templates/; - uses a Jinja
forloop; - displays each name;
- displays each mark;
- uses a table row or list item for each record in the correct order.
Common weak answer:
- printing the Python list directly instead of looping through records in the template.
Answer 6: SQLite Insert
import sqlite3
from flask import Flask, request
app = Flask(__name__)
DB_NAME = "tasks_exercise.db"
def init_db():
conn = sqlite3.connect(DB_NAME)
conn.execute("""
CREATE TABLE IF NOT EXISTS Task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT
)
""")
conn.execute("DELETE FROM Task")
conn.commit()
conn.close()
@app.route("/add", methods=["POST"])
def add():
title = request.form["title"]
conn = sqlite3.connect(DB_NAME)
conn.execute("INSERT INTO Task (title) VALUES (?)", (title,))
conn.commit()
conn.close()
return "stored"
def get_titles():
conn = sqlite3.connect(DB_NAME)
rows = conn.execute("SELECT title FROM Task ORDER BY id").fetchall()
conn.close()
return [row[0] for row in rows]After posting title=Revise, the evidence should be:
stored
['Revise']Mark points:
- creates or initialises the SQLite table;
- begins from an empty exercise table;
- accepts POST data;
- reads form field
title; - uses parameterised SQL;
- commits the insert;
- closes database connections;
- returns clear route evidence;
- selects the inserted title back;
- avoids SQL string concatenation.
Common weak answer:
- concatenating the submitted title into the SQL string.
Answer 7: SQLite Display
app.py:
import sqlite3
from flask import Flask, render_template
app = Flask(__name__)
DB_NAME = "tasks_display.db"
def setup_db():
conn = sqlite3.connect(DB_NAME)
conn.execute("""
CREATE TABLE IF NOT EXISTS Task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT
)
""")
conn.execute("DELETE FROM Task")
conn.executemany(
"INSERT INTO Task (title) VALUES (?)",
[("Read",), ("Code",)]
)
conn.commit()
conn.close()
@app.route("/tasks")
def tasks():
conn = sqlite3.connect(DB_NAME)
rows = conn.execute("SELECT title FROM Task ORDER BY id").fetchall()
conn.close()
return render_template("tasks.html", rows=rows)templates/tasks.html:
<ul>
{% for row in rows %}
<li>{{ row[0] }}</li>
{% endfor %}
</ul>Expected list items, in order:
<li>Read</li>
<li>Code</li>Mark points:
- creates the table safely with
IF NOT EXISTS; - makes setup deterministic;
- inserts the two sample records;
- queries rows from SQLite;
- orders rows by
id; - closes the database connection;
- passes rows to
render_template(); - uses a Jinja loop;
- displays
<li>elements; - preserves the order
Read, thenCode.
Common weak answer:
- querying the database but never passing the rows to the template.
Answer 8: Static CSS
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/page")
def page():
return render_template("page.html")templates/page.html:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Page</h1>
</body>
</html>style.css belongs in:
static/style.cssExpected generated link path:
/static/style.cssMark points:
- route renders a template;
- stylesheet link is in the HTML template;
- uses
url_for('static', ...); - uses filename
style.css; - recognises
style.cssbelongs instatic/.
Common weak answer:
- hard-coding a path to the
templates/folder for CSS.
Answer 9: Upload Handling
HTML form:
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>Flask route:
from flask import Flask, request
app = Flask(__name__)
@app.route("/upload", methods=["POST"])
def upload():
uploaded = request.files.get("file")
if uploaded is None or uploaded.filename == "":
return "rejected"
filename = uploaded.filename
if filename.lower().endswith(".txt"):
return "accepted"
return "rejected"Expected results:
notes.txt -> accepted
NOTES.TXT -> accepted
image.png -> rejected
empty filename -> rejectedThis is only the exercise’s allowed-extension check. It does not prove that the file content is safe.
Mark points:
- form uses
multipart/form-data; - file input uses field name
file; - route accepts POST uploads;
- reads the file from
request.files; - handles a missing file or empty filename;
- checks the extension case-insensitively;
- accepts
.txt; - rejects non-
.txtfilenames.
Common weak answer:
- checking only whether a file exists and accepting every extension.
Answer 10: Test Client
from flask import Flask
app = Flask(__name__)
@app.route("/ping")
def ping():
return "pong"
client = app.test_client()
response = client.get("/ping")
print(response.status_code)
print(response.get_data(as_text=True) == "pong")Expected output:
200
Truestatus_code checks whether the request succeeded at HTTP level. The response-body comparison checks that the route returned the expected content.
Mark points:
- creates a route
/ping; - returns exact body
pong; - creates a test client;
- sends a request to
/ping; - checks status code
200and the response body.
Common weak answer:
- testing only that the function exists without making a route request.