Lab 8: Introduction to
Web Applications

Part 3: Templates

Links to:  Overview   Part 1    Part 2     Part 3    Part 4    Part 5    Part 6

If you find typos or problems with the lab instructions, please let us know.

So far, our web pages don't look very professional.   To get them to look more professional, we can use something called templates, that allows us to have a common layout for each page.

 This part of the lab is designed to be done together as a pair.

Step 1: Create a repo for both partners to use

Follow the instructions from part 1 of this lab to create a repo for your partnership to use. The key points to remember are:

Make sure that both you and your pair partner can access this new repo.

Next, to get started, copy one of your main.py files from your individual repos for part 2 and copy-paste the text into main.py in your new repo.  In paticular, you'll need your ctof function from part 2.


Step 2: Using Templates

Now that we know a bit about HTML, we can try building a multi page application that uses HTML on each page.

Here is an example of how that would look. You need to create a subdirectory (folder) in your repo called templates.
Inside the templates folder, the first file you should create and store is one called layout.html.  (Please note: .html, not .htm)

To create a new folder in VS Code, either right click in the Explorer sidebar and choose "New Folder..." or click the New Folder icon at the top right of the sidebar.

Here's the code that you should copy/paste into layout.html

The layout.html file should look like this:

<!doctype html>

<html>

  <head>

    <meta charset="UTF-8">

    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">

    <title> {% block title %}{% endblock %} - My Webpage</title>

  </head>


  <body>

    <main id="content">{% block content %}{% endblock %}</main>

  </body>

</html>

The file layout.html defines a template for every page of your web application.

There are two parts of this file that are, strictly speaking, not HTML code. Those are:

These are placeholders where some other HTML code will be inserted, and the syntax is defined by a system called Jinja2, which is part of the Flask framework. That’s all you really need to know about Jinja2 for now, but if you are curious to learn more, there is documentation here: http://jinja.pocoo.org/.

The title and content blocks for each of our pages are going to be defined in additional files in our templates directory. These files will be different webpages, each with their own title and content blocks.

Creating a template for the home page

Now you can define the templates for the rest of the pages in your web application. Let’s make a web application with three pages: one that converts fahrenheit to celsius, another than converts celsius to fahrenheit, and a third that converts miles to kilometers.

The template for the home page will be called home.html and should be in the templates folder

The code for home.html:

{% extends "layout.html" %}


 {% block title %}Home{% endblock %}


 {% block content %}

      <h1>Home</h1>

      <ul>

        <li><a href="/ctof">Convert Celsius to Fahrenheit</a></li>

      </ul>

 {% endblock %}

Adding a route for our home page to main.py

Now, we need to add this code into main.py so that our home page will work:

First, add this near the top of your main.py file, next to the other import statements.

from flask import render_template

Find this code in your main.py

@app.route("/")

def hello():

   return "Hello World!"



And replace it with:

@app.route('/')

def render_home():

    return render_template('home.html')

Explanation:


Now give it a try!

Go back into your Codespace, stop the web server if it is running, and try restarting it.  You should now see a home page that looks like the one at right!

If instead you see Internal Server Error like the image at right, then you may have missed something.

For example, you'll get an Internal Server Error if you leave out (or comment out) the line
from flask import render_template 

When you see  Internal Server Error, the thing to do is to look in the Terminal window for an error message.  For example, in the case of a missing import for render_template, what you'll see in that window is what's shown at right.   Wow, what a mess!

But here's a tip: whenever you see long error output like this, look at the very top, and the very bottom—not always, but that's often where the useful parts are.  In this case near the very bottom, we see:

  File "main.py", line 8, in render_home

    return render_template('home.html')

NameError: name 'render_template' is not defined

That name 'render_template' is not defined  is a big clue as to what went wrong in this particular case!  Anytime you have an Internal Server Error, look in the Terminal output for the clues as to what went wrong.

If you have the correct output, you are almost ready for the next step.

