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

edit

One 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

edit

Defining 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

edit

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

Dynamic_web_page

Jinja2 Template Inheritance

Flask-SQLAlchemy Model declaration and relationships