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/status

Expected browser response:

OK

Mark 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 Asha

Mark 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.html in static/, 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 Asha

Mark points:

  • route allows both GET and POST;
  • 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 username but reading request.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 " + username

Expected results:

username="   " -> Missing username
username="Chen" -> Joined Chen

Mark 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 82

Mark points:

  • passes the list of records to the template;
  • uses render_template();
  • places the template in templates/;
  • uses a Jinja for loop;
  • 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, then Code.

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.css

Expected generated link path:

/static/style.css

Mark points:

  • route renders a template;
  • stylesheet link is in the HTML template;
  • uses url_for('static', ...);
  • uses filename style.css;
  • recognises style.css belongs in static/.

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 -> rejected

This 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-.txt filenames.

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
True

status_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 200 and the response body.

Common weak answer:

  • testing only that the function exists without making a route request.