But first, lets try clicking on the link we see on the new home page, that Convert Celsius to Fahrenheit link (not the one on this screenshot, but the on your running web page in GitHub Codespaces).

You should get the error shown at right.    This is because the web page is trying to go to /ctof, because of this line from home.html:

<li><a href="/ctof">Convert Celsius to Fahrenheit</a></li>

But the link /ctof  is not defined in our main.py file.     We have a link for /ctof/<ctemp> where <ctemp> is some temperature, but we don't have a link for just plain /ctof 

We'll fix this in the next step.

Step 3: A web app with a form page and a results page

In Part 2, we showed that we could take input from a URL parameter, use it in an calculation, and show a result.

However, that's now how most users interact with web pages—that is, they don't just type parameters into a URL.

Instead, they want to follow a link to a form, enter information, click a button, and see a result.

In this step, I'll walk you through doing that with the ctof function so that you can see how the pieces fit together.

Then, we'll circle back and convert all of the functions from Part 2 to this way of working.

Creating a form page for user input (ctof.html)

Notice that in the explorer bar in VS code, the templates folder can be opened and closed by clicking on it.  Don't panic if all of your templates seem to disappear; you may just need to click the folder icon. And if you ever lose your explorer bar, just click on the "two sheets of paper" icon on the far left.

Now, right click on the templates folder and select "New file..." to create a new file in the templates folder called ctof.html.

The full code for what we are going to put into ctof.html appears below, followed by some explanation of the lines of code.   You may copy/paste this code into the editor, or retype it, as you see fit.

ctof.html contents

{% extends "layout.html" %}


{% block title %}Convert ctof{% endblock %}


{% block content %}


<p>Enter a temperature and click "submit" to convert to Fahrenheit</p>


<form action="/ctof_result">

  <p>Celsius Temp: <input type="number" name="ctemp" value="20.0"></p>

  <input type="submit" value="Submit">

</form>


{% endblock %}

Explanation of ctof.html

The line {% extends "layout.html" %} indicates that we are using layout.html as our underlying template, which requires two blocks:

Our definitions of those blocks are then substituted in to the layout.html contents to produce the final page.

Inside the content block, we use the <form> and <input> tags to create the fields.   We wrap this in a <p> element to make it nicely spaced.

Take note of the attributes we add:

Routing to the ctof form page in main.py

Now, in main.py, we need to link the route /ctof to this page.   That's done with this code.  Put this code into main.py right below the code for render_home—the new code is shown in bold below.

Code to add to main.py:

@app.route('/')

def render_home():

    return render_template('home.html')


@app.route('/ctof')

def render_ctof():

    return render_template('ctof.html')

Now try stopping and restarting your web app.  Once you've done that, click on the Convert Celsius to Fahrenheit link on the home page. It should now take you to a page with a form on it like the one shown at right.


Now, try clicking Submit on that page: you should get a Not Found error.
Here's why:  the code behind that Submit button is this code from ctof.html:

<form action="/ctof_result">

  <p>Celsius Temp: <input type="number" name="ctemp" value="20.0"></p>

  <input type="submit" value="Submit">

</form>

When you click the submit button in a form, it tries to redirect you to web address at the form's action attribute, which in this case is "/ctof_result"
But if you look inside main,py, there is no route for /ctof_result.   We'd need to see something like @app.route('/ctof_result') but that does not appear.  

We'll fix that in the next step.

Creating a result page (ctof_result.html)

The page  ctof_result.html goes in—you guessed it—the templates folder.   Create a new file under templates called ctof_result.html

Here’s ctof_result.html:

{% extends "layout.html" %}


{% block title %}Result of converting Celsius to Fahrenheit{% endblock %}


{% block content %}

<p> In Celsius: {{ ctemp }}.  In Fahrenheit: {{ ftemp }} </p>

{% endblock %}


