Exercise 50: Your First Website

These final three exercises will be very hard and you should take your time with them. In this first one you'll build a simple web version of one of your games. Before you attempt this exercise you must have completed Exercise 46 successfully and have a working pip installed such that you can install packages and know how to make a skeleton project directory. If you don't remember how to do this, go back to Exercise 46 and do it all over again.

Activating Your Virtual Environment

You should be in the habit of using a virtualenv but if you forgot then activate it now. On Linux or OSX do this:

$ source ~/.venvs/lpthw/bin/activate
(lpthw) $

But, in Microsoft Windows PowerShell do this:

> pushd ~
> .\.venvs\lpthw\Scripts\activate
(lpthw) > popd

From now on always use this virtualenv to work on the book. It makes things much easier. In the rest of these instructions I'm doing python rather than python since the virtualenv solves this problem for us. You'll also see (lpthw) before each prompt to remind you that you're in a virtualenv.

Installing Flask

Before creating your first web application, you'll first need to install the "web framework" called flask. The term "framework" generally means "some package that makes it easier for me to do something." In the world of web applications, people create "web frameworks" to compensate for the difficult problems they've encountered when making their own sites. They share these common solutions in the form of a package you can download to bootstrap your own projects.

In our case, we'll be using the flask framework, but there are many, many, many others you can choose from. For now, learn flask, then branch out to another one when you're ready (or just keep using flask since it's good enough).

Using pip, install flask:

(lpthw) $ # are you in your lpthw virtualenv?
(lpthw) $ sudo pip install flask
[sudo] password for zedshaw:
Downloading/unpacking flask
  Running setup.py egg_info for package flask

Installing collected packages: flask
  Running setup.py install for flask

Successfully installed flask
Cleaning up...

But on Microsoft Windows PowerShell you do this:

(lpthw) > # are you in your lpthw virtualenv?
(lpthw) > pip install flask

This will work on Linux and macOS computers, but on Windows just drop the sudo part of the pip install command and it should work. If not, go back to Exercise 46 and make sure you can do it reliably.

Make a Simple "Hello World" Project

Now you're going to make an initial very simple "Hello World" web application and project directory using flask. First, make your project directory:

(lpthw) $ cd projects
(lpthw) $ mkdir gothonweb
(lpthw) $ cd gothonweb
(lpthw) $ mkdir bin gothonweb tests docs templates
(lpthw) $ touch gothonweb/__init__.py
(lpthw) $ touch tests/__init__.py

In PowerShell you do this:

(lpthw) > cd projects
(lpthw) > mkdir gothonweb
(lpthw) > cd gothonweb
(lpthw) > mkdir bin
(lpthw) > mkdir gothonweb
(lpthw) > mkdir tests
(lpthw) > mkdir docs
(lpthw) > mkdir templates
(lpthw) > new-item -type file gothonweb/__init__.py
(lpthw) > new-item -type file tests/__init__.py

You'll be taking the game from Exercise 43 and making it into a web application, so that's why you're calling it gothonweb. Before you do that, we need to create the most basic flask application possible. Put the following code into app.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    greeting = "World"
    return f'Hello, {greeting}!'

if __name__ == "__main__":
    app.run()

Then run the application like this:

(lpthw) $ python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Finally, use your web browser and go to http://localhost:5000/, and you should see two things. First, in your browser you'll see Hello, world!. Second, you'll see your terminal with new output like this:

(lpthw) $ python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [22/Feb/2017 14:28:50] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [22/Feb/2017 14:28:50] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [22/Feb/2017 14:28:50] "GET /favicon.ico HTTP/1.1" 404 -

Those are log messages that flask prints out so you can see that the server is working and what the browser is doing behind the scenes. The log messages help you debug and figure out when you have problems. For example, it's saying that your browser tried to get /favicon.ico but that file didn't exist, so it returned the 404 Not Found status code.

I haven't explained the way any of this web stuff works yet, because I want to get you set up and ready to roll so that I can explain it better in the next two exercises. To accomplish this, I'll have you break your flask application in various ways and then restructure it so that you know how it's set up.

What's Going On?

Here's what's happening when your browser hits your application:

  1. Your browser makes a network connection to your own computer, which is called localhost and is a standard way of saying "whatever my own computer is called on the network." It also uses port 5000.
  2. Once it connects, it makes an HTTP request to the app.py application and asks for the / URL, which is commonly the first URL on any website.
  3. Inside app.py you've got a list of URLs and what functions they match. The only one we have is the '/', 'hello_world' mapping. This means that whenever someone goes to / with a browser, flask will find the def hello_world and run it to handle the request.
  4. Now that flask has found def hello_world, it calls it to actually handle the request. This function runs and simply returns a string for what flask should send to the browser.
  5. Finally, flask has handled the request and sends this response to the browser, which is what you are seeing.

Make sure you really understand this. Draw up a diagram of how this information flows from your browser, to flask, then to def hello_world and back to your browser.

Fixing Errors

First, delete line 6 where you assign the greeting variable, then hit refresh in your browser. Then use CTRL-C to kill flask and start it again. Once it's running again refresh your browser, and you should see an "Internal Server Error". Back in your terminal you'll see this ([VENV] is the path to your .venvs/ directory):

(lpthw) $ python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[2017-02-22 14:35:54,256] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "[VENV]/site-packages/flask/app.py",
    line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "[VENV]/site-packages/flask/app.py",
    line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "[VENV]/site-packages/flask/app.py",
    line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "[VENV]/site-packages/flask/_compat.py",
  line 33, in reraise
    raise value
  File "[VENV]/site-packages/flask/app.py",
line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "[VENV]/site-packages/flask/app.py",
line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "app.py", line 6, in hello_world
    return greeting
NameError: name 'greeting' is not defined
127.0.0.1 - - [22/Feb/2017 14:35:54] "GET / HTTP/1.1" 500 -

This works well enough, but you can also run Flask in "debugger mode". This will give you a better error page and more useful information. The problem with debugger mode is it's not safe to run on the internet, so you have to explicitly turn it on like this:

(lpthw) $ export FLASK_DEBUG=1
(lpthw) $ python app.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger pin code: 222-752-342

After this, you hit refresh in your browser, and you get a much more detailed page with information you can use to debug the application and a live console to work with to find out more.

Warning

It's the Flask live debugging console and the improved output that makes debug mode so dangerous on the internet. With this information an attacker can completely control your machine remotely. If you ever do place your web application on the internet do not activate debugger mode. In fact, I would avoid making FLASK_DEBUG easy to activate. It's tempting to simply hack this startup so that you save a step during development, but then that hack will get onto your web server and it'll turn into a real hack, not just something lazy you did one night when you were tired.

Create Basic Templates

You can break your flask application, but did you notice that "Hello World" isn't a very good HTML page? This is a web application, and as such it needs a proper HTML response. To do that you will create a simple template that says "Hello World" in a big green font.

The first step is to create a templates/index.html file that looks like this:

<html>
    <head>
        <title>Gothons Of Planet Percal #25</title>
    </head>
<body>

{% if greeting %}
    I just wanted to say
    <em style="color: green; font-size: 2em;">{{ greeting }}</em>.
{% else %}
    <em>Hello</em>, world!
{% endif %}

</body>
</html>

If you know what HTML is, then this should look fairly familiar. If not, research HTML and try writing a few web pages by hand so you know how it works. This HTML file, however, is a template, which means that flask will fill in "holes" in the text depending on variables you pass in to the template. Every place you see {{ greeting }} will be a variable you'll pass to the template that alters its contents.

To make your app.py do this, you need to add some code to tell flask where to load the template and to render it. Take that file and change it like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def index():
    greeting = "Hello World"
    return render_template("index.html", greeting=greeting)

if __name__ == "__main__":
    app.run()

Once you have that in place, reload the web page in your browser, and you should see a different message in green. You should also be able to do a View Source on the page in your browser to see that it is valid HTML.

This may have flown by you very fast, so let me explain how a template works:

  1. In your app.py you've imported a new function named render_template at the top.
  2. This render_template knows how to load .html files out of the templates/ directory because that is the default magic setting for a Flask application.
  3. Later in your code, when the browser hits the def index, instead of just returning the string greeting, you call render_template and pass the greeting to it as a variable.
  4. This render_template method then loads the templates/index.html file (even though you didn't explicitly say templates) and processes it.
  5. In this templates/index.html file you have what looks like normal HTML, but then there's "code" placed between two kinds of markers. One is {% %}, which marks pieces of "executable code" (if-statements, for-loops, etc). The other is {{ }} which marks variables to be converted into text and placed into the HTML output. The {% %} executable code doesn't show up in the HTML. To learn more about this template language read the Jinja2 Documentation.

To get deeper into this, change the greeting variable and the HTML to see what effect it has. Also create another template named templates/foo.html and render that like before.

Study Drills

  1. Read the documentation at http://flask.pocoo.org/docs/0.12/, which is the same as the flask project.
  2. Experiment with everything you can find there, including their example code.
  3. Read about HTML5 and CSS3 and make some other .html and .css files for practice.
  4. If you have a friend who knows Django and is willing to help you, then consider doing Exercises 50, 51, and 52 in Django instead to see what that's like.

Common Student Questions

I can't seem to connect to http://localhost:5000/.
Try going to http://127.0.0.1:5000/ instead.
I can't find index.html (or just about anything).
You probably are doing cd bin/ first and then trying to work with the project. Do not do this. All of the commands and instructions assume you are one directory above bin/, so if you can't type python app.py then you are in the wrong directory.
Why do we assign greeting=greeting when we call the template?
You are not assigning to greeting. You are setting a named parameter to give to the template. It's sort of an assignment, but it only affects the call to the template function.
I can't use port 5000 on my computer.
You probably have an anti-virus program installed that is using that port. Try a different port.

Buy The Python 3 Edition!

When you buy Learn Python 3 The Hard Way, you'll receive the Python 3 Edition PDF, special access to a paid HTML version, and 12 hours of 1080p video, one video for each exercise. All files are DRM free and you can download them to your computer for offline viewing. Digital Download Only! You do not get a physical book.

$29.99

Buy Digital Download From Zed

Try a a free sample of Learn Python the Hard Way right here, video lectures not included.

Other Buying Options

Buy on Amazon Buy from Barnes & Noble