Web Technologies/2021-2022/Laboratory 9
Dynamic pages (or dynamic content) display different content (and different layouts) based on various chosen parameters.
This Laboratory shows the usage of Jinja2 template inheritance and Flask-SQLAlchemy for creating content based on data found in an SQLite database.
Jinja2 template inheritance
editOne of the most important features of Jinja is template inheritance. This mechanism allows for the creation of a template skeleton containing the common elements for pages, allowing for the declaration of various block sections which child templates (the templates that inherit the base template) can override.
Blocks are defined using the {% block <blockname> %} ... {% endblock %} directives
Examples
editDefining a base template (base.html):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet"/>
<title>{% block title %} {% endblock %}</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
Overriding the base template (post.html):
{% extends "base.html" %}
{% block title %} {{ post.title }} {% endblock %}
{% block content %}
<h1> {{ post.title }} </h1>
<i>Written on {{ post.posted }} by {{ post.posted_by.username }}</i>
<p>
{{ post.body }}
</p>
{% endblock %}
Overriding the base template (posts.html):
{% extends "base.html" %}
{% block title %}Blog Posts{% endblock %}
{% block content %}
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('display_post', post_id=post.id) }}">
<h1>{{ post.title }}</h1>
</a>
<i class="date">Posted on {{ post.posted }}</i>
<p>{{ post.body[:14] }}...</p>
</li>
{% endfor %}
</ul>
{% endblock %}
Template inheritance in a sense resembles the inheritance concept from Object-Oriented Programming.
It helps with reduction of the codebase, as all the common elements can be placed in the base template.
Flask-SQLAlchemy creating and populating an SQLite database
editAs seen previously, in Laboratory 7, Flask-SQLAlchemy provides an Object-Relational Mapper, and ways to define Models and relationships between models.
In order to be able to use the templates in the template inheritance examples, we must first build the User and Post models, as well as define a script to create and populate the SQLite database, and routes for Flask.
Definition of the models:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
age = db.Column(db.Integer)
def __repr__(self):
return f"User: {self.username}\nEmail: {self.email}"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
posted_by = db.relationship('User')
posted = db.Column(db.DateTime, nullable=False)
title = db.Column(db.String(120), nullable=False)
body = db.Column(db.String(500), nullable=False)
reads = db.Column(db.Integer)
NOTE: The user_id attribute in the Post model is created as a Foreign Key. Additionally, the posted_by attribute in the User model is using db.relationship(). Making it point to a query result for the corresponding field in the User table. Therefore, completing the relationship between the User -> Post tables (A user holds one or multiple posts). Flask routing here is based on using Flask-SQLAlchemy queries to retrieve all or individual posts, which are to be rendered by the appropriate templates.
@app.route('/')
def display_content(content=None):
return "Hello world"
@app.route('/user/profile/<int:user_id>')
def dispay_user_profile(user_id):
user = User.query.filter_by(id=user_id).first()
return render_template('user_profile.html', user=user)
@app.route('/users')
def dispay_all_users():
users = User.query.all()
return render_template('users.html', users=users)
@app.route('/posts')
def dispay_all_posts():
posts = Post.query.all()
return render_template('posts.html', posts=posts)
@app.route('/post/<int:post_id>')
def display_post(post_id):
found_post = Post.query.filter_by(id=post_id).first()
return render_template('post.html', post=found_post)
The script responsible to create the database and add entries (based on the defined models) is:
from datetime import datetime
from flaskapp import app, db, User, Post
with app.app_context():
db.create_all()
# Populate database with some entries
admin = User(username='admin', email='admin@example.com', age=42)
regular_user = User(username='user', email='user@example.com', age=20)
post1 = Post(user_id=1,
posted=datetime.strptime("5 November 2021", "%d %B %Y"),
title="Hello World!",
body="This is the first post on this blog.")
post2 = Post(user_id=1,
posted=datetime.strptime("17 November 2021", "%d %B %Y"),
title="Post Number two",
body="This is the second post on this blog.")
post3 = Post(user_id=2,
posted=datetime.utcnow(),
title="Post number three",
body="This is the third post on this blog.")
db.session.add(admin)
db.session.add(regular_user)
db.session.add(post1)
db.session.add(post2)
db.session.add(post3)
db.session.commit()
The current directory structure of the files should look like:
├── create_db.py
├── flaskapp.py
├── static
│ └── style.css
├── templates
│ ├── base.html
│ ├── post.html
│ ├── posts.html
│ ├── user_profile.html
│ └── users.html
├── venv
Useful links: