from IPython import display
Creating a flask-bsed online message bank
Intro
In this blog post, we would be creating a flask-based online message bank deployed as a python web app using the google cloud service and mySQL database.
A web application is a computer program that utilizes web browsers and web technology to perform tasks over the Internet, accessible by users with internet connections (Stackpath).
The skills you’ll need are:
- Flask fundamentals, including render_template(), the basic anatomy of templates, and user interactions.
- Database skills, including adding items to databases and displaying them.
- Basic CSS (Cascading styling sheets) in order to add a bit of personal flare to your webapp.
Built With Flask - Web framework and related modules
Sqlite3 - Database
Bootstrap - Styling
Jinja2 - Template formatting
Git repo: https://github.com/linnilinnil/message-bank
Functionalities Overview
The app you’re going to build is a simple message bank. It should do two things:
- Allow the user to submit messages to the bank.
- Allow the user to view a sample of the messages currently stored in the bank.
- Additionally, you should use CSS to make your app look attractive and interesting.
Setting up the basics
We first create a .py file called app.py where we would import modules and define the application frame.
from flask import Flask, render_template,request, redirect, url_for, g
import sqlite3
= Flask(__name__)
app
# Code for different pages to be inserted below
# Begin -------
# End ---------
if __name__ == "__main__":
=8000, debug=True) app.run(port
Type python app.py in the terminal to run the flask app.
Alternatively, without the app.run section, we run the following commands in the terminal:
(Setting the environmental variables to dev and debug mode will allow you to see updates of the web app without the need to resetart the web app everytime)
export FLASK_ENV=development
export FLASK_DEBUG=1
flask run
which will show you the following lines:
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with fsevents reloader
* Debugger is active!
* Debugger PIN: 331-048-817
following the link after Running on to check out your ‘website’.
Next, we write the separate functions for different pages: - \: the root directory, we will have this redirected to the “home” page - home: front page of the message bank - submit: page for submitting the message, contains a form, allows post&get - view: page for viewing random messages, contains a form, allows post&get
# @app,route... are decoraters ,i.e., functions wrappers that allow you to execute
# codes before and after it that does not change the func definition
# in this case, it specifies what function would be called to render the page
# under a specific web directory/route
@app.route("/", methods=['GET'])
def index():
return redirect(url_for('home'))
@app.route("/home/", methods=['GET', 'POST'])
def home():
return render_template("home.html")
@app.route("/submit/", methods=['POST', 'GET'])
def submit():
# if we access the page from another page with differnt url
if request.method == "GET":
return render_template("submit.html")
# if we submit a form on this page and land on the same page again
else:
# the insert_message function updates the messages database
insert_message(request)# pass in the user handle to write a thank you message to the user
return render_template("submit.html",
=request.form['name'])
name
@app.route("/view/", methods=['GET', 'POST'])
def view():
# default: gives one message
if request.method == "GET":
# pass in the list of messages we retrieve from the database for display
return render_template('view.html',
= random_messages(1))
messages else:
# add a slider that allows users to change the num. of messages displayed
return render_template('view.html',
= random_messages(int(request.form['n']))) messages
Enabling submissions
After defining the functions for different routes, we fill in the functions for updating the message dataset based on user input and the function for displaying random messages.
# this function is used to retrive the message database
def get_message_db():
# g is an object provided by Flask.
# It is a global namespace for holding any data you want during a single app context.
# in this case, we create a message_db variable under g to store the dataset containing user messages
try:
# if the db already exists, just return it
return g.message_db
except:
# if not, we establish the database
= sqlite3.connect("messages_db.sqlite")
g.message_db = g.message_db.cursor()
cursor # we create a data table with id (primary key), handle (user name), and message (user message)
# called messages if it does not exist in messages_db yet
= """
cmd CREATE TABLE IF NOT EXISTS messages(
id INTEGER PRIMARY KEY AUTOINCREMENT,
handle TEXT NOT NULL,
message TEXT NOT NULL
);
"""
cursor.execute(cmd)
g.message_db.commit()return g.message_db
Note: when working directly with SQL commands, it is necessary to run db.commit() after inserting a row into db in order to ensure that your row insertion has been saved.
You should ensure that the ID number of each message is unique. One way to do this is by setting the ID number of a message equal to one plus the current number of rows in message_db. Another way to do this is setting it as an autoincremented primary key.
Don’t forget to close the database connection within the function!
We then write the function to update the database.
# this function inserts user input message to the message_db database
def insert_message(request):
# establish connection to the database
= get_message_db()
db # extract message and handle from the request form
= request.form['msg']
message = request.form['name']
handle # update the database
= 'INSERT INTO messages (handle, message) VALUES (?, ?)'
cmd
db.execute(cmd,(handle, message))# commit changes and close connection
db.commit() db.close()
Viewing Random Submissions
In this part, we will write a function called random_messages(n) which will return a collection of n random messages from the message_db, or fewer if necessary.
# fetch n random messages from the database
# if n > # of current rows, n = # of current rows
def random_messages(n):
# establish connection
= get_message_db()
db = g.message_db.cursor()
cursor # select random messages, fetch both the handle and message
= 'SELECT handle, message FROM messages ORDER BY RANDOM() LIMIT ?'
cmd = cursor.execute(cmd,(n,)).fetchall()
msgs # close connection
db.close()return msgs
Defining html templates
In this part, we will define the templates for all the pages: home page (home.html), message bank submission page (submit.html), and message bank random viewing page (view.html).
We will first define a base template which styles the pages we would be showing for the web apps, then have the submit.html extends the base.html. Both htmls were to be put under a folder called template. Here are more examples about extends from flask and jinja.
<!DOCTYPE html>
<!--created by marlene lin-->
<html lang="en">
<head>
<!-- Required meta tags -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS and my own -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/favicon.ico" rel="icon">
<link href="/static/styles.css" rel="stylesheet">
<!-- Block that allows other pages to customize title -->
<title>Message bank: {% block title %}{% endblock %}</title>
</head>
<body>
<div class="container">
<div class="row header">
</div>
<div class="row">
<div class="col-md-1 col-lg-1 col-xl-1"></div>
<div class="col-md-3 col-lg-3 col-xl-3 sidebar">
<!-- navbar -->
<ul class="nav flex-column">
<li class="nav-item">
<p><i><a class="nav-link" href="/home"><b>Welcome: </b></a></i></p>
</li>
<li class="nav-item">
<a class="nav-link" href="/submit">Write a Message</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/view">View messages</a>
</li>
</ul>
</div>
<!--the block where the main content is hosted -->
<div class="col-md-8 col-lg-8 col-xl-8 maininfo">
{% block main %}{% endblock %}</div>
<div class="col-1"></div>
</div>
<div class="row">
<div class="col footer">
</div>
</div>
</div>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>
Next, we will define submit.html which has the following three user interface elements:
- A text box for submitting a message.
- A text box for submitting the name of the user.
- A “submit” button.
{% extends "base.html" %}<!-- change title-->
{% block title %}
Submit
{% endblock %}
{% block main %} <!-- define a form that takes in user handle and message-->
<div class="card">
<div class="card-header">
<i><b>Kirby would appreciate your messages.</b>
</div>
<div class="card-body">
<form method="post">
<label for="name">Your name or handle:</label>
<input name="name" id="name">
<br>
<label for="msg">Your message:</label>
<input name="msg" id="msg">
<br>
<br>
<input type="submit" value="Submit form">
</form>
</div>
</div>
<br>
<!-- sends a thank you message to the user-->
<!-- the name argument is passed in to the render_template method when the request type is GET-->
{% if name %}<b>{{name}}</b>:
Hello <br>
<br>
Thank you for submitting the form. <a href="/view">View messages</a> or resubmit another message.
You can now view messages by going to
{% endif %}<br>
{% endblock %}
"static/pics/Screenshot 2023-02-14 at 2.01.03 AM.png") display.Image(
'static/pics/Screenshot 2023-02-14 at 2.01.07 AM.png') display.Image(
Then, we define the view.html page, which is somewhat similar.
{% extends "base.html" %}
{% block title %}
View
{% endblock %}
{% block main %}
<div class="card">
<div class="card-header">
<i><b>Kirby has chosen some messages for you.</b>
</div>
<div class="card-body">
<!-- messages is passed in to the render_template function when the request type is POST
the messages come from the random_messages function which returns a list of tuple, (handle, message) -->
{% for m in messages %}<br>
<!-- handle -->
<p> From {{m[0]}} :</p>
<!-- message -->
<b>{{m[1]}}</b>
<br>
{% endfor %}</div>
</div>
<br>
<!-- allow user to adjust the number of messages displahyed -->
<form method="post">
<input type="range" id="n" name="n" min = "1" max = "9" step = "2" list="values">
<label for="n">num. of messages</label>
<datalist id="values">
<option value="1" label="1"></option>
<option value="3" label="3"></option>
<option value="5" label="5"></option>
<option value="7" label="7"></option>
<option value="9" label="9"></option>
</datalist>
<br>
<input type="submit" value="Change">
</form>
<br>
{% endblock %}
'static/pics/Screenshot 2023-02-14 at 2.01.14 AM.png') display.Image(
#more messages
"static/pics/Screenshot 2023-02-14 at 2.01.23 AM.png") display.Image(
Finally, the home page.
{% extends "base.html" %}
{% block title %}
Home
{% endblock %}
{% block main %}<div class="card">
<div class="card-header">
<i><b>Kirby thinks you are an amazing person and they are grateful that you exist.
</div>
<div class="card-body">
<blockquote class="blockquote mb-0">
<p> Vent. <br> Scream to the void. <br>Blow off some steam. </b></i>
</br>
</p>
<footer class="blockquote-footer"></footer>
</blockquote>
</div>
</div>
<br>
<h6>By: Marlene Lin</h6>
<h6>ins: @m.lin01</h6>
<br>
{% endblock %}
"static/pics/Screenshot 2023-02-14 at 2.00.15 AM.png") display.Image(
Additional stylings
We create a folder called static to store page resources (background images), stylesheet, and page icon. I perform the following stylings to the message bank:
/* Using non-default font,
notice this won't be good enough for cross-browser support*/
@font-face {
font-family: 'Kirby-nnGM';
src: url('/static/font/Kirby-nnGM.ttf');
}
/* customized body, card */
body {background: url('/static/pics/dark.jpeg') no-repeat center center fixed;
background-color: black;
-webkit-background-size: cover;
-moz-background-size: cover;
background-size: cover;
-o-background-size: cover;
font-family: 'Kirby-nnGM';
font-size: 14px;
}
h1 {font-size: 35px;
}
.header{
height: 100px;
}.card{
background-color:rgb(116, 45, 51);
color: white;
}.maininfo {
color: rgb(244, 217, 217);
background-color: rgba(192, 79, 109, 0.6);
font-size: 16px;
}
/* customized navigation bar */
.sidebar {font-size: 30px;}
.nav-link { color: #a46a6a;
font-size: 25px;}
.nav-link:hover { color: #fff;
font-size: 25px;
}
/* customized position, use this as a class */
.center{
margin-left: auto;
margin-right: auto;
width:70%;
}/* customized bottons */
.btn-primary{
border-color: #fff;
}
.btn-outline-info:hover {
color: #fff;
background-color:#C75100;
border-color: #C75100;
}
.btn-primary:hover{
background-color: rgb(199, 81,0);
border-color: white;
color:black;
}.btn:focus{
border-color:white;
}
/* customized links */
:link {
acolor: rgb(199, 81,0);
background-color: transparent;
text-decoration: none;
}:visited {
acolor: rgb(147, 106, 130);
background-color: transparent;
text-decoration: none;
}
/* customized forms */
.form-control{
background-color: rgba(199,199,188,0.4);
box-shadow: 0 1px 1px yellow inset, 0 0 8px red;
outline: 0 none;
}
Honestly, I like Times better but just for the assignment’s sake.
"static/pics/out.png") display.Image(