As with other template files, we use {% extends "layout.html" %} to say that the file layout.html should have the basic layout, but that we'll supply two blocks of code that are inserted into that template: a title block and content block.
Inside the content block, we can specify expressions such as {{ ctemp }} and {{ ftemp }} which refer to values we pass into the template from our Python code (we'll see where those come from in a minute; we take care of that in main.py).

Before we can use the new ctof_result.html template, we need to add some code to main.py that will route to it. 

First, add this import to the top of the file:

from flask import request

Now add a function that connects the route /ctof_result to this new template.  We've done that before, but this time the code looks a little different, because we are going to:

 Here's the new code that you should add to main.py, shown next to the other two template rendering functions, so that you can compare them. 
As before, the new code is shown in bold. 

@app.route('/')

def render_main():

    return render_template('home.html')


@app.route('/ctof')

def render_ctof():

    return render_template('ctof.html')


@app.route('/ctof_result')

def render_ctof_result():

    try:

        ctemp_result = float(request.args['ctemp'])

        ftemp_result = ctof(ctemp_result)

        return render_template('ctof_result.html', 

                               ctemp=ctemp_result, 

                               ftemp=ftemp_result)

    except ValueError:

        return "Sorry: something went wrong."

Here's an explanation of this new code:

Ok, we are ready to try it!   Click Stop then Run in repl to restart your web server. Then you should see a home page like the one at right. 

Click on the link Convert Celsius to Fahrenheit 


That should take you to the page where you can enter a temperature and click Submit


You should, finally, then see a page that shows the result!

If that all works, you are ready for the next step.  But if you got an Internal Server Error or a Not Found, then go back and see if you can locate the source of the problem.

Step 4: Do it again for ftoc

Now that you've seen the process, we are going to go through the entire cycle again for the ftoc function:

In this case, we'll once again give you all of the code (though with fewer explanations), so that you can compare this code to the code for ctof to try to understand what's the same, what's different and why.

Then, we'll repeat the process again for mtokm, but in that case, you'll be expected to supply some of the code yourself.

Finally, we'll do the same process for your custom function.

Add a link to Fahrenheit to Celsius conversion to the home page

In the template file home.html you'll find this code:

  <ul>    

    <li><a href="/ctof">Convert Celsius to Fahrenheit</a></li>

  </ul>

Add another line like this:

  <ul>    

    <li><a href="/ctof">Convert Celsius to Fahrenheit</a></li>

    <li><a href="/ftoc">Convert Fahrenheit to Celsius</a></li>

  </ul>

The <ul> makes an unordered list, and each <li> is a list element.  The <a> tag makes a link, and the href attribute is the url of where that link should take you.  So we've just added a second link to our home page, one that takes us to a new page (one that doesn't exist yet).

When you try it you should see that:

Add a form for fahrenheit to celsius conversion

Add a file under templates called ftoc.html with this content

{% extends "layout.html" %}


{% block title %}Convert ftoc{% endblock %}


{% block content %}


<p>Enter a temperature and click "submit" to convert to Celsius</p>


<form action="/ftoc_result">

  <p>Fahrenheit Temp: <input type="number" name="ftemp" value="68.0"></p>

  <input type="submit" value="Submit">

</form>


{% endblock %}

Compare this to the contents of ctof.html, and make sure you understand the code above.

To route to this, add this into main.py:

@app.route('/ftoc')

def render_ftoc():

    return render_template('ftoc.html')

Now try clicking Stop and then Run, and see what happens if you try to get to the form page from the home page.  That should now work, but you should find that clicking Submit on the form gives you a Not Found. 

In most browsers (though not in Safari, so I suggest using Firefox or Chrome instead), you can more easily see what is happening the URL bar as you navigate from page to page. Here's what it looks like in Chrome on a Mac; note how the URL changes as we navigate from page to page (ignore the fact that the domain differs from our current tech setup).

Add a result page for the fahrenheit to celsius conversion 

As you can see in the animation above, the ftoc form tries to route to ftoc_result, but there is no such page.   So let's create one. In the templates folder, add ftoc_result.html with this content:

