We will be using a simple guestbook application to show you a range of computing and database systems and how they operate. The application adopts a model-view-controller (MVC) architecture initially in order to allow us to separate the data backend being used (model) from the HTML user interface (view), and the application logic (controller). The Guestbook will be implemented via Python and its Flask web framework.

To begin with, on a Linux VM or on your machine, clone the repository and change into the directory containing the code.

git clone https://github.com/bfritscher/guestbook-src.git
cd guestbook-src/01_mvc_pylist

This is the simplest version and has a file directory structure shown below (including the descriptions of what each file does).

.
├── app.py             # Controller
├── Model.py           # Model (abstract base)
├── model_pylist.py    # Model (Python list implementation)
├── requirements.txt   # List of python packages needed
├── static
|   └── style.css      # View (stylesheet)
└── templates
    ├── index.html     # View (Index view)
    └── layout.html    # View (abstract base)

We will start with the model code. We wish to support arbitrary backend databases, yet we do not want to re-implement all of our application code whenever a new backend database is used. The beauty of an MVC-style architecture is that the separation of concerns allows you to support this. Towards this end, the Model.py code below defines a simple abstract interface for two operations on a backend database. The select() returns all rows of the database as a list of lists where each row contains a name, email, date, and message for a guestbook entry. The insert() takes a name, email, and message, then generates a date, before inserting all four into the database.

Model.py

class Model():
    def select(self):
        """
        Gets all rows from the database as a list of lists.
        Row consists of name, email, date, and message.
        :return: List of lists containing all rows of database
        """
        pass

    def insert(self, name, email, message):
        """
        Inserts entry into database
        :param name: String
        :param email: String
        :param message: String
        :return: none
        :raises: Database errors on connection and insertion
        """
        pass

Our database backends will be derived from this abstract model, but hide the implementation of the database backend from the application. As long as any of our derived models supports the calling interface above, they will work with the application.

We start with a simple model implemented as an in-memory Python list. As this model is stored in program memory, it will be destroyed when we terminate our application. We will be replacing this model with a persistent database backend later. As the code in model_pylist.py shows, the model class is derived from the abstract Model. Its constructor sets the guestentries attribute as a blank list and each insert() appends a list containing the row information it is passed along with a generated timestamp (via date.today()). The select() simply returns the guestentries attribute.

model_pylist.py

from datetime import date
from datetime import datetime
from Model import Model

class model(Model):
    def __init__(self):
        self.guestentries = []

    def select(self):
        """
        Returns guestentries list of lists
        Each list in guestentries contains: name, email, date, message
        :return: List of lists
        """
        return self.guestentries

    def insert(self, name, email, message):
        """
        Appends a new list of values representing new message into guestentries
        :param name: String
        :param email: String
        :param message: String
        :return: True
        """
        params = [name, email, datetime.now(), message]
        self.guestentries.append(params)
        return True

The controller code is in app.py. The code instantiates the application via the Flask package and the model backend via the code shown in the prior step. It then registers two routes for the application. The first route is the default page for viewing the guestbook. This route creates a list of dictionaries where each dictionary is generated from one of the rows in the entries returned from model.select(). This list is then passed to Flask's render_template which will take the Jinja HTML template specified (index.html) and use the data in entries to render the final HTML returned to the client.

app.py

from flask import Flask, redirect, request, url_for, render_template
from model_pylist import model

app = Flask(__name__)       # our Flask app
model = model()

@app.route('/')
def index():
    entries = [dict(name=row[0], email=row[1], signed_on=row[2], message=row[3] ) for row in model.select()]
    return render_template('index.html', entries=entries)

The second route is to handle form submissions via HTTP POST requests. After pulling the form parameters out of the request via Flask, model.insert() is called to add them to the backend database (e.g. the Python list). The application then redirects the client back to the first route. Finally, the last part of the application runs the application if it is being called using the Python on port 8000 (default if not set is 5000).

app.py

@app.route('/sign', methods=['POST'])
def sign():
    """
    Accepts POST requests, and processes the form;
    Redirect to index when completed.
    """
    model.insert(request.form['name'], request.form['email'], request.form['message'])
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

The view consists of two Jinja2 templates. The first, templates/layout.html, serves as a "base" template for all derived templates. This standardizes the layout of all pages. As the template shows, all pages the title "My Visitors" and the same style sheet, which will be filled in by Jinja2 as the generated URL for the stylesheet in static/style.css. The template contains a single page that specifies a block called content. This block will be defined in a derived template.

templates/layout.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>My Visitors</title>
    <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
  </head>
  <body class="page">
    {% block content %}{% endblock %}
  </body>
</html>

The second Jinja2 template templates/index.html is derived from the base above. It contains the form for submitting a new entry via the sign route. It also outputs each entry it has been given. Recall that it was given a list of dictionaries. The for loop goes through each dictionary in the list, outputting the name, e-mail, date, and message.

templates/index.html

{% extends "layout.html" %}
{% block content %}
  <h2>Guestbook</h2>
  <form action="{{ url_for('sign') }}" method="post" class="box">
    <label><span>Name:</span><input type="text" name="name" required /></label>
    <label><span>Email:</span><input type="text" name="email" /></label>
    <label><span>Message:</span><textarea name="message" required></textarea></label>
    <label><span></span><input type="submit" value="Sign" /></label>
  </form>

  <h2>Entries</h2>
  {% for entry in entries|reverse %}
    <section class="entry box">
      <pre>{{ entry.message }}</pre>
      <header>
        {{ entry.name }} {% if entry.email %}&lt;{{ entry.email }}&gt;{% endif %}
        <em>signed on {{ entry.signed_on.strftime("%d.%m.%Y at %H:%M") }}</em>
      </header>
    </section>
  {% endfor %}
{% endblock %}

Within the directory containing the application, create a Python 3 virtual environment. This allows us to install custom Python packages specifically for our application without modifying the system's version of Python. After creating the environment, activate it. Note that you must always activate the environment whenever you wish to run the application with its installed packages.

python3 -m venv env
source env/bin/activate

Or on windows with PowerShell

python -m venv env
.\env\Scripts\Activate.ps1

By convention with Python applications, a list of packages to install for an application is given in a file called requirements.txt. Viewing this file shows us that the application uses flask for handling web requests. To install the packages, we'll run pip and point it to this file.

pip install -r requirements.txt

Finally, we'll run the application

python app.py

You should see the following:

This is Python's development web server. It's meant for testing. As the UI shows, the server is running on port 5000 and is listening to connections on all interfaces (e.g. 0.0.0.0). Launch a browser on your local machine or inside your VM and visit the site running via http://localhost:5000

Then, view the requests that show up in the terminal running the server. Within the terminal, type "Ctrl+c" to stop it.