A document from MCS 275 Spring 2023, instructor Emily Dumas. You can also get the notebook file.

MCS 275 Spring 2023 Homework 14 Solutions

  • Course Instructor: Emily Dumas
  • Contributors to this document: Johnny Joyce and Kylash Viswanathan

Instructions:

  • Complete the problems below, which ask you to write Python scripts.

Deadline

This homework assignment must be submitted in Gradescope by Noon central time on Tuesday April 25, 2023.

🎈 You made it 🎈

This is the last homework assignment in MCS 275. After this, the only graded work remaining is Project 4.

Collaboration

Collaboration is prohibited, and you may only access resources (books, online, etc.) listed below.

Content

This homework is about Flask application development.

Resources you may consult

Most relevant:

Less likely to be relevant, but also allowed:

Point distribution

This homework assignment has 2 problems. The grading breakdown is:

Points Item
3 Autograder
6 Problem 2
6 Problem 3
15 Total

The part marked "autograder" reflects points assigned to your submission based on some simple automated checks for Python syntax, etc. The result of these checks is shown immediately after you submit.

What to do if you're stuck

Ask your instructor or TA a question by email, in office hours, or on discord.

Problem 2: Bug report system - form submission handling

This problem concerns a Flask application that collects and stores bug reports (e.g. describing problems users have with a piece of software or technology) in a SQLite database. In this application, a bug report has the following data associated with it:

  • id - A primary key assigned by the database
  • created_ts - The timestamp (time.time() return value) when the bug report was created
  • fullname - The full name of the person who created the report
  • description - A description of the problem
  • severity - One of the words low, medium, high, or critical.

As provided, the application is incomplete.

Start by getting the incomplete application:

  • Download and extract the ZIP file linked below.
  • Or if you prefer you can download the files one by one from the course sample code repository (see below for the list of files you need)

List of files needed for this exercise

  • bugreport.py - Main program
  • static/ - Subdirectory for static files
  • static/main.css - Main stylesheet
  • templates/ - Subdirectory for templates
  • templates/report_list.html - Template for page listing all bug reports

What to do

As provided, the application has:

  • / - A front page
  • /create/ - A form for a creating new bug report
  • /list/ - A page listing all bug reports
  • /create/done - A page meant to be shown after a bug report has been added to the database

However, it is missing the body of the function for

  • /create/submit - The URL that receives form submissions and adds a new bug report to the DB (adding a suitable value for created_ts as well)