ftoc_result.html

{% extends "layout.html" %}


{% block title %}Result of converting Fahrenheit to Celsius{% endblock %}


{% block content %}

<p> In Fahrenheit: {{ ftemp }}.  In Celsius: {{ ctemp }} </p>

{% endblock %}

And in the main.py, somewhere near your render_ftoc function, add a route for the ftoc_result page, like this:

@app.route('/ftoc_result')

def render_ftoc_result():

    try:

        ftemp_result = float(request.args['ftemp'])

        ctemp_result = ftoc(ftemp_result)

        return render_template('ftoc_result.html',

                              ftemp=ftemp_result, 

                              ctemp=ctemp_result)

    except ValueError:

        return "Sorry: something went wrong."


We are then ready to test the entire sequence of clicking on the link for the Fahrenheit to Celsius conversion, entering a value in the form, clicking submit and seeing the result.  Give it a try! 

If it works, you are ready to try the next step.  Otherwise, try to debug the work so far.

Step 5: Do it again for mtokm

Ok, we are going through the entire process again, but this time, I'm going to ask you to figure out how to do some of the steps.

I'll walk you through the steps, but you'll have to come up with some of the code on your own.

Add a link to the home page.

Change home.html so that we have a link to a /mtokm form.

What we want the home page to look like is shown in the figure on the right.

The code you need to change is in home.html

We want the text Convert Miles to Kilometers to link to the url /mtokm.  

Figure out what code needs to be added, and add it.

Test it in a browser; you should see a Not Found error, but the url shoudl change to /mtokm

Add a form for converting miles to kilometers

Next, you should add a form under the templates directory called mtokm.html.

It should present a page like the one shown at right.   

The action should take you to the url /mtokm_result

The code will be similar to that in ctof.html and ftoc.html, but it is up to you to figure out what to write.

Note that to get the link on the home page to route to this form, you also need to add some code into main.py, so figure out what code is needed and add that code.

Now, if you've done everything right up to this point,  the home page should route to this form, but the Submit button doesn't work yet. We'll take care of that next.

Add a result page for converting miles to kilometers

Now, we need to implement the missing /mtokm_result route.  
We start by creating a template mtokm_result.html under templates.

Use the ctof_result.html and ftoc_result.html as models to figure out what to write.  We want the results to look like those shown at right.

We'll also need code in main.py to route to this page; use the model of the code in main.py for the other result pages as your model.

What if i get a Bad Request error?

True story:  the first time the original author tried the mtokm_result while setting up this lab, he got a Bad Request error.   

This is a close relative of the Internal Server Error.

It often results from a mismatch between 

Just as with  an Internal Server Error, the Terminal window can be helpful in debugging a Bad Request error.

Can you tell from the output there what went wrong? 

Hint: the problem wasn't in my new mtokm_result.html file, but was a problem in my mtokm.html file, in the form.  I wasn't able to test that until I could click the Submit button and expect it to route to my new mtokm_result.html page.

See if you can spot the error!

Step 6: Do it again for your custom function

If your home page now looks like the one at right, and all three of these links work properly, then you are ready to try implementing your own custom function (the same one you implemented in Part 2).

Go back and look at the code your wrote for that, and ask these questions:

 Once you've thought about that, you are ready to start.

Add a link to the home page for your custom function

You'll need to add some code to home.html for your custom function. 

Here is what my home page looked like after I added a link for a custom function to sort three strings.

An input form for your custom function

In step 2, you created a custom function: a function of your own choosing that you turned into a web app.   We'll now create a form for your custom function. Here's an example of what that might look like for the custom function in the example from part 2 (the one that sorts three strings).  You'll need to adapt either this, or one of the examples above to come up with a form for your function.

What you need to think about

You typically need exactly one <input> element for each parameter to your function.  In my case, the Python function has three string parameters, a, b and c, so I named my three input files the same thing.

Then:

Here's a sample form sort.html and what it looks like when rendered.

