Lab 8: Introduction to
Web Applications

Part 3: Templates

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: Fork your repl from Part 2 and update README.md

As at the start of part 1, we are going to fork the repl again, so that we have a snapshot of each stage of our work.

Fork the repl, and this time, since you are working in a pair, add your pair partner(s) to the forked repl.

Then update your README.md file with a link to your repl for Part 3 (just like you did at the start of Part 2), except this time there is just one repl for both of you (or all three of you).

# Part 3


Repl: https://replit.com/@phtcon/RichColorlessExponents-7


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) called templates.
Inside the templates folder, the first file you should create and store is one called
layout.html. (Please note: .html, not .htm)

Here's what that looks like in repl:

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

The layout.html file should look like this:

<!doctype html>

<html>

<head>

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

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

</head>


<body>

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

</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:

  • {% block title %}{% endblock %}

  • {% block content %}{% endblock %}

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 subdirectory. Adding it will look like this. The code that you need to paste in is listed after the animation.

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:

  • @app.route('/') stays the same because we are still writing a function to display the home page.

  • We change the name of the function from hello to render_home because we are rendering the home page (to render a page means to create the content for that page). Honestly, we could have called this function anything (as long as the name doesn't duplicate another function), but it's useful to keep the names consistent according to some pattern.

  • Instead of returning a string with the contents of the page (e.g. return "Hello World!") we return a function call to render_template, passing in the name of a .html file. That .html file can contain any valid HTML code, but it can also contain things like this: {% extends "layout.html" %} that are part of the Jinja Template language (https://jinja.palletsprojects.com/en/3.0.x/templates/).

    It isn't necessary to read all of the details of Jinja right now, but I do want you to understand where these non-HTML things are coming from.


Now give it a try!

Go back into your repl, 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 Console 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 Console output for the clues as to what went wrong.

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

Bu
t 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 repl).

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 in the animation at right that 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.

Now, click to create a new file in the templates folder called ctof.html, as shown in the animation at right.

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 template, which requires two blocks:

  • a title block

  • a content block

Those 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:

  • Attributes are the things such as type="text" and name="ctemp" that appear inside HTML open tags.

  • The <form> open tag has an attribute action="/url"that specifies the web address where we are going to redirect the user when they press enter.

    • In these examples, that is the name of a template that we are going to supply in the next step (e.g. /ctof_result)

  • The <input> open tag has attributes for type. Some of the available values are "text", "submit" or "number".

    • Use "text" when the input is a string.

    • Use "number" when the input is expected to be a number. But: note that using "number" will constrain the value in the form to be a number, but it will still be treated as a string on the Python side, and will still have to do the conversion from string to number.

    • Use "submit" to get a submit button that can submit the form.

  • The <input> open tag also has an attribute value which can be used to specify a default initial value for the field.

  • In an <input type="submit"> element, the value attribute is used to specify the text that goes on the button.

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.

  • If you get Internal Server Error check the Console window as described earlier.


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 th
at 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:

  • get the input from the form

  • compute some results with it

  • inject those results into the template before rendering it

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.

Note however, that since this code contains a function call to ctof, it must come after the definition of ctof in your file!
So move the definition of render_ctof_result as needed so that it comes after the definition of ctof

@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:

  • @app.route('/ctof_result') says that we are defining the code that will render the response to the url /ctof_result

  • We use render_ctof_result as the name of our function in order to stay with a consistent naming pattern. While not required, this does make things much easier as our code gets bigger.

  • The try / except block is used so that if there is an error in the code, we can control the message that comes up on the page. If there is a ValueError (e.g.if we pass a value into float() function that cannot be converted to a number), then the page will contain the text "Sorry: something went wrong".

  • Inside the try block, we see ctemp_result = float(request.args['ctemp'])

    • Inside the float() function call, request.args['ctemp'] will give us the value of the form field with name ctemp

      The 'ctemp' comes from the name="ctemp" shown in the excerpt from ctof.html below:

      <form action="/ctof_result">
      <p>Celsius Temp: <input type="number" name="ctemp" value="20.0"></p>
      <input type="submit" value="Submit">
      </form>

    • In general, inside the code to process a web form, we can use request.args['whatever'] to get the value of any <input> element that has the attribute name="whatever"

    • Values from request.args always get passed to us as strings; since we need a numerical value, we use float() to convert it to a number.

  • The line ftemp_result = ctof(ctemp_result) is just an ordinary function call to our ctof function

  • The return render_template('ctof_result.html', ctemp=ctemp_result, ftemp=ftemp_result ) works just like the other calls to render_template that we've already seen, except that:

    • this time we also assign values to ctemp and ftemp, which are variables that we are going to use inside the template.

    • If you look back at ctof_result.html you'll see <p> In Celsius: {{ ctemp }}. In Fahrenheit: {{ ftemp }} </p>

    • The value assigned to ctemp, which is ctemp_result, will be substituted everywhere we have
      {{ ctemp }} in the template.

    • The value assigned to ftemp, which is ftemp_result, will be substituted everywhere we have
      {{ ftemp }} in the template.

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:

  • we'll start by adding a line to the home page that routes to the page ftoc

  • we'll then add a form page ftoc.html, and the changes to main.py so that we can route to that page.

  • we'll then add a result page, and the changes to main.py to make it compute the result and display it

In this case, I'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:

  • the first link on the home page works

  • the second one gives you a Not Found error. We'll fix that next by making a route for /ftoc

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 shoudl find that clicking Submit on the form gives you a Not Found.

Now, try it in a regular browser. As a reminder, you can do that by clicking where it says "open in a new tab"

In most regular 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 (this mini browser inside repl doesn't show this as clearly.) Here's what it looks like in a regular browser; note how the URL changes as we navigate from page to page.

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 below the definition of the 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 regular 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 I tried the mtokm_result while setting up this lab, I got a Bad Request error.

This is a close relative of the Internal Server Error.

It often results from a mismatch between

  • the names of the request parameters (e.g. the name attributes in <input> elements) the form is sending

  • the names of the parameters the code is expecting.

Just as with an Internal Server Error, the Console 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:

  • What does the user need to be told about what to expect?

  • What inputs do you need from the user (these typically correspond to the parameters to the function)?

  • What data types are these? (number? string?)

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

  • What do you need to tell the user so they know what to enter?

  • What information do you need the user to provide?

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:

  • put the instructions to the user in a <p> element

  • make a form that has fields on it, i.e. <input> elements for each thing you want to ask for.

  • the action should be the web address of a template that will display the results (usually the name of your function followed by _result).

  • add an <input type="submit" value="Submit" > element so there's a button to submit the form

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 looks like in repl.it (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:

  • serif, sans-serif, cursive, fantasy, and monospace.

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 repl and your web app.

You should have already added a link to your repl in Step 1 of this part.

Now also add a link to your running web app. That is, change this

# Part 3


Repl: https://replit.com/@phtcon/TragicHoarseCompilers-5


to this:

# Part 3

Repl: https://replit.com/@phtcon/TragicHoarseCompilers-5

Web
app: https://tragichoarsecompilers-5.phtcon.repl.co/


Then you are done with Part 3!

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