Views

MVC Application using Python, Flask and Jinja Template

MVC Application using Python, Flask and Jinja Template
Page content

In this article, I will develop a Model-View-Controller (MVC) application using Python Language with Flask Framework and Jinja Template.

By the end of this article, we will learn:

  • How to develop a Model-View-Controller (MVC) application using Python Language with Flask Framework and Jinja Template.
  • Connecting to the SQLite Database to persist the data, using SQLAlchemy ORM.

If you have no time to read this article, but want to try the code for yourself, GitHub location is provided here. Go ahead and clone the code.


Overview

We will build a MVC application using Python with Flask Framework and Jinja Template for a Todo Task Application in that:

  • Each Todo Task has id, title, description, creation date, due date, status and comments.
  • User can Create New Task, Update Existing Task, View All Tasks as List, View a Task and Delete a Task.

Definitions

Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together. Python supports modules and packages, which encourages program modularity and code reuse.

Flask is a web framework, it’s a Python module that lets you develop web applications easily. It’s has a small and easy-to-extend core: it’s a microframework that doesn’t include an ORM (Object Relational Manager) or such features. Flask is a web application framework written in Python. Flask is based on the Werkzeg WSGI toolkit and the Jinja2 template engine.

Jinja Template is a web template engine for the Python programming language. Jinja is a fast, expressive, extensible templating engine. Special placeholders in the template allow writing code similar to Python syntax. Then the template is passed data to render the final document.

SQLAlchemy is a library that facilitates the communication between Python programs and databases. SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.

Microservices is an architecture that allows the developers to develop and deploy services independently. Each service running has its own process and this achieves the lightweight model to support business applications. One of the most popular types of APIs for building Microservices applications is known as “RESTful API” or “REST API”.

REpresentational State Transfer (REST) is an architectural style that defines a set of constraints to be used for creating web services. REST API is a way of accessing web services in a simple and flexible way without having any processing. All communication done via REST API uses only HTTP request.

Application Programming Interface (API) is a software intermediary that allows two applications to talk to each other. To simplify, an API delivers a user requests to a system and sends the system’s response back to a user.


Prerequisites

There are some prerequisites that are required for creating the MVC Application using Python, Flask and Jinja Template.

Familiarity with Technology and Frameworks

It is assumed that you have prior knowledge or familiarity with Python Language, Flask Framework, working with RDBMS databases and basic SQL commands, because I will not be covering the basics of these in this article.

If you are not familiar, then it is advised to get the basic knowledge of these before continuing.

Install Python

Install latest version for your platform from here. Select the latest version of Python 3 and download the Windows Installer. Click on the downloaded .exe and follow the on-screen instructions to complete the download.

Integrated Development Environment (IDE) for Code Development

You can use any Text Editor or IDE of your choice. I will be using the Visual Studio Code.

If you wish to use the Visual Studio Code, download the latest version from here. Click on the downloaded .exe and complete the installation.

Install the Python Extension for the VS Code

Install the Python extension for VS Code from the Visual Studio Marketplace.

  1. You can browse and install extensions from within VS Code. Bring up the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of VS Code or the View: Extensions command (Ctrl+Shift+X).
  2. Browse for Python and click on Install button.

Create Base Project

An important concept while working with Python projects is a virtual environment. It allows you to create an isolated Python environment, where all required packages for the project are installed.

Create a Project Environment

  1. Open command prompt and go to the folder where you want to create the Python Project and type following command:
mkdir mvc-python-flask-jinja
cd mvc-python-flask-jinja
  1. In that folder, use the following commands (one after another) to create and activate a virtual environment named .venv:
py -3 -m venv .venv
.venv\Scripts\activate

If the activate command generates the message Activate.ps1 is not digitally signed. You cannot run this script on the current system., then you need to temporarily change the PowerShell execution policy to allow scripts to run (see About Execution Policies in the PowerShell documentation):

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

Import Project into Visual Studio Code IDE