Write the body of the corresponding function create_report_submission in bugreport.py, and submit the modified bugreport.py as part of your homework. (Don't submit the rest of the files in this application.)

Note: You might need to try the existing application out and read through the source code in some detail to get all the information you need to complete this problem.

Solution

In [ ]:
# The update to bugreport.py is given below
@app.route("/create/submit", methods=["GET", "POST"])  # GET-only is default
def create_report_submission():
    "Create a bug report based on information submitted in the form"
    fullname = flask.request.values.get("fullname")
    description = flask.request.values.get("description")
    severity = flask.request.values.get("severity")

    con = sqlite3.connect(DB_FILE)

    res = con.execute(
        """
        INSERT INTO reports (created_ts, fullname, description, severity)
        VALUES (?,?,?,?);
        """,
        (time.time(), fullname, description, severity),
    )

    con.commit()
    con.close()

    return flask.redirect("/create/done")

Problem 3: Capture the flag

Imagine a Flask application that, when you visit URL /question/1/ asks you a question with a numerical answer, such as

Take the smallest power of 7 that contains all 10 decimal digits (0,1, ... 9). Form an integer out of the rightmost five digits in that number (when written in decimal).

In this case, the answer is 10449. That means you're supposed to visit /question/10449/ next. That page may ask you another question, telling you where to go next, and so on, until at some point you open the page for the answer to the previous question and see

Winner! You captured the flag.

However, this application only lets one person "win" in this way. That is, once someone has visited the final page, it is marked as "captured" and any subsequent visit to any of the question pages or the final page will show

(The CTF game is over. Someone else has captured the flag.)

Also, at any time, visiting a page /question/<int:n>/ where n is not one of the supported questions/answers will show

Oops. That is not a correct answer. (Maybe use the browser's back button and try again?)

You could store the information needed for this application in a database table structured as follows:

n question is_winner captured
1 Take the smallest power of 7 that contains... 0
10449 What's the smallest prime greater than 37? 0
41 What is 11 raised to the 4th power? 0
14641 1 0

(This means 14641 is the winning answer, and it hasn't been visited yet. Note that n=1 is always the entry point of the game, and the winning answer never has a question associated to it.)

Write a Flask application that implements this idea, using a SQLite database with a single table as above. You can pre-populate it with the data shown above, or make your own game, but the program should initialize the database when it is run.

Your application doesn't need to have any styling (CSS) at all. Use of HTML templates is also optional.

Note that this application has just one route, but that function has a lot in it (i.e. several DB queries and a significant amount of program logic). It needs to:

  • Check if the game has already been won (i.e. what is the value of captured in the row where is_winner equals 1)
    • If already won: Display the "game over" message
    • If not already won: Check if the number in the URL is one of the possible answers to questions in the game and
      • If the current URL is not a game answer, show the "oops" message
      • If the current URL is a game answer: Check if this request is the one that wins the game and
        • If this request wins the game: Show the "winner" message
        • If this request does not win the game: Display the question for this value of n

Solution

In [ ]:
import flask
import sqlite3
import os
DB_FILE = "ctf.sqlite"
app = flask.Flask("Capture The Flag")


@app.route("/question/<int:n>/")
def question(n):
    """Capture the flag game"""
    # A string representing a simple HTML page that displays a given message when `.format is used`
    html_str = """<!doctype html>
                <html>
                <head><title>Capture The Flag</title></head>
                <body><h1>{}</h1></body>
                </html>
                """
    
    con = sqlite3.connect(DB_FILE)
    res = con.execute("SELECT captured FROM ctf WHERE is_winner = 1;")

    if int(res.fetchone()[0]): # If won
        con.close()
        return html_str.format("(The CTF game is over. Someone else has captured the flag.)")
    
    res = con.execute("SELECT question, is_winner FROM ctf WHERE n = ?;", (n,))
    row = res.fetchone()
    if row is None: # If wrong answer
        con.close()
        return html_str.format("Oops. That is not a correct answer. (Maybe use the browser's back button and try again?)")
    
    if int(row[1]): # If winner
        con.execute("UPDATE ctf SET captured=1 WHERE is_winner=1")
        con.commit()
        con.close()
        return html_str.format("Winner! You captured the flag")
    
    con.close()
    return html_str.format(row[0])

# Make sure database exists
add_sample_data = False
if not os.path.exists(DB_FILE):
    print("The database '{}' was not found.  Creating it.".format(DB_FILE))
    add_sample_data = True

con = sqlite3.connect(DB_FILE)

print("Making sure the DB contains the necessary tables...", end="")
con.execute("""
CREATE TABLE IF NOT EXISTS ctf (
    n INTEGER PRIMARY KEY,
    question TEXT,
    is_winner INT,
    captured INT);""")
print("Done")

if add_sample_data:
    print("Populating DB with sample data, since it was empty...", end="")
    con.execute("INSERT INTO ctf (n,question,is_winner) VALUES (?,?,?)", (1,"Take the smallest power of 7 that contains all 10 decimal digits (0,1, ... 9). Form an integer out of the rightmost five digits in that number (when written in decimal).", 0))
    con.execute("INSERT INTO ctf (n,question,is_winner) VALUES (?,?,?)", (10449,"What's the smallest prime greater than 37?", 0))
    con.execute("INSERT INTO ctf (n,question,is_winner) VALUES (?,?,?)", (41,"What is 11 raised to the 4th power?", 0))
    con.execute("INSERT INTO ctf (n,question,is_winner,captured) VALUES (?,?,?,?)", (14641, "", 1, 0))
    
    print("Done")

con.commit()
con.close()

# Run the application
app.run()  # This will never return.  You lose all control over the program.

Revision history

  • 2023-04-20 Initial publication