|
| 1 | +# Lesson Plan: Python & the Web, Server-side (Week 6) |
| 2 | + |
| 3 | +[[TOC]] |
| 4 | + |
| 5 | +## Pre-Section Preparation |
| 6 | +Visit the [Ed Workspace](https://edstem.org/us/courses/20141/workspaces/paet6LZnIplKXLz0B14XvQMAynn98nVu). Fork it to create your own copy. |
| 7 | + |
| 8 | +The first part of the lab will be about deploying a simple website: the goal of this part is for students to have a web application (even if it's just a "Hello, world!" one) running live on the Internet using [PythonAnywhere](https://www.pythonanywhere.com). |
| 9 | + |
| 10 | +After that, some more UX-inclined students can learn Jinja2 and `render_template`, while other options include writing more sophisticated backend code. |
| 11 | + |
| 12 | +Finally, before section, take a look at the [Mid-Quarter Feedback Form](https://docs.google.com/forms/d/1-UWQy_eoUH1oqeEdp-jRIknjOeQOwNyOVmibXip9hGw/edit#responses) and the [Project Proposals](https://docs.google.com/forms/d/11RgFJ2Aj89_oHFnUwThZCeZLV0anaE5aKjoLgcRogu4/edit#responses). |
| 13 | + |
| 14 | +## Community Building \[5 mins\] |
| 15 | + |
| 16 | +Here are some ideas for community building activities (mostly these are the same as last week, with the top one being a new addition): |
| 17 | +* Your sections should be composed of two or three final project groups - even though students are still likely in the early phases of their projects, ask them to share what they're thinking with each other. |
| 18 | +* Ask everyone a Would You Rather question; there's a list in [this folder](https://drive.google.com/drive/folders/1SobifNwo_dPMA_dO78IUVUuyATwlqF9N?usp=sharing) |
| 19 | +* Have people sign up to lead a community building activity each week by adding their name to a Google Doc; then, each week, they'll be in charge of doing some sort of icebreaker during the first 5 minutes of section or so |
| 20 | +* Ask people to send you their favorite meme/video/song/picture/... and share during section. |
| 21 | +* Ask people to DM you a fun fact about themselves and then share the fun fact and have them guess whose it was. |
| 22 | +* Ask people to share the last picture they took on their phone (that they're comfortable with) and share about it. |
| 23 | +* Play a (short) game like skribbl.io! |
| 24 | +* Build a trivia quiz (maybe from the trivia API?) and see if your students can figure it out. |
| 25 | + |
| 26 | +## Concept Review \[10 mins\] |
| 27 | + |
| 28 | +We began this week's lecture by re-visiting Flask and talking about dynamic routing. "Dynamic routing" refers to Flask's ability to handle general route patterns and store modifications as variables. We built the following route scheme: |
| 29 | + |
| 30 | +```python |
| 31 | +from flask import Flask |
| 32 | + |
| 33 | +app = Flask(__name__) |
| 34 | + |
| 35 | +@app.route('/<name>', methods=['GET']) |
| 36 | +def greet(name): |
| 37 | + return f'Hello, {name}!' |
| 38 | +``` |
| 39 | + |
| 40 | +The string `'/<name>'` represents a general route pattern where the user types anything after the first slash. For example visiting `/parth` would call `greet('parth')`, visiting `/81` would call `greet('81')`, etc. Note that visiting `/parth/tara` would return a 404 error—`'/<name>'` only matches the *first* string that comes after the slash. |
| 41 | + |
| 42 | +We also talked about building an API in Flask. Specifically, if you return a dictionary from a route function, Flask will automatically convert that into a JSON object. |
| 43 | + |
| 44 | +In the last half of the course, we covered `render_template` and using Flask to render HTML (this is called "templating"). Specifically, we set up the route `'/<wikipedia_page_name>'` and used that to create a word cloud of the words that appear on that Wikipedia page. We did that by... |
| 45 | + |
| 46 | +1. Querying the Wikipedia API (<https://en.wikipedia.org/w/api.php?action=query&format=json&titles=Coachella_Valley_Music_and_Arts_Festival&prop=extracts&explaintext>, replace `Coachella_Valley_Music_and_Arts_Festival` for any wikipedia title) and extracting the page text from there. |
| 47 | +2. Using the `wordcloud` library to build SVG code for a wordcloud. |
| 48 | +3. Displaying that SVG code in a website. |
| 49 | + |
| 50 | +## Problems \[45 mins\] |
| 51 | + |
| 52 | +This lab is divided into three parts. In Part 1, students will deploy a simple "Hello, World!" Flask application to the Internet using PythonAnywhere. In Part 2, students will implement nontrivial functionality into their application so that it accepts user input and adjusts its behavior according to what has previously been entered by users. In Part 3, students will have the option to develop their app further. |
| 53 | + |
| 54 | +### Part 1: Deployment |
| 55 | + |
| 56 | +Although many of the subsequent problems in lab are optional where students can pick-and-choose the direction they'd like to go in; however, we'd love it if you could make sure to go over this problem at the beginning of lab as it's going to set the stage for what follows. |
| 57 | + |
| 58 | +To prepare for this section of the lab, we'd recommend making an account on [PythonAnywhere](https://pythonanywhere.com) and following [these instructions](https://pythonhow.com/deploy-flask-web-app-pythonanywhere/) for deploying a simple Flask app. Here's an example app: |
| 59 | + |
| 60 | +```python |
| 61 | +from flask import Flask |
| 62 | + |
| 63 | +app = Flask(__name__) |
| 64 | + |
| 65 | +@app.route('/<name>') |
| 66 | +def hello_world(name): |
| 67 | + return f'Hello {name}!' |
| 68 | + |
| 69 | +@app.route('/') |
| 70 | +def hello(): |
| 71 | + return 'Hello world!' |
| 72 | +``` |
| 73 | + |
| 74 | +### Part 2: Message in a Bottle |
| 75 | +For this portion, once everyone has familiarized themselves with PythonAnywhere, we'd recommend that students work in their project groups to design a more complex application, and deploy it on PythonAnywhere. |
| 76 | + |
| 77 | +The application will work in much the same way as the example we detailed above: when the user navigates to a route, the application will capture that route, and store it as a string in persistent storage in the application. Then, when the user visites the root route, the application will provide them with one of the strings that has previously been stored by visitors of the application. In this way, the app acts like a "message in a bottle" for future visitors, to which any user can contribute a message. |
| 78 | + |
| 79 | +#### Solution Code |
| 80 | +There isn't a ton of "solution code" for this segment, but here's the way that we've written a solution. |
| 81 | + |
| 82 | +```python |
| 83 | + |
| 84 | +from flask import Flask |
| 85 | + |
| 86 | +app = Flask(__name__) |
| 87 | + |
| 88 | +messages = set() |
| 89 | + |
| 90 | +@app.route('/<msg>') |
| 91 | +def hello_world(msg): |
| 92 | + messages.add(msg.replace('+', ' ')) |
| 93 | + return f"Your message has been added to the bottle: {msg.replace('+', ' ')}" |
| 94 | + |
| 95 | +@app.route('/') |
| 96 | +def hello(): |
| 97 | + try: |
| 98 | + return messages.pop() |
| 99 | + except KeyError: |
| 100 | + return "The bottle is empty of messages!" |
| 101 | + |
| 102 | +``` |
| 103 | + |
| 104 | +After implementing the basic version of this application (above), students can work in their groups to implement a `render_template`-based UX to display the messages in the bottle. |
| 105 | + |
| 106 | +#### `render_template`-based UX |
| 107 | + |
| 108 | +Rather than simply displaying text indicating when a message has been added (or whether the bottle is empty), students will import an SVG of a bottle, and display their message within the bottle. |
| 109 | + |
| 110 | +In lecture, we went through an example where we displayed an SVG image generated by a word cloud library, and this exercise will fall along similar lines. The code for that work can be found on the [course website](https://stanfordpython.com/#/lectures). |
| 111 | + |
| 112 | +The solution code for the UX is as follows: |
| 113 | + |
| 114 | +In `bottle.html` (the template file, located in the `templates/` directory): |
| 115 | +```HTML |
| 116 | +<html> |
| 117 | + |
| 118 | + <body> |
| 119 | + <!-- Render the word cloud image of a bottle. --> |
| 120 | + {{ bottle_img|safe }} |
| 121 | + <br> |
| 122 | + <!-- Render the message itself. --> |
| 123 | + <p><b>Message: </b>{{ msg }}</p> |
| 124 | + </body> |
| 125 | + |
| 126 | +</html> |
| 127 | +``` |
| 128 | + |
| 129 | +In `app.py`: |
| 130 | +```python |
| 131 | +from flask import Flask, render_template |
| 132 | +from wordcloud import WordCloud |
| 133 | +import numpy as np |
| 134 | +from PIL import Image |
| 135 | + |
| 136 | +app = Flask(__name__) |
| 137 | + |
| 138 | +messages = set() |
| 139 | + |
| 140 | +@app.route('/<msg>') |
| 141 | +def bottle(msg): |
| 142 | + msg = msg.replace("+", " ") |
| 143 | + messages.add(msg) |
| 144 | + return f"Successfully added message: {msg}" |
| 145 | + |
| 146 | +@app.route('/') |
| 147 | +def display_messages(): |
| 148 | + try: |
| 149 | + msg = messages.pop() |
| 150 | + except KeyError: |
| 151 | + return "The bottle is empty of messages!" |
| 152 | + |
| 153 | + # From the tutorial linked from the lab on masked word clouds |
| 154 | + bottle_mask = np.array(Image.open("bottle_img.png")) |
| 155 | + wc = WordCloud(mask=bottle_mask) |
| 156 | + img_data = wc.generate(msg).to_svg() |
| 157 | + |
| 158 | + return render_template("bottle.html", msg=msg, bottle_img=img_data) |
| 159 | +``` |
| 160 | + |
| 161 | +### Part 3: Extensions |
| 162 | + |
| 163 | +We've scaffolded an extension for this lab involving web forms - students are free to implement that extension, or work on another one of their choosing if time permits. |
| 164 | + |
| 165 | +#### Web Forms |
| 166 | + |
| 167 | +Until this point, we've used the URL of our web application to contain all the relevant information we leverage in our app: this is - in many cases - inconvenient and difficult for end-users to interact with. We've put together some scaffolding in the notebook to acquaint students with using web forms, and ask students to build a simple web form which allows a user to submit new content to the message in a bottle. |
| 168 | + |
| 169 | +The solution code for the UX with web forms is as follows: |
| 170 | + |
| 171 | +In `start.html`: |
| 172 | +```HTML |
| 173 | +<html> |
| 174 | + <form action="{{ url_for('submit_via_form') }}" method='POST'> |
| 175 | + <div> |
| 176 | + <label for="bottle-msg">Enter a message to submit to the bottle!</label> |
| 177 | + <input id="bottle-msg" name="bottle-msg"/> |
| 178 | + </div> |
| 179 | + <div> |
| 180 | + <button type="submit">Submit message!</button> |
| 181 | + </div> |
| 182 | + </form> |
| 183 | +</html> |
| 184 | +``` |
| 185 | + |
| 186 | +In `app.py`: |
| 187 | +```python |
| 188 | +from flask import Flask, render_template, request |
| 189 | +from wordcloud import WordCloud |
| 190 | +import numpy as np |
| 191 | +from PIL import Image |
| 192 | + |
| 193 | +app = Flask(__name__) |
| 194 | + |
| 195 | +messages = set() |
| 196 | + |
| 197 | +@app.route('/<msg>') |
| 198 | +def bottle(msg): |
| 199 | + msg = msg.replace("+", " ") |
| 200 | + messages.add(msg) |
| 201 | + return f"Successfully added message: {msg}" |
| 202 | + |
| 203 | +@app.route('/bottle') |
| 204 | +def display_messages(): |
| 205 | + try: |
| 206 | + msg = messages.pop() |
| 207 | + |
| 208 | + # From the tutorial linked from the lab on masked word clouds |
| 209 | + bottle_mask = np.array(Image.open("bottle_img.png")) |
| 210 | + wc = WordCloud(mask=bottle_mask) |
| 211 | + img_data = wc.generate(msg).to_svg() |
| 212 | + |
| 213 | + return render_template("bottle.html", msg=msg, bottle_img=img_data) |
| 214 | + except KeyError: |
| 215 | + return "The bottle is empty of messages!" |
| 216 | + |
| 217 | +@app.route('/') |
| 218 | +def main(): |
| 219 | + return render_template("start.html") |
| 220 | + |
| 221 | +@app.route('/submit', methods=["POST"]) |
| 222 | +def submit_via_form(): |
| 223 | + # Accept the form data - index into the dictionary, the keys of which are |
| 224 | + # the field names. (In this case the name is 'bottle-msg'). |
| 225 | + form_data = request.form.get("bottle-msg", "") |
| 226 | + messages.add(form_data) |
| 227 | + return f"Successfully added message: {form_data}" |
| 228 | +``` |
| 229 | + |
| 230 | +## Final Project Work Time \[30 mins\] |
| 231 | + |
| 232 | +For the last thirty minutes of class, I'd recommend that you reconvene and have your students work on their final project. It may be good if you can give them discrete deliverables to provide—you can review their project proposals and see what each of them will need. |
| 233 | + |
| 234 | +## Reflection |
| 235 | + |
| 236 | +It doesn't seem like folks are responding to the prompts in the chat—you're all pretty busy! So instead of messaging everyone, send Parth a check-in: how are you finding section so far? What's going well? What do you want to improve on? |
0 commit comments