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

Worksheet 14

MCS 275 Spring 2021 - David Dumas

Topics

This worksheet focuses on making web applications with Flask and SQLite.

The main references for these topics are:

Instructions

  • Problem 1 is handled differently than the others:
    • Tuesday discussion students: Problem 1 will be presented as an example at the start of discussion
    • Thursday discussion students: Please complete problem 1 before discussion and bring your solution to discussion
  • For the other problems:
    • Work on these problems in discussion.

1. Get Yellaro and Whinge set up for local development

You need the source code for Yellaro and Whinge to do your work this week. Download this zip file of all the source code and sample database files (as of 2021-04-17):

(The code is also on GitHub, but downloading the zip is the easiest way to get everything you need without downloading the entire course sample code repository.)

A. Setup and testing

  • Get Yellaro and its database reset utility working

    • Run yellaro.py and check that you can view its main page and post messages using your web browser.
      • Note: You can run the application immediatley because I included yellaro.db in the zip file. If you didn't have a database file, it would be necessary to run dbreset.py first.
    • Stop the yellaro server, reset the database, and start it again.
    • Check that Yellaro is once again accessible in the browser, and that the message feed shows only the sample data.
    • Try using Yellaro from two tabs at the same time. Check that a message posted in one tab becomes visible in the other tab as soon as you click the reload button or create a message.
    • Test what happens if you manually delete the yellaro.db and try to run/use Yellaro. Does it start up? Can it serve the main page?
  • Get Whinge and its database reset utility working

    • Run whinge.py and check that you can view its main page, submit a new whinge, and change scores on items using the + and - buttons.
    • Stop the Whinge server, reset the database, and start it again
      • The dbreset.py for Whinge makes a blank database by default, but can add sample data upon request. The usage message it prints on the terminal describes how to do this.
    • Check that Whinge is once again accessible in the browser, and that the front page is now empty. Add a new whinge.
    • Open Whinge in a browser tab and add a few whinges. Open it in another browser tab. Choose an entry and click + in one tab. Then switch to the other tab, and click - on the same item. What happens to the score? Is this the expected behavior?

B. Better dbreset.py for Yellaro

Modify the dbreset.py in Yellaro to behave similarly to the one from Whinge, where the default behavior is to clear or create the database and leave it empty, but it can also add a few rows of sample data if requested by a command line argument.

2. Yellaro: Highlight mention of Python

Modify Yellaro so that when displaying the contents of a message, any time the word Python appears (with any capitalization), it is shown in a different color than the rest of the message text. Do this by detecting the presence of the word in the data retrieved from the database, wrapping it in a <span class="py"> tag, and then styling that class of span tags in the CSS. (You shouldn't save the modified text to the database.)

If you want to go furhter and have the color change over time, here is some sample CSS that does that. The text in any span tag of class py will fade between two of the main colors in the Python language logo:

span.py {
  color: #3B8BBE;
  animation: colorcycle 3s infinite;
}

@keyframes colorcycle {
  0% {
    color: #3B8BBE;
  }

  50% {
    color: #FFD43B;
  }

  100% {
    color: #3B8BBE;
  }
}

3. Whinge: New submissions page

Add a route /new/ to Whinge which is similar to the current front page, but which sorts submissions from newest to oldest instead of sorting by score. Limit the number of submissions that are shown. It should have the same +/- buttons for changing the score of an item, and should have the submission form at the bottom.

Doing so will introduce some rough edges to the user interface, where any action you take on the new posts page (clicking + or -, submitting a new post) will take you back to /top/, because all parts of the application that perform actions are hard-coded to redirect back there when they finish. At first, that's fine, but you should then fix this problem as follows:

  • The URL that is the target of the +/- buttons should include a second query parameter in addition to postid, which will tell the application whether it should redirect back to /new/ or /top/ after updating the score. The top and new pages generate appropriate URLs when they render the list of submissions. The /plus and /minus routes should be modified to check this parameter and act accordingly.

  • The new post submission form at the bottom of each page should have a hidden input field that stores the page the user was on when they composed the submission (either /new/ or /top/). The route that handles form submission uses this to decide where to redirect the client after the form data are processed.

4. Hide or prevent posts based on content

Choose Yellaro or Whinge for this. The concept can be applied to either one. In this problem you'll add features which treat messages and/or users differently if a message or submission contains certain words. (This is meant as a way to test your skills in implementing interesting program logic, not as an endorsement of content filtering or censorship.)

A. Level 1 ban: obscured

Make a global variable in the main Python program that contains a list of words that are hidden when they appear in posts. The words in this list should be replaced by a series of * characters when displaying the post, but the database should still hold the original text.

e.g. if the list is LEVEL1_BANNED_WORDS = ["C++","Java"], an attempt to post

Before learning Python, I spent a while experimenting with Java.

then the application should store this but display it as

Before learning Python, I spent a while experimenting with ****.

B. Level 2 ban: post fails

Add a second list of words that, if found in a post, result in the post not being added to the database at all. Optionally, display a message to the user indicating that something went wrong.

e.g. if the list is LEVEL2_BANNED_WORDS = ["blockchain","home equity loan"], then a user would be unable to post the following message:

I think blockchain technology is going to revolutionize grading of programming assignments!  Want to hear how?

C. Level 3 ban: user can never post again

Add a third list of words that, if found in a post, result in the user who submitted the post being added to a database table of banned users. The post does not appear, nor does any future post from that username. (Previous posts by this user can still be shown.)

e.g. if the list is LEVEL3_BANNED_WORDS = ["durian","pineapple pizza"], and if user ddumas attempts to post the message

I was so full from eating that entire durian fruit that I didn't have any room for dinner.

then no further messages submitted by user ddumas will be accepted.

(To do this, you'll need to alter the database by adding a new table. I suggest updating dbreset.py so that it creates all the tables your application needs, even if you initially create the new table in the sqlite command line shell or with the Python REPL.)

Also make command line utilities that can be used to manually ban or unban a user specified as a command line argument, e.g.

python3 unban.py ddumas  # unbanned; durian is not so bad
python3 ban.py ddumas    # banned again; starting talking about the HYG star database

Note: The right way to add support for user-level actions like this is to have a separate table listing all users, assigning each one a unique id number, and storing characteristics like username, ban status, etc.. The table of messages or posts would then refer to the user by id number, rather than storing the username. But for this problem, you can use a simpler implementation where the table for banned users has a single column of type TEXT and acts like a persistent list of strings.