When building web applications with Flask, templates are a powerful tool that allows us to create dynamic and reusable HTML pages. Instead of returning plain strings from our routes, templates enable us to separate our Python logic from our HTML presentation, making our code more organised and easier to maintain.
Separation of Concerns: Templates separate the presentation logic (HTML) from the business logic (Python code), making your application more modular and easier to maintain.
Dynamic Content: Templates allow you to inject dynamic data into your HTML, creating personalized and data-driven web pages.
Code Reusability: With template inheritance, you can create a base layout that can be extended by other pages, reducing code duplication.
Enhanced Readability: Templates make your HTML more readable by using simple template tags instead of complex string concatenations in your Python code.
Before we dive into using templates, let's set up our project structure:
Create a folder named templates in your project root using mkdir. This is where Flask will look for your HTML templates.
Create another folder named static using mkdir. This folder will store static files like CSS, JavaScript, and images.
Your project structure should look something like this:
your_project/
│
├── app.py
├── templates/
│ ├── base.html
│ └── other_templates.html
│
└── static/
├── css/
├── js/
└── images/
In this activity, we'll create a simple HTML template and render it using Flask.
Rename your current app.py file to task2.py with mv if you would like to keep it. Create a new app.py with touch.
Then, to create the basic.html, there is no need to cd into the templates directory. Simply use touch templates/basic.html.
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/basic')
def basic():
return render_template('basic.html')
if __name__ == '__main__':
app.run(debug=True)
templates/basic.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Basic Template</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
In this activity, we're introducing the concept of rendering HTML templates in Flask:
We import the render_template function from Flask, which allows us to serve HTML files.
We create a route /basic that uses render_template() to serve our basic.html file.
The basic.html file is a simple HTML document that Flask will render when we visit the /basic route.
Run the app and visit http://127.0.0.1:5000/basic in your web browser.
You should see a web page displaying "Hello World!" as a heading.
Try editing and adding a few basic HTML elements in the body of basic.html.
In this activity, we'll pass variables from our Flask app to our HTML template. This allows us to dynamically insert content into our web pages.
Once again, use touch to create the greet.html in the templates subdirectory.
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/greet/<name>')
def greet(name):
return render_template('greet.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
templates/greet.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Greet User</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
This activity introduces dynamic content in our templates:
We create a route that captures a name parameter from the URL.
We pass this name to our template using render_template('greet.html', name=name).
In our HTML template, we use double curly braces to insert variables. E.g. {{ name }} to insert the value of name into our HTML.
Run your app and navigate to http://127.0.0.1:5000/hello/Alice to see the message.
Change the name in the URL and see what happens!
Flask uses Jinja2 as its template engine. Jinja2 is a powerful and flexible templating language that allows you to dynamically generate HTML content. Let's explore some basic Jinja2 syntax:
Syntax: {{ variable_name }}
Example: <p>Hello, {{ name }}!</p>
For loops:
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
If statements:
{% if user_logged_in %}
<p>Welcome back!</p>
{% else %}
<p>Please log in.</p>
{% endif %}
Filters in Jinja2 are functions that modify or format variables when they are displayed, allowing you to transform data directly in your templates without changing the original variable.
Syntax: {{ variable | filter_name }}
Example: <p>{{ name | capitalize }}</p>
Templates become even more powerful when we introduce control flow statements like loops and conditionals.
Once again, use touch to create the inventory.html in the templates subdirectory.
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/inventory')
def inventory():
inventory_items = ['apple', 'banana', 'cherry']
return render_template('inventory.html', inventory=inventory_items)
if __name__ == '__main__':
app.run(debug=True)
templates/inventory.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>User Inventory</title>
</head>
<body>
<h1>Your Inventory:</h1>
<ul>
{% for item in inventory %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if 'apple' in inventory %}
<p>You have an apple.</p>
{% else %}
<p>You do not have an apple.</p>
{% endif %}
</body>
</html>
This activity showcases how we can use programming constructs within our templates:
We pass a list of inventory_items to our template.
In the template, we use a for loop ({% for item in inventory %}) to iterate over the inventory items and display them as a list.
We use an if statement to check if 'apple' is in the inventory and display an appropriate message.
These control structures allow us to create more complex and dynamic HTML based on the data we're working with. This is crucial for creating data-driven web pages where content changes based on user input or database queries.
Run your Flask app and visit http://127.0.0.1:5000/inventory.
You should see a list of inventory items and a message about whether you have an apple.
Modify your inventory and revisit the page to see your changes.
Template inheritance allows us to create a base template that other templates can extend. This is useful for reusing common elements like headers, navbars and footers. Once again, use touch to create the base.html and test.html in the templates subdirectory.
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/test/<message>')
def test(message):
return render_template('test.html', message=message)
if __name__ == '__main__':
app.run(debug=True)
templates/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Flask App</title>
</head>
<body>
<header>
<nav>
<a href="{{ url_for('test', message='Welcome') }}">Home</a>
<a href="{{ url_for('test', message='This is a test') }}">Test</a>
</nav>
</header>
<div class="container">
{% block content %}
{% endblock %}
</div>
<footer>
<p>© 2024 My Flask App</p>
</footer>
</body>
</html>
templates/test.html
{% extends 'base.html' %}
{% block content %}
<h1>Test Message</h1>
<p>{{ message }}</p>
{% endblock %}
Template inheritance is a key concept in building maintainable web applications:
We create a base.html template that defines the common structure of our web pages, including navigation and footer.
We use {% block content %} in base.html to define areas that child templates can override.
In test.html, we use {% extends 'base.html' %} to inherit from the base template.
We define a content block in test.html that will be inserted into the corresponding block in base.html.
This approach allows us to maintain a consistent layout across our website while easily customizing the content of individual pages. It's particularly useful for creating a cohesive look and feel across a large web application.
Run your Flask app and visit http://localhost:5000/test/Hello%20World (replace Hello%20World with any message).
Observe how test.html fills the content block in base.html.
Click on the navigation links to see how url_for works.
In this activity, we will simplify how Flask's url_for function works to generate URLs and how to use static assets like images and CSS files in your web app. Create the files below using touch and find yourself an image to use for static/profile_pic.jpg.
app.py
from flask import Flask, render_template, url_for
app = Flask(__name__)
@app.route('/profile/<username>')
def profile(username):
return render_template('profile.html', username=username)
if __name__ == '__main__':
app.run(debug=True)
templates/profile.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ username }}'s Profile</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Welcome, {{ username }}!</h1>
<img src="{{ url_for('static', filename='profile_pic.jpg') }}" alt="Profile Picture">
</body>
</html>
static/style.css
body {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
}
h1 {
color: #333;
}
img {
max-width: 150px;
}
What is url_for: url_for() is a Flask function that dynamically generates URLs for routes and static files. This ensures that links in your templates will work correctly, even if you change the structure of your routes later.
Using url_for for Routes: When you pass a route (like profile) and any required parameters (like username), url_for() creates the correct URL. For example, url_for('profile', username='John') generates /profile/John.
Using url_for for Static Files: Static files, such as images and stylesheets, are placed in a folder named static. To include them in your HTML, use url_for('static', filename='style.css'), which creates the correct path to the file (e.g., /static/style.css).
Why use url_for: It makes sure your links and static assets always point to the right place, even if you change file locations or update your routes.
Run your Flask app and visit http://localhost:5000/profile/John (replace John with any name).
The page will display your name and a profile picture using the image from the static folder.
Change the image file in the static folder and update the CSS file to see the effects on the page.
Remember to commit and push your code when done! Here are the terminal commands to do so:
Commit your changes with a meaningful message:
git commit -am "Your message here"
Note: The -a flag automatically stages all modified files before committing, -m is to add a message.
Push your changes to GitHub:
git push origin main