{% extends "layout.html" %}


{% block title %}Sort three strings{% endblock %}


{% block content %}


<p>Enter three strings, and I'll sort them</p>


<form action="/sort_result">

  <p>String 1: <input type="text" name="a" value=""> </p>

  <p>String 2: <input type="text" name="b" value=""> </p>

  <p>String 3: <input type="text" name="c" value=""> </p>

  <input type="submit" value="Submit">

</form>


{% endblock %}

A result page for your custom function

Now you need a result page, which will be another template, something like sort_result.html, along with code in main.py that routes to it.

Here is an example that you can use as a model (in addition to the earlier ones, ftoc_result.html and ctof_result.html). 

The result we want looks like what you see at right.

Here's a sort_result.html 

{% extends "layout.html" %}


{% block title %}Result sorting three strings{% endblock %}


{% block content %}

<p> Sorted: {{ result }} </p>

{% endblock %}

And here is the code that goes in main.py.  Note that since we are dealing with string values, we don't need  a try/except to watch for value errors.

def sort_strings(a,b,c):

  list_of_strings = [a,b,c]

  list_of_strings.sort()

  return list_of_strings


@app.route('/sort_result')

def render_sort_result():

      a = request.args['a']

      b = request.args['b']

      c = request.args['c']

      result_as_list = sort_strings(a,b,c)

      return render_template('sort_result.html',
                              result = str(result_as_list))



Try it and make sure it works.

Step 7: Adding a CSS file

We'll now add a CSS file so that you can make control the appearance of your web app.

Create a new folder at the top level of your repl called static, at the same level as your main.py file, and as a sibling of your templates directory (not inside it.) 

Inside that folder, put a file called style.css

Here's what that might look like in a different editor (right):

This file will contain rules for the fonts, colors, spacing, and layout for your web page, expressed in a language called CSS, which stands for Cascading Style Sheets.  Below right, you'll see code for a basic style.css file. 

Here, the value #cceeff is a hexadecimal (base 16) color code for a light blue. 
You can visit this page to see various values you can use: 

Try some different color values.

You can also try some other values for the font-family, including these:

body {

    background-color: #ccffee;

    color: black;

    font-family: sans-serif;

}

You can also learn more about CSS rules at w3schools and experiment with the style if you like.

With this file in place, the page should look like this:

Other things that might go into our static directory later on include things like images (.png, .jpg files) that we may want to display on our web pages.

Step 8: Test your web app

Make sure you run and spend time on your web app to ensure that everything is working how you want it to! For example, make sure that values are being converted correctly. You want to make sure that after you click submit, it directs you to a different webpage, showing you the information you want it to. 

Sometimes, things that were working earlier can break when we add more code—this is sometimes called a regression

(But do take note of the difference between: 

So be sure to test all of the pages of your web app, even the ones you already tested to be sure they still work.

Step 9: Make sure README.md has a link to your web app.

Now also add a link in README.md to your running web app.  Include a link to the  root URL  such as

https://super-duper-lamp-ppwjwvrw45j3rx5g-5000.app.github.dev/

In addition, include a description of the custom function you wrote in Step 4, and the URL used to access it. 

Follow the instructions Step 4 of Part 1 of this lab to commit and push your changes to GitHub. Your commit message should mention something about part 3; or you can be more specific if you've done mutiple commits on this part of the lab.

We've been creating branches to keep track of your milestones. Create a branch called part2 as you did with part1 so that the mentors can see that state of your code right now (in case you later change anything). Here are the instructions again:

Now we want to switch back to working on the main branch. Click the "..." again, and then select "Checkout to..." from the menu. Another selection should appear at the top of the screen. Choose "main" (not "origin/main") to switch back to working on the main branch.


Then you are done with Part 3! 

In Part 4, we'll make this all look a lot more professional by adding Bootstrap navigation.

Links to:  Overview   Part 1    Part 2     Part 3    Part 4    Part 5    Part 6