There are two ways of importing the project into the Visual Studio Code IDE.

  1. In the project folder, open the command prompt and type the command: code ., this would open the Visual Studio Code IDE and import the current project folder.
  2. Open the Visual Studio Code IDE and navigate to File > Open Folder > <Select Project Folder>.

Activate the virtual environment in Visual Studio Code IDE

Once the code is imported, we need to activate the virtual environment in the Visual Studio Code IDE also.

In Visual Studio Code IDE, open the Command Palette (View > Command Palette or (Ctrl+Shift+P)). Then select the Python: Select Interpreter command:

command-palette.jpeg
Command Palette in VS Code Editor

The command presents a list of available interpreters that VS Code can find automatically, including virtual environments. Select the folder that starts with .venv

venv-selection.jpeg
Select the .venv that starts with \.venv

Run Terminal: Create New Terminal (Ctrl+Shift+`)) from the Command Palette, which creates a terminal and automatically activates the virtual environment by running its activation script.

Install Flask and SQLAlchemy

Use the inbuilt Python package manager tool pip for installing dependencies. In your VS Code terminal enter the following one after another.

pip install flask
pip install flask-sqlalchemy

pip-install-flask.jpeg
Succeesful installation of Flask
pip-install-sqlalchemy.jpeg
Succeesful installation of SQLAlchemy

Setting up an initial Flask server

  • Create a new Python file in project root directory, let us call it app.py
  • Import Flask class and create its instance for this application
1from flask import Flask
2app = Flask(__name__)
3@app.route('/')
4def hello():
5    return 'Hello!'
  • Set the FLASK_APP Environment variable using the command
$env:FLASK_APP="app.py"
  • Start the server using the command
flask run

flask-run.jpeg
Start the server

  • When you navigate to http://localhost:5000/ you should see ‘Hello!’ on the browser
    browser-with-hello.jpeg
    Browser displaying Hello!

Change the port

As we have seen, default port for Python Flask Application Server is 5000. However, we can change the port.

Start the server using the command:

flask run --port 8080 

This would start the Python Flask Application Server on the port 8080.

Now that we have base flask app setup complete, lets dive in and start designing our Todo Task Application project.


Using HTML templates

Flask provides a render_template() helper function that allows use of the Jinja template engine. This will make managing HTML much easier by writing your HTML code in .html files as well as using python logic in your HTML code.

Update main app.py file

1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def index():
7    return render_template('index.html')

The index() view function returns the result of calling render_template() with index.html as an argument, this tells render_template() to look for a file called index.html in the templates folder.

Opening the URL http://localhost:5000/ in your browser will result in the debugger page informing you that the index.html template was not found.

index-html-template-not-found.jpeg
Index.html template Not Found Error

To fix this error, create a directory called templates. Then inside it, create a index.html file.

Add the following HTML code inside index.html:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <title>Todo Tracker Application</title>
 6</head>
 7<body>
 8   <h1 style="text-align:center;">Todo Tracker Application</h1>
 9</body>
10</html>

When we refresh the browser, if the changes are done properly, we should see the below page:

index-html-page.jpeg
index.html template


Enhance the Existing Application

Let us enhance our application to use the Python - Flask as Backend Server for Rest API, SQLite as Database and Jinja Templates as HTML Templating Engine.

Database Setup

We will first setup the database for persisting the data. I will be using Relational Database known as SQLite Database since the sqlite3 module, which we will use to interact with the database, is readily available in the standard Python library.

You can extend this to use any other Relational Database and only Database configurations needs to be changed.

Define Database configuration details - __init__.py

1from sqlalchemy import create_engine, MetaData
2from sqlalchemy.ext.declarative import declarative_base
3
4SQLALCHEMY_DATABASE_URI= "sqlite:///todo-app.sqlite"
5engine = create_engine(SQLALCHEMY_DATABASE_URI, echo = True)
6meta = MetaData()
7Base = declarative_base()

This would create a todo-app.sqlite SQLite Database file in the project folder.

Define models - models.py

 1from . import engine, Base
 2from sqlalchemy import Column, Integer, String, Date, ForeignKey
 3from sqlalchemy.orm import relationship
 4
 5class Tasks(Base):
 6    __tablename__="tasks"
 7
 8    systemTaskId = Column(Integer, primary_key = True)
 9    title = Column(String)
10    description = Column(String)
11    status = Column(String)
12    dueDate = Column(Date)
13    creationDate = Column(Date)
14    todoTaskCommentsSet = relationship("TodoTaskComments", backref = "task", lazy='subquery', cascade="all, delete-orphan")
15
16class TodoTaskComments(Base):
17    __tablename__="todoTaskComments"
18
19    systemTodoTaskCommentsId = Column(Integer, primary_key = True)
20    taskComments = Column(String)
21    creationDate = Column(Date)
22    task_systemTaskId = Column(Integer, ForeignKey('tasks.systemTaskId'))
23
24Base.metadata.create_all(engine)
  • Each class will represent the entity model.
  • Each class contains: tablename, Column and Column type
  • relationship represents the One To Many Relationship
  • Base.metadata.create_all will create all the tables and its dependencies in the database, at the application startup.

Update the /app.py

Update the app.py to include the SQLAlchemy Session for handling the persistance.

1from flask import Flask
2from . import engine
3from sqlalchemy.orm import sessionmaker
4
5app = Flask(__name__)
6Session = sessionmaker(bind= engine)

Now when we stop and restart the Server, we can see on the console that, 2 tables are created.

database-table-creation.jpeg
Tables Created in Database


Base Template File

Flask - Jinja Framework provides an option for creating the base template and extending it to all HTML files, so that we could move all common/repetitive code to this file.

Let us create a Base Template File - base.html.

This Base Template File will contain:

  • Bootstrap and Font-Awesome stylesheets
  • Navigation Menu for the all pages.
  • Block for Body content from Other templates.
  • Javascript for Bootstrap, jQuery, popper.js and any custom code.
 1<!doctype html>
 2<html>
 3    <head>
 4        <!-- Bootstrap CSS -->
 5        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"
 6            integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
 7        <!-- font-awesome css -->
 8        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css">
 9    </head>
10    <body>
11        <!-- Start of navigation menu -->
12        <div class="container">
13        <nav class="navbar navbar-expand-lg navbar-light bg-light">
14            <div class="container-fluid">
15                <a href="{{ url_for('index')}}" class="navbar-brand">
16                    <b>Todo Tracker Application</b></a>
17                <h4>{% block title %} {% endblock %}</h4>
18                <ul class="nav nav-pills">
19                    <li class="nav-item"><a href="{{ url_for('index')}}" class="nav-link" id="home" aria-current="page">Home</a></li>
20                    <li class="nav-item"><a href="{{ url_for('create_task')}}" id="create" class="nav-link">Create Task</a></li>
21                </ul>
22            </div>
23        </nav>
24        <!-- End of navigation menu -->
25        <br/>
26        {% block content %} {% endblock %}
27        <!-- It is always advisable to add the script tags inside body tag toward the end for faster rendering of the page  -->
28        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
29        <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
30        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.6.0/umd/popper.min.js"
31            integrity="sha512-BmM0/BQlqh02wuK5Gz9yrbe7VyIVwOzD1o40yi1IsTjriX/NGF37NyXHfmFzIlMmoSIBXgqDiG1VNU6kB5dBbA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
32        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.min.js"
33            integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9/vQ9uori45z4JjnFsRydbmQbmL5t1tQ0culUzyK" crossorigin="anonymous"></script>
34    </body>
35</html>

Display All Tasks as List

Let us first build the capability to display/view all tasks as list.

Update the HTML templates

Update the index.html template to extend the base.html

 1{% extends 'base.html' %}
 2
 3{% block title %}List All Tasks{% endblock %}
 4{% block content %}
 5  <table class="table table-striped table-hover">
 6    {%  if tasks|length > 0 %}
 7      <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 8    {% endif %}
 9    <thead align="center">
10      <tr>
11        <th>Title</th>
12        <th>Description</th>
13        <th>Due Date</th>
14        <th>Status</th>
15        <th>No. Of Comments</th>
16      </tr>
17    </thead>
18    <tbody align="center">
19          {% for task in tasks %}
20          <tr onclick="rowClick('{{task.systemTaskId}}')">
21              <td>{{ task.title }}</td>
22              <td>{{ task.description }}</td>
23              <td>{{ task.dueDate }}</td>
24              <td>{{ task.status }}</td>
25              <td>{{ task.todoTaskCommentSet|length }}</td>
26          </tr>
27          {% endfor %}
28      </tbody>
29  </table>
30{% endblock %}

Update the app.py

 1@app.route('/todo-app/tasks/', methods=['GET'])
 2def index():
 3  with Session() as session:
 4    data = []
 5    taskList = session.query(Tasks).all()
 6    
 7    for task in taskList:
 8      todoTaskCommentsList = session.query(TodoTaskComments).filter(TodoTaskComments.task_systemTaskId == task.systemTaskId)
 9      data.append(__jsonResponse(task, todoTaskCommentsList))
10  return render_template("index.html",tasks=data)
11
12def __jsonResponse(task, todoTaskCommentsList):
13  todoTaskCommentSet = []
14  taskDict = {
15    'systemTaskId' : task.systemTaskId,
16    'title' : task.title,
17    'description' : task.description,
18    'status' : task.status,
19    'dueDate' : task.dueDate,
20    'creationDate' : task.creationDate,
21    'todoTaskCommentSet' : todoTaskCommentSet,
22    }
23  
24  if (todoTaskCommentsList is not None):
25    for todoTaskComment in todoTaskCommentsList:
26      todoTaskCommentDict = {
27        'systemTodoTaskCommentsId': todoTaskComment.systemTodoTaskCommentsId,
28        'taskComments': todoTaskComment.taskComments,
29        'creationDate': todoTaskComment.creationDate,
30        'task_systemTaskId': todoTaskComment.task_systemTaskId
31      }
32
33      taskDict["todoTaskCommentSet"].append(todoTaskCommentDict)
34      
35  return taskDict

Let us look at this method in details:

  • Line#1: Defines the route for HTTP:GET for default homepage call (Display all tasks).
  • Line#3: Defines the session scope of the sessionmaker from sqlalchemy. All database transactions are wrapped inside the Session.
  • Line#5: Defines the query for the model using the Session object.
  • Lines#7-9: Extracting the TodoTaskComments model object list from the Task model object.
  • Line#12-35: This method converts the sqlalchemy object to json format.

When we refresh the browser, if the changes are done properly, we should see the below page:

blank-home-page.jpeg
index.html template without any data


Create New Task

Now let us go ahead and build view for creating new task.

Update app.py

 1@app.route('/todo-app/tasks/create/', methods=['GET', 'POST'])
 2def create_task():
 3    # If the request method is GET, display the create_task.html page.
 4    if(request.method == 'GET'):
 5        return render_template("create_task.html", statusList=__getTodoStatusAsList())
 6
 7    if not request.form:
 8        abort(400)
 9    task = Tasks(title = request.form.get('title'), description = request.form.get('description'), status = request.form.get('status'), 
10    dueDate = datetime.strptime(request.form.get('dueDate'), '%Y-%m-%d').date(), creationDate = date.today())
11    
12    with Session() as session:
13        session.add(task)
14        session.commit()
15        session.refresh(task)
16        
17    return redirect(url_for("index")) 
18
19def __getTodoStatusAsList():
20    statusList = {"Not Started", "In Progress", "On Hold", "Completed", "Deferred"}
21    return statusList

Let us analyze this method:

  • Line#1: With Python - Flask Framework, it is possible to define same method with different HTTP Methods like GET and POST.
  • Line#4-5: Defines the process for the HTTP:GET method.
  • Line#5: For HTTP:GET method, render create_task.html page.
  • Line#7 and below: Defines process for HTTP:POST method.

Create /templates/create_task.html

 1{% extends 'base.html' %}
 2
 3{% block title %}Create Tasks{% endblock %}
 4{% block content %}
 5
 6<form action="{{url_for('create_task')}}" method="post">
 7  <table class="table table-striped">
 8    <tbody align="center">
 9      <tr>
10        <th>Title</th><td><input type="text" name="title" id="title" class="form-control"></td>
11      </tr><tr>
12        <th>Description</th><td><textarea name="description" id="description" class="form-control"></textarea></td>
13      </tr><tr>
14        <th>Due Date</th><td><input type="date" name="dueDate" id="dueDate" class="form-control"></td>
15      </tr><tr>
16        <th>Status</th><td>
17            <select name="status" id="status" class="form-control">
18                <option value=''>--Select Status--</option>
19                {%  for status in statusList %}
20                    <option value="{{ status }}">{{ status }}</option>
21                {% endfor %}
22            </select>
23        </td>
24      </tr><tr>
25        <td colspan="2">
26            <button type="submit" class="btn btn-success">Submit</button>
27        </td>
28      </tr>
29    </tbody>
30  </table>
31</form>
32{% endblock %}

Now, if for some reason, the Python does not build the code automatically, stop and start the server.

In the Homepage, when we click on the Create Task Menu link, we are directed to the Create Task Page.

create-task-page.jpeg
Create Task Page

Once user completes the form, click on Submit and Home page will be displayed with the data.

home-page-after-create.jpeg
Home page After Create Action


Display One Item

Update app.py

 1@app.route('/todo-app/tasks/<int:id>', methods=['GET'])
 2def view_task(id):
 3  return render_template("view_task.html", task=__get_one_task(id))
 4
 5def __get_one_task(id):
 6  with Session() as session:
 7    task = session.query(Tasks).get(id)
 8
 9  if task is None:
10    abort(404)
11
12  return __jsonResponse(task, task.todoTaskCommentsSet)

Create /templates/view_task.html

 1{% extends 'base.html' %}
 2
 3{% block title %}View Task{% endblock %}
 4{% block content %}
 5  <table class="table table-striped">        
 6    <tbody align="center">
 7      <tr>
 8        <th>Title</th>
 9        <td>{{ task.title }}</td>
10      </tr><tr>
11        <th>Description</th>
12        <td>{{ task.description }}</td>
13      </tr><tr>
14        <th>Creation Date</th>
15        <td>{{ task.creationDate }}</td>
16      </tr>
17      </tr><tr>
18        <th>Due Date</th>
19        <td>{{ task.dueDate }}</td>
20      </tr><tr>
21        <th>Status</th>
22        <td>{{ task.status }}</td>
23      </tr>
24      {%  if task.todoTaskCommentSet|length > 0 %}
25      <tr>
26        <td colspan="2">
27          <table class="table table-bordered" style="text-align:center;">
28            <caption style="caption-side: top;font-weight:bold;text-align:center;">Task Comments</caption>
29            <thead>
30              <tr>
31                <th>Creation Date</th>
32                <th>Description</th>
33              </tr>
34            </thead>
35            <tbody>
36              {%  for taskComment in task.todoTaskCommentSet %}
37                <tr>
38                  <td>{{ taskComment.creationDate }}</td>
39                  <td>{{ taskComment.taskComments }}</td>
40                </tr>
41              {% endfor %}
42            </tbody>
43          </table>
44        </td>
45      </tr>
46      {% endif %}
47    </tbody>
48  </table>
49{% endblock %}

Now, if for some reason, the Python does not build the code automatically, stop and start the server.

In the Homepage, Click on the row and the Task will be displayed.

view-task-page.jpeg
View Task Page


Update One Task

Update app.py

 1@app.route('/todo-app/tasks/update/<int:id>', methods=['GET', 'POST'])
 2def update_task(id):
 3  # If the request method is GET, display the update_task.html page.
 4  if(request.method == 'GET'):
 5    return render_template("update_task.html", task=__get_one_task(id), statusList=__getTodoStatusAsList())
 6  
 7  with Session() as session:
 8    task = session.query(Tasks).get(id)
 9    if task is None:
10        abort(404)
11
12    task.title = request.form.get('title')
13    task.description = request.form.get('description')
14    task.status = request.form.get('status')
15    task.dueDate = datetime.strptime(request.form.get('dueDate'), '%Y-%m-%d').date()
16
17    todoTaskComments = TodoTaskComments(taskComments = request.form.get('taskComments'), creationDate = date.today(), task_systemTaskId=task.systemTaskId)
18    session.add(todoTaskComments) 
19
20    session.commit()
21    session.refresh(task)
22
23  return redirect(url_for("index")) 

Update /templates/index.html

 1{% extends 'base.html' %}
 2
 3{% block title %}List All Tasks{% endblock %}
 4{% block content %}
 5    <table class="table table-striped table-hover">
 6        {%  if tasks|length > 0 %}
 7            <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 8        {% endif %}
 9        <thead align="center">
10            <tr>
11                <th>Title</th>
12                <th>Description</th>
13                <th>Due Date</th>
14                <th>Status</th>
15                <th>No. Of Comments</th>
16                <th>Edit</th>
17            </tr>
18        </thead>
19        <tbody align="center">
20            {% for task in tasks %}
21            <tr onclick="rowClick('{{task.systemTaskId}}')">
22                <td>{{ task.title }}</td>
23                <td>{{ task.description }}</td>
24                <td>{{ task.dueDate }}</td>
25                <td>{{ task.status }}</td>
26                <td>{{ task.todoTaskCommentSet|length }}</td>
27                <td><a class="btn btn-warning" href="{{ url_for('update_task', id=task.systemTaskId)}}"><i class="fa fa-edit"></i></a></td>
28            </tr>
29            {% endfor %}
30        </tbody>
31    </table>
32{% endblock %}

Create /templates/update_task.html

 1{% extends 'base.html' %}
 2
 3{% block title %}Update Tasks{% endblock %}
 4{% block content %}
 5
 6<form action="{{ url_for('update_task', id=task.systemTaskId)}}" method="post">
 7  <table class="table table-striped">
 8    <tbody align="center">
 9      <tr>
10        <th>Title</th><td><input class="form-control" type="text" name="title" id="title" value="{{ task.title }}"></td>
11      </tr><tr>
12        <th>Description</th><td><textarea class="form-control" name="description" id="description">{{ task.description }}</textarea></td>
13      </tr><tr>
14        <th>Creation Date</th><td>{{ task.creationDate }}</td>
15      </tr><tr>
16        <th>Due Date</th><td><input class="form-control" type="date" name="dueDate" id="dueDate" value="{{ task.dueDate }}"></td>
17      </tr><tr>
18        <th>Status</th><td>
19          <select name="status" class="form-control" id="status">
20            <option value=''>--Select Status--</option>
21            {%  for status in statusList %}
22              <option value="{{ status }}" {% if task.status== status %} selected="selected"{% endif %}>{{ status }}</option>
23            {% endfor %}
24          </select>
25        </td>
26      </tr>
27      {%  if task.todoTaskCommentSet|length > 0 %}
28      <tr>
29        <td colspan="2">
30          <table class="table table-bordered" style="text-align:center;">
31            <caption style="caption-side: top;font-weight:bold;text-align:center;">Task Comments</caption>
32            <thead>
33              <tr>
34                <th>Creation Date</th>
35                <th>Description</th>
36              </tr>
37            </thead>
38            <tbody>
39              {%  for taskComment in task.todoTaskCommentSet %}
40                <tr>
41                  <td>{{ taskComment.creationDate }}</td>
42                  <td>{{ taskComment.taskComments }}</td>
43                </tr>
44              {% endfor %}
45            </tbody>
46          </table>
47        </td>
48      </tr>
49      {% endif %}
50      <tr>
51        <td colspan="2">
52          <table class="table table-borderless">
53            <tr>
54              <td>                        
55                <input type="button" class="btn btn-primary" onclick=addNewComments() value="Add New Comments"/>
56              </td>                     
57              <td id="commentsTable" style="display: none;">
58                <textarea class="form-control" name="taskComments" id="taskComments" style="height: 100px;width: 1090px;">{{ taskComments }}</textarea>
59              </td>                      
60            </tr>
61          </table>
62        </td>
63      </tr>
64      <tr>
65        <td colspan="2">
66          <button type="submit" class="btn btn-success">Submit</button>
67        </td>
68      </tr>
69    </tbody>
70  </table>
71</form>
72{% endblock %}

Now, if for some reason, the Python does not build the code automatically, stop and start the server.

In the browser, type http://localhost:8080/todo-app/tasks, Home page with Edit link is available.

home-page-with-edit-button.jpeg
Homepage with Edit Link

When we click on Edit Link, we are directed to the Update Task Page.

edit-task-page.jpeg
Update Task Page

Once user updates the form, click on Submit button and Home page will be displayed with the updated data.

home-page-after-update.jpeg
Home page After Update Action

If we click on the row now, we will see the updated data with comments.

view-page-after-update.jpeg
View page After Update


Delete Task

Update app.py

 1@app.route("/todo-app/tasks/delete/<int:id>", methods=["GET"])
 2def delete_task(id):
 3  # python -m pip install pyautogui 
 4  answer = pyautogui.confirm(text="Are you sure you want to delete?", title="Delete Confirmation")
 5  
 6  if answer == "OK":
 7    with Session() as session:
 8      task = session.query(Tasks).get(id)
 9      if task is None:
10        abort(404)
11  
12      session.delete(task)
13      session.commit()
14  return redirect(url_for("index"))

Update /templates/index.html

 1{% extends 'base.html' %}
 2
 3{% block title %}List All Tasks{% endblock %}
 4{% block content %}
 5    <table class="table table-striped table-hover">
 6        {%  if tasks|length > 0 %}
 7            <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 8        {% endif %}
 9        <thead align="center">
10            <tr>
11                <th>Title</th>
12                <th>Description</th>
13                <th>Due Date</th>
14                <th>Status</th>
15                <th>No. Of Comments</th>
16                <th>Edit</th>
17                <th>Delete</th>
18            </tr>
19        </thead>
20        <tbody align="center">
21            {% for task in tasks %}
22            <tr onclick="rowClick('{{task.systemTaskId}}')">
23                <td>{{ task.title }}</td>
24                <td>{{ task.description }}</td>
25                <td>{{ task.dueDate }}</td>
26                <td>{{ task.status }}</td>
27                <td>{{ task.todoTaskCommentSet|length }}</td>
28                <td><a class="btn btn-warning" href="{{ url_for('update_task', id=task.systemTaskId)}}"><i class="fa fa-edit"></i></a></td>
29                <td><a class="btn btn-danger" href="{{ url_for('delete_task', id=task.systemTaskId)}}"><i class="fa fa-trash" aria-hidden="true"></i></a></td>
30            </tr>
31            {% endfor %}
32        </tbody>
33    </table>
34{% endblock %}

Now, if for some reason, the Python does not build the code automatically, stop and start the server.

In the browser, type http://localhost:8080/todo-app/tasks, Home page with Delete Link is available.

home-page-with-delete-button.jpeg
Homepage with Delete Link

When we click on Delete button, Confirmation Box is displayed.

confirmation-box-for-delete.jpeg
Confirmation Box For Delete

Homepage after the task is deleted.

home-page-after-delete.jpeg
Home page After Delete Action


Conclusion

With this setup complete, we have come to the end of this article, where we have learnt:

  • How to develop a Model-View-Controller (MVC) application using Python Language with Flask Framework and Jinja Template.
  • Connecting to the SQLite Database to persist the data, using SQLAlchemy ORM.

Complete code for this project can be found at GitHub here. Go ahead and clone it.

Instructions on how to clone the code repository and run the project are provided on the GitHub project page.