Robotics Summative

Write-Up

For my summative write-up for the year 2020, I'll be going over the material in The Flask Mega-Tutorial (Chapters 1-12). To follow along and understand what's happening, some previous OOP (Object-Oriented Programming) knowledge is needed, as well as, what I'd classify as an intermediate/expert understanding of the Python and HTML5 programming languages. Although, the 'expert' level things can be picked up on in the later chapters, considering the fact that I personally came into learning this material as about an intermediate at both languages. For things unique to building a dynamic website, you're hopefully going to find everything you need right here in this write-up.

Side note: pictures aren't always fully shown on small windows or mobile screens, but everything else all works cross-platform.

Below are clickable links for you to jump straight to whichever chapter you're on in reading this write-up:




Chapter 1: Hello, World!

Installing Python

Firstly, you need to ensure that Python is installed onto your machine. This can be done by visiting the python.org website. Make sure to have it be at least Python3.6 or later, since older versions aren't supported by Flask. Also, make sure that you select "add to PATH" upon installation. By installing Python, you also get pip, which will allow you to install packages/libraries directly from whatever terminal window you're using. You can also use your terminal window to check if python is installed by typing in "python3", or "python" if that doesn't work. This'll display your current version and enter you into the Python Interpreter, which will be used later for various python commands, so remember how to access it.

Installing Flask

Once Python is installed, along with pip, you can then use the command below to install packages (like Flask).

If you're having issues with running this command later on, check to see if you are on the administrator's account, since it won't work on any other user's account.

In order to take care of issues such as having to maintain different versions of packages for different applications, Python uses the concept of virtual environments. It's essentially a copy of the python interpreter. If you install a package into this virtual environment, it'll remain local and not affect the rest of your system. Also, another huge benefit to having a virtual environment setup in your folder ("microblog"), is the portability of your project to other users/computers. Lets say, if you wanted to put all this code inside of a GitHub repository, like I did when first making this website with Hassan, you'll be able to clone the repository, along with the venv. This means that you won't have to go through the process of installing every single package and such on every single device, but instead, they're ready to go inside of the venv folder.

Yet another benefit to virtual environments, is that they are actually owned by the user that created them. Which means, that if you faced the issue earlier of not being the administrator on your computer, you can run commands in here freely.

The first step to this is making a directory to have this virtual environment in.

Then, you need to enter into the directory you've just made.

Now, you can make a new virtual environment, that'll be named "venv". The first word "venv" is for the Python virtual environment package, while the second "venv" is for your virtual environment's name, which you can change if you'd like.

Note that in some operating systems you may need to use "python" instead of "python3" in the command above, since some installations use "python" for Python2.x releases and "python3" for the 3.x releases, while others simply use "python" for the 3.x releases.

Once you have your virtual environment created, you need to tell your system that you wish to use it. To activate your virtual environment you need to type this command on a UNIX (Mac or Linux) system:

If you are using the Microsoft Windows command prompt window (like I am), the activation command is slightly different:

Once in your virtual environment, you can then use the "pip install (package-name)" command from earlier to install flask.

"Hello, World" Flask Application

The tutorial I followed for this website made various Python and HTML5 files/folders by using the terminal and making directories instead of just doing it the easy way. I found that a much simpler method of doing this was by just doing it all in your IDE (Integrated Development Environment), like Visual Studio Code. I'm assuming that anyone reading this uses this IDE, considering it's the one we use at robotics and is among the world's most popular IDEs. If you do choose to not use Visual Studio Code, just know that most of what I'm saying will apply to any other IDE, or at least shouldn't be too difficult to figure out yourself. If you want to follow along either: install an IDE such as VSC (Visual Studio Code Installation) or figure out how to make folders and files within the terminal.

So, let's get into setting up our first folder and Python file inside of it. Firstly, you want to open a new window of Visual Studio Code, click "File" -> "Open Folder"

Then, navigate through to your folder called "microblog". If you aren't sure where this folder is, you can do a search or look at the file path from when you were doing commands in the terminal, as shown below.

Next, you just want to click "Select Folder". Once you have the folder open, you'll notice how the "venv" folder has already been made inside of your "microblog" folder. It contains all the things you've installed into your virtual environment thus far. Now, you want to make a new folder at the same 'level' as this folder. You do this by clicking on the button shown below to make a new folder directly inside the "microblog" one - make sure you haven't selected the "venv" folder and are just in the "microblog" folder. You're going to name this new folder "app".

Inside of the new app folder, you're going to make a new file by clicking on this button, and name it "__init__.py". This'll let Visual Studio Code know about which kind of file it is by its file extension. This is ".py" in our case, which is short for Python. "__init__" signifies it as being the initialization file.

Now, we finally get to the fun part of making the website - coding it! Feel free to copy the code exactly to follow along. Although, I would highly suggest typing it out yourself to get a feel for actually doing so, rather than copy-pasting it.

The Python script above instantiates the application object from the Flask class. The __name__ variable used as a parameter to the Flask class is a predefined variable in Python, which is set to the name of the module in which it's used. Later on, Flask will use the location of this module passed here as a starting point when it needs to load associated resources such as template files (HTML5 front-end files). We then import "routes" from the folder we made earlier called "app". In case you were curious, the reason as to why we're importing routes at the bottom, instead of at the top like usual, is because routes references the app variable created here in this file. You don't need to worry about this too much for now, but just know that this is a little work-around for a common issue with Flask applications, being that they mutually reference each other sometimes and can cause an error by doing so. However, as of now, the routes file has not yet been created. So, let's do that!

Just like how you did it earlier, you're going to be making yet another python file inside of the "app" folder, name it "routes.py". Once you've done this, type this code into that file.

This file will be used to make various 'routes' to the URLs for the application, or its different 'pages'/'views'. The routes are the different URLs that the application implements. With the Flask web framework, handlers for the application routes are written as Python functions, called view functions. These functions are mapped (like a pointer) to one or more route URLs so that Flask knows what to execute under that URL.

This view function is very simple, all it does when a user accesses this URL (either '/' or '/index') is return "Hello, World!" The two @app.route lines above the function are what's known as decorators. They're not part of the flask framework, rather they're built into Python, meaning you might see them elsewhere in other projects of yours later on. They essentially act like modifiers for the function under them. This decorator take the URL as an argument inside of the brackets and associates it with the function. This means that when a user accesses either of the two URLs, the function below them will be executed. Most of the time, though, these functions only require one of these decorators in order to get called. It's just that whether the user is on the default (blank or '/') URL, or on the '/index' URL, we want it to run that same function.

To finally get the application up and running on your browser, there is one more Python script you need to make. It should be made a top 'level' of all your files inside of microblog. Meaning, it is on the same 'level' as "app" or "venv". You do this by clicking on the new file button after having clicked on the microblog folder so that you make a file directly inside of this folder.

This one line of code just imports the application instance from the app package. I know it can be a little confusing with having them as the same name, but to re-clarify: the first "app" is the variable in which we instantiated the app class object inside of, and the second "app" is the package class it's getting instantiated from in the app.py file. Feel free to change the variable name or importing the app package as a different name by doing the following: "import app as (new name for it)" as opposed to simply doing "import app" at the top of the "__init__.py" file.

The last step before running your application on a localhost is setting up the FLASK_APP environment variable. On a windows machine, you need to do this following command:

On other machines, you need to type "export" instead of "set" like so:

Get used to running this command in your virtual environment. You're going to have to do it every single time you re-launch your terminal to run this application. Also, if you're running multiple flask applications, you're going to have to make sure you're in the right flask_app environment. This has caused a lot of frustration in the past for me, when I didn't realize I had actually just been in the wrong flask_app environment. Save yourself the headache and always remember to do this when opening up the terminal for running your flask application.

Finally, the last thing left to do is run your flask application.

To access the application that's just been run on your computer, you can type "localhost:5000" into your browser's address bar. If you remember from earlier, though, we set up 2 different URL decorators to access this same page. This was the first one ('/'), alternatively, you can also type "localhost:5000/index". However, these are the only two URLs you can type in at the moment. In the terminal, once you're done, you can press Ctrl-C to stop the application from running.

Before we leave off this chapter, there's one more thing to mention. If you'll find it tedious to reset the flask_app environment variable every single time you open up the terminal window, you can setup a file at the top 'level' of your "microblog" folder that'll be called ".flaskenv".

Then, after doing this you need to type in this command into the terminal:

Now, to run your flask application again in the future, all you need to do is go into the terminal and follow these steps:

  1. "cd (directory)" into the right directory, which should be "microblog" at the end of the file path you're in
  2. activate your virtual environment
  3. if you didn't make the .flaskenv file and haven't automatically set your flask environment variable, do so by typing in the proper command: "set"/"export" FLASK_APP=microblog.py
  4. type "flask run" into your terminal
  5. visit the "localhost:5000" address in your browser
  6. press Ctrl-C to stop deployment



Chapter 2: Templates

Introduction To Templates

Since, we don't yet have users on our webpage, we don't have to worry about that yet, instead, we can make mock ones. We're going to be implementing these as Python dictionaries, linking a user's username to their username. In our case, we'll name our first mock user "Bogar". Having mock objects or components to your website is a really useful thing in web development. It allows you to concentrate on one part of the application without having to worry about the other parts of the system that don't exist yet, while still being able to test it out like it were implemented already.

Right now, we want to create our homepage that'll display a custom message based on our user's username being "Bogar". The first step to that is to set a mock username to the variable "user".

Now, though, we need to return something from this view function and display it on the website. We'll do this by having an HTML5 file. To make this file, we first need to make a new folder called "templates" inside the "app" folder. Then, inside of the new "templates" folder, make a new file called "index.html". This is the standard name among various HTML applications to indicate the main page of a website.

Now, we need to give it a title to have displayed in the tab bar and our message inside of the body.

This is a pretty basic HTML page, other than the {{...}} symbols. These represent placeholders for dynamic content, like variables.

Now, we need to head back to the routes.py file and return something in the index view function. For this, we'll be importing a function from flask called "render_template". This function takes a template filename ('index.html') and a variable list of template arguments in our placeholders and returns the template, now with actual values instead of placeholders for those values.

You can now, of course, try this in your browser once again, to see how the content got modified.

User Posts

Once again, we're going to make use of mock users and also some new mock posts to display users and their posts on the home page.

I know that this can seem pretty complex, but I'll try explaining these different data types and colour-coding parts of it in the image. To represent user posts we'll be using a list. Inside this list, there'll be two dictionaries with "author" and "body" fields. The first post dictionary's first item's "author" field has a dictionary as its value, and in this dictionary, lies the key ('username') along with the value ('Daniel').

Now, we have to update the index template. Since, the list of posts can have any number of elements, it's up to the view function to decide how many posts are going to be presented in the page. The template cannot make any assumptions about how many posts there are, so it needs to be prepared to render as many posts as the view sends. We can do this by making a simple for loop that iterates through the posts and, for each post, has the post author's username and its post body to put in a paragraph tag. The little conditional I put in the head tag is pretty simple to understand. It just evaluates whether there's already a title for this page, and either uses it in the title before "- Microblog" or generates the generic title of "Welcome to Microblog".

Template Inheritance

Just like this website you're currently on, many have navigation bars at the top of their pages, with a few links. The links on this website are: "HOME", "WRITE-UP", and "ABOUT RAVENSCOUT". This isn't very hard to do normally by just copy-pasting the same links into every single file, but there's a simpler way to do this in HTML5. For good practice, we want to avoid repetition at all costs in programming if it's more efficient.

Jinga2, which is included in Flask, has a template inheritance feature that helps us with avoiding repetition in our HTML files. It essentially allowed you to make one 'base' file, which we'll call "base.html" that'll have all parts of the page layout that are common to all templates, from which other templates are derived from.

Let's go ahead and make this file in the templates folder and call it "base.html". It'll include a simple navigation bar and the title conditional logic implemented earlier into the index.html file.

In this main template, the block control statement (anything inside of {...}) is there to define the place where the derived templates can insert themselves into this one. Blocks are given a unique name, which derived templates can reference when they provide their proper content. Now, we can adapt the index.html file to now inherit from this base template we've just made.

The base.html template file will now be taking care of all the general page structure parts, so all that's required in the index.html file is its proper content for that page. The "extends" control statement establishes the inheritance link between the two template files. In more complicated terms, this means that Jinja2 know that when it is asked to render index.html it needs to embed it inside base.html. Two two templates have matching block statements named "content", and this is how Jinja2 knows how to combine the two templates into one. Now, for any additional pages, this base.html template has you covered and will just need to be extended once again, in the same manner. This is how we can achieve the same look and feel of two web sub-pages without the need for duplication.




Chapter 3: Web Forms

Introduction To Flask-WTF

We are now going to be addressing our little issue of only having mock users. This will be done through the use of web forms by accepting input to store actual users. Web forms are key in most web applications, being one of the most basic building blocks to making a great web application. We'll be using forms to allow users to submit posts, and also for logging in to the application.

This is where the Flask-WTF extension will come in handy. There will also be many more flask extensions to come in the future. Once again, you can install this into your virtual environment by using the command seen below, like you've done previously.

Configuration

The application we've made so far is very simple, and for that reason we don't need to worry about its configuration. However, in many cases with web apps, you might find yourself wanting to configure some variables that'll get passed to the Flask web framework to personalize your website.

There's several formats for the application to specify configuration options. The format we're going to be using is to store configuration variables in a class. This file named "config.py" will be made directly inside the main "microblog" folder, on the same level as "microblog.py"

If we eventually get to need more than one configuration set, then there is an option to make subclasses of this configuration class.

The SECRET_KEY configuration variable is used as a value for a cryptographic key. Flask uses this to prevent a Cross-Site Request Forgery attack or CSRF. The value of this secret key is set as an expression with two terms, joined by the or operator. The first term looks for the value of an environment variable, also called SECRET_KEY. The second term, is just a hardcoded string. This is a pttern that you'll see repeated often for config vaiables.

The idea's that a value sourced from an environment variable is preferred, but if the environment doesn't define the variable, then the hardcoded string is used instead. When you're developing this web application, the security requirements are low, so you can just ignore this and let the hardcoded string be used. However, when it's deployed onto a production server, you should set a unique and difficult to guess value as the environment variable.

Now that the config file's setup, we need to let Flask know about it. So, we need to go back to the Flask application instance in the __init__.py file. To do this, we need to call the method known as "from_object" and have "Config" as a parameter.

In case you're confused about why it seems as though I'm importing something from itself, seeing as "flask" and "Flask" or "config" and "Config" seem like the same things, just know that I'm referring to the uppercase ones as their respective classes, whereas "flask" and "config" are modules (like the file "config.py").

User Login Form

The Flask-WTF extension uses Python classes to represent web forms. A form class just defines the fields of the form as class variables. To implement this new feature, we're going to be creating a new file called "forms.py" inside of the "app" folder. This user login form will include: username, password, a "remember" me check box, and a submit button.

The four classes that represent the field types that we're using for this form are imported directly from the WTForms package, since the Flask-WTF extension doesn't provide customized versions. For each field, an object is created as a class variable in the LoginForm class. Each field's given a little description or label as a first argument.

The optional validators argument that you see in some of the fields is used to attach validation behaviors to fields. The DataRequired validator simply checks that the field isn't submitted emtpy. There's many more validators available, some of which we're going to use in future forms.

Form Templates

Now, that we have a functional back-end for the user login form, our next step is to add the form to an HTML template so that it can be rendered on a web page. For this, we're going to make a new HTML5 file inside the "templates" folder called "login.html".

For this template, we're using the "base.html" template once again. This templates expects a form object isntantiates from the LoginForm class to be given as an argument, which you can see referenced as "form". This argument will be sent by the login view function, which isn't written yet in the "routes.py" file.

The HTML "form" element in <> is used as a container for the web form content. The "action" attribute of the form is used to tell the browser the URL that should be used when submitting the information the user entered in the fomr. When the action is set to an empty string, the form is submitted to the URL that is currently in the address bar, which is the URL that rendered this form on the page. The "method" attribute specifies the HTTP request method that should be used when submitting the form to the server. The default is to send it with a GET request, but in almost all cases, using a POST request makes for a better user experience because requests of this type can submit the form data in the body of the request, while GET requests add the form fields to the URL, cluttering the browser address bar.

The "form.hidden_tag()" template argument generates a hidden field that includes a token that's used to protect the form against CSRF attacks. All you need to do to have the form protected is include this hidden field and have the SECRET_KEY variable defined in the Flask configuration.

The form object calls the fields as methods, and they're already programmed to know how to render themselves as HTML code. {{ form.field_name.label }} is used to include the field label, and {{ form.field_name() }} is used to get the field. For fields that require additional HTML attributes, those can be passed as arguments. The username and password fields in this template take the "size" as an argument that'll be added to the "input" HTML element as an attribute. This is how you can also attach CSS classes or IDs to form fields.

Form Views

Finally, we have to code a new view function in the application that renders the template made. This is done in the "routes.py" file. We're going to make another view function right below the previous one and import the LoginForm class from the "forms" file inside of the "app" folder.

Here, we've instantiated an object of the LoginForm class form the "forms.py" file, and sent it to the template. The "form=form" syntax means that it's passing the "form" object created in the line above to the template named "form". Now, we just need to add another hyperlink on the top of every file to the new login page. All we need to do for this is to add it to the base.html file, for all other template to inherit form, making it so all templates have this new hyperlink.

Receiving Form Data

As of right now, if you try hitting the submit button, the browser's going to display a "Method Not Allowed" error. This is because the inside the login view function, the submit button has no logic in place yet to process data submitted by the user yet. So, we're now going to be accepting and validating the data submitted by the user in this updated view function. Also, there are new modules to import at the top of the file.

The first new thing in this version, though, is the "methods" argument in the route decorator. This tells Flask that this view function accepts both GET and POST requests, overriding the default, which is to accept only GET requests. The HTTP states that GET requests are those that return information to the client (the web browser). All the requests in the application so far are of this type. POST requests are typically used when the browser submits form data to the server. Now that we've enabled "POST" requests, the "Method Not Allowed" error should be all fixed.

The "flask()" function, imported from Flask, is a useful way to show a message to the user. This is only a temporary solution for our application, though, since we don't have a system setup to actually log users in yet.

Although, the "flash" messages don't automatically appear in the browser, so we need to set up a way for these messages to display in the "base.html" file itself.

Here, similarly to how you'd do it in Python, we use a "with" construct to assign the result of calling the "get_flashed_messages" function to a messages variable. The conditional after this checks if "messages" has content, and if it does, displays them in an unordered list.

Improving Field Validation

We're now going to tell the user which fields they're incorrectly supplied data into in the login form, using style in the "login.html" page.

Now, if you try submitting the form with an empty username or password, this is what'll occur on your browser tab:

Generating Links

Before leaving off this chapter, I'd like to talk about making links better in your application. Let's say, for instance, you decide to reorganize your link names, or just don't want to bother with having to remember them all by name. Well, you can simply use the "url_for()" method from Flask that will provide the link for certain pages. You'll see this in the following two files below:

  1. base.html

  2. routes.py




Chapter 4: Database

Flask Databases

Flask doesn't actually support databases natively. This means you'll have the freedom to choose the database that best fits. There are two groups of databases, those that follow the relational model, and those that don't. Ones that are relational implement the popular relational query language SQL, and those that don't are called NoSQL. Relational databases are a better match for application that have structured data such as lists of users, blog posts, etc. while NoSQL databases are better for applications with less defined structure. We're going with a relational one.

An extenstion we'll need to install is called "Flask-SQLAlchemy", which provides a Flask-friendly wrapper to the popular SQLAlchemy package, which is an Object Relational Mapper or ORM. ORMs allow applications to manage a database using high-level entities such as: class, objects, and methods, instead of tables and SQL. ORMs translate high-level operations into database commands.

To install this package into your virtual environment, execute this command in your terminal window:

Database Migrations

Database migrations address the issue of trying to make updates to an existing database as the application needs to change. This is often difficult, seeing as relational databases are centered around structured data, so when the structure itself changes, the data that's already in the database needs to be migrate to the modified structure.

So, the next extension we'll need to help with that is Flask-Migrate, created by Miguel Grinberg, the guy who wrote the blog I'm basing this write-up on. To install it, type in this command:

Configuring Flask-SQLAlchemy

During development (as opposed to production), we're going to use a SQLite database. These are convenient for developing small applications. We now have two new configuration items to add to the config file:

This new extension takes the location of the application's database from the "SQLALCHEMY_DATABASE_URI" config variable. Just like we saw in Chapter 3, it's a good practice to set configuration from environment variables, and provide a fallback value as the second parameter in case the environment doesn't define the variable. We're actually configuring a database named app.db located at the top level of the application, which is stored in the basedir variable. This file will be automatically made for us later, once we create the database.

The "SQLALCHEMY_TRACK_MODIFICATIONS" configuration option is set to False to disable a feature of Flask-SQLAlchemy that's not needed.

The database is going to be represented in the application by the database instance. The database migration engine will also have an instance. These are objects that need to be created after the application, in the "__init__.py" file.

These are all the changes we've made to the init script:

  • Imported a couple more modules at the top of the file
  • We've added a db object that represents the database
  • We've added another object that represents the migration engine
  • Imported a new module called "models" at the bottom of the file, that will define the structure of the database

Database Models

The data that'll be stored in the database will be represented by a collection of classes, usually called database models.

Here I've made a model that represents users in a table. I did this using the WWW SQL Designer Tool. It includes the fields, field datatypes, and field sizes.

The id field is typical to all models, and is used as the primary key. Each user in the database will be assigned a unique id value, stored in this data field. Primary keys are, in most cases, automatically assigned by the database, so we just need to provide the field to house them in.

The other three fields for: username, email, and password_hash are defined as strings (or "VARCHAR" in database lingo), and their maximum lengths are specified so that the database can optimize its memory usage (which is important to do in computer science).

While the "username" and "email" fields are self-explanatory, the "password_hash" one has a little more to it. It is used for security reasons so that passwords get 'hashed' by the database they're stored in, as to not be vulnerable to an attack. Although, this'll be done in Chapter 5.

Now, let's go ahead and make a new file under the "app" folder and call it "models.py".

The "User" class created above inherits from "db.Model", which comes from Flask_SQLAlchemy and is its base class. This class defines several attributes. Fields are created as instances of the "db.Column" class, which takes the field type as an argument, plus other optional arguments, for example, allow us to indicate which fields are unique and indexes, which is important so that database searches are efficient.

The "__repr__" method tells Python how to print objects of this class, which is going to be useful for debugging.

Migration Repository Creation

The model class created defines the initial database structure for this application, but as the application continues to grow, there's going to be a need to change that structure, whether that's adding, modifying, or removing items. Alembic (the migration framework used by Flask-Migrate), maintains a migration repository, which is a directory in which it stores its migration scripts. Each time a change is made to the database structure, a migration script is added to the repository with the details of the change.

The "flask db" sub-command to Flask's commands, added by Flask-Migrate manages everything related to database migrations. So, lets' create the migration repository for microblog by running this command:

After running this command, you will find a new migrations directory, with a few files and a versions sub-directory inside.

First Database Migration

This first migration will include the users table that maps the "User" database model. How Alembic normally creates a database migration is automatically. To generate a migration automatically, Alembic compares the database structure as defined by the database models, against the actual database structure currently used in the database. It then populates the migration script with the changes necessary to make the database schema match the application models. In this case, since there is no previous database, the automatic migration will add the entire "User" model to the migration script. The "flask db migrate" sub-command generates these automatic migrations:

The -m "(...)" part is optional, but it adds a short descriptive text to whatever migration you're currently implementing. The output of the command gives you an idea of what Alembic included in the migration. The first two lines are informational and can usually be ignored. It then says that it found a user table and two indexes. Then it tells you where it wrote the migration script. The 976200887022 code is an automatically generated unique code I got for the migration.

This migration script has two functions "upgrade()" and "downgrade()" to apply or remove migrations. This command must be used after migrating a new script to the database:

Database Relationships

Relational databases are good at storing relations between data items, hence their name. Consider the case of a user writing a blog post. The user will have a record in the "users" table, and the post will have a record in the "posts" table. The most efficient way to record who wrote a given post is to link the two related records.

Once a link between a user and a post is established, the database can answer queries about this link, like which user wrote a certain blog post. This would actually be a pretty easy one, but one where you try finding the reverse would be complex. If you have a user, you may want to know all the posts that this user has written. Flask-SQLAlchemy will help with both types of queries.

Now, I've expanded the database diagram to show the link between a user's id field and a post's user_id field. Sounds confusing, but that's because it is. Later on, in Chapter 8, it'll get even harder when we introduce followers. However, this stuff about database relationships is very good to know, considering it's taught in university computer science classes. For right now, though, I hope you can follow, since I don't think it's too complex just yet.

The "posts" table will have the required "id", the "body" of the post and a "timestamp". In addition, though, is a "user_id" field, which links the post to its author. The way to link a blog post to the user that authored it is to add a reference to the user's "id", and that's exactly what the "user_id" field's for. The "user_id" field is called a foreign key. The database diagram above shows foreign keys as a link between the field and the "id" field of the table it refers to. This kind of relationship is known as a "one-to-many", because 'one' user writes 'many' posts.

Seeing as we now have another database table, we need to add a new corresponding class to the "models.py" file for the posts.

The new "Post" class will represent blog posts written by users. The "timestamp" field is going to be indexed, which is useful if you want to retrieve posts in chronological order. We're also added a "default" argument, and passed the "datetime.utcnow" function. When you pass a function as a default, SQLAlchemy will set the field to the value of calling that function. This is done by not including the (), to signal that we don't want the value from calling that function, rather the function itself to be passed. In general, we want to be working with UTC dates and times in a server application. This ensures that you're following the standard regardless of where the users are located. These timestamps will be converted to the user's local time when they're displayed.

The "user_id" field was initialized as a foreign key to "user.id", which means that it references an "id" value from the users table. In this reference the "user" part is the name of the database table for the model.

The "User" class now also has a new "posts" field, that is initialized with "db.relationship". This is not an actual database field, but a high-level view of the relationship between users and posts, and for that reason it isn't in the database diagram. For a "one-to-many" relationship, a "db.relationship" is normally defined on the "one" side, and is used as a convenient way to get access to the "many".

The "backref" argument defines the name of a field that will be added to the objects of the "many" class that points back at the "one" object. This will add a "post.author" expression that will return the user given a post. The "lazy" argument defines how the database query for the relationship will be issued, which will be discussed later.

With a new update to the application models, a new update to the database migration needs to be made.

Now, for the upgrade command to finish it off:

Testing Time

After suffering all this time trying to get the database setup, it's time to see how it all works. We could do this in the Python interpreter, but the problem with that is: we would have to import our libraries before testing inside the interpreter. A fix to this is something the "flask shell". It is an exact copy of the Python interpreter, only it imports things from the "microblog.py" app for you by making a function for it in that file.

Now, with these configured symbols to pre-import before heading into the shell, we don't have to worry about doing it ourselves. Also, with the shell, comes the application instance pre-imported, which means that anything in the "app.db" file for our database.

The first step is getting into the shell by running the flask command.

Now that we're in, it should look exactly like the Python intrepreter. Let's start testing by creating a new user.

Changes to a database are done in the context of a session, which can be accesses as "db.session". Multiple changes can be accumulated in a session and once all the changes have been registered you can issue a single "db.session.commit()" command. If at any time while working on a session there's an error, a call to "db.session.rollback()" will abort the sesison and remove any changes stored in said session.

With all that said, let's add another user.

Now that we have 2 users in our database, we can ask a query that returns all the users.

All database models have a "query" attribute that's the entry point to run database queries. The most basic query is that one that returns all elements of that class. Also, since we indexed the "username" field, the "id" fields were automatically set to 1 and 2 when they were added.

However, instead of doing a query to retrieve all users in a database, you can also locate one using the "id" of that user.

Now, let's add a mock blog post.

Since, we set a default value for the "timestamp" class attribute, we don't need to set it. As for the "user_id" field, we have a "db.relationship" setup between the "User" class and the post, because the class adds a "posts" attribute to users, and also an author attribute to posts. We went about assigning an turho to this post using the "author" virtual field instead of having to deal with user IDs, which we don't have setup yet.

Now, to end this chapter off, we need to clean the shell so that there's no existing users and posts in our database for chapters to come.




Chapter 5: User Logins

Password Hashing

Time to address the "password_hash" field of the user model, that's gone unused thus far. The purpose of this field is to contain a hash of the user password, which will be used to verify the password entered by the user during login. Although, password hashing is pretty complicated and advanced, there's several libraries to help us with that.

Werkzeug, is a package we're going to implement to set and check a password. This'll be done in the "models.py" file under two functions inside of the User class.

These two methods are pretty self explanatory by reading the function names inside of them. Just don't forget to import the two packages from werkzeyg.security.

Introduction To Flask-Login

This popular Flask extension manages the user's logged-in state. This also provides the "remember me" functionality that allows users to remain logged in even after closing the browser window. The first step to all of this though, is to - you guessed it, execute the install command in the terminal for the extension.

As with other extensions, Flask-Login needs to be created and initialized right after the application instance in "__init__.py". This is how:

User Model Setup

The Flask-Login extension works in tandam with the application's user model, and expects certain properties and methods to be implemented in it. The four required items are listed below:

  • "is_authenticated": a property that's True if the user has valid credentials or False otherwise
  • "is_activate": a property that's True if the user's account is activate or False otherwise
  • "is_anonymous": a property that's False for regular users, and True for a sepcial, anonymous user
  • "get_id": a method that returns a unique identifier for the user as a string

These four items are simple to implement, but since since they're fairly generic, Flask-Login provides a class called "UserMixin". It's first imported from flask-login, then added as a parameter to the "User" class.

User Loader Function

The way Flask-Login keeps track of the logged in user is by storing its unique identifier in Flask's "user" session, a storage space assigned to each user who connects to the application. Since, Flask-Login knows nothing about databases, we need to configure a user loader function for it to load a user given the ID. This function can be added in the "models.py" file.

The "@login.user_loader" decorator on top of the function is what registers the user loader with Flask-Login. The "id" that Flask-Login passes to the function as an argument is going to be a string, so databases that user numeric IDs need to convert the string to integer as you can see above.

Logging Users In

We are finally going to change the fake login that just issued a "flask()" message in the login view function.

The first two lines are a solution to a weird situation. This would be if a user tried clicking on the login button while already being logged into your application. So, this condition uses the "current_user" variable from Flask-Login to check if they're already "authenticated" and redirects them to the "index" page if True.

In place of the "flash()" call that we used earlier, now we can actually log the user in. The first step to load the user from the database. The username came with the form submission, so we can query the database with that to find the user. For this purpose we're using the "filter_by()" method of the SQLAlchemy query object. The result of this method is a query that only includes the objects that have the matching username. Since we know there is only going to be one or zero results, the query is completed by calling the "first()" command, which'll return the user object if it exists, or None if it doesn't.

If the username match was successful, we can then check if the password that also came with the form is valid. This is done by calling the "check_password" method defined above. This will take the password hash stored with the user and determine if the password entered in the form matches the hash or not. So now we have two possible error conditions:

  1. The username can be invalid
  2. The password can be incorrect for the user

Either way, a message is flashed, and the user gets redirected back to the login prompt so that the user can try logging in again.

If both fields are correct, then we can call the "login_user()" function, which comes from Flask-Login. Finally, the user's reidrected to the "index" page of the web application.

Logging Users Out

Flask has the "logout_user()" function already built in for us. All we have to do is add another view function to the "routes.py" file and another hyperlink in the "base.html" file.

Now, all we have to do is add some pretty simple logic to our hyperlinks in the "base.html" file. This is so that, if the user is already logged in there'll be a "logout" button, but if they aren't, then there'll be a "login" button instead. They aren't if "is_anonymous" is True.

Requiring Users To Login

Flask-Login provides a very useful feature that forces users to login before they can view certain pages of the application. Flask-Login will automatically redirect a user that's trying to access restricted content to the user login form, and only redirect back to the page the user was originally trying to access once they get logged in.

For this, we just need to let Flask-Login know about which view function that handles logins. This'll be added in "__init__.py":

The 'login' valule above is the function (or endpoint) name for the login view. In other words, the name you'd use in a "url_for()" call to get the URL.

The way Flask-Login protects a view function against anonymous users is with a decorator called "@login_required". When you add this decorator to a view function below the "@app.route" decorators from Flask, the function becomes protected and will not allow access to users that aren't authenticated. Here's how the decorator can be applied to the index view function of the application in the "routes.py" file:

What remains is to implement the redirect back from the successful login to the page the user wanted to access in the first place. What we want to have happen is if the user navigates to the "index" page, for example, the "@login_required" decorator will intercept the request and respond with a redirect to the "login" page, but it will add a query string argument to this URL, making the complete redirect URL /login?next=/index. The "next" query string argument is set to the original URL, so the application can use that to redirect back after login.

Right after the user's logged in by calling Flask-Login's "login_user()" function, the value of the "next" query string argument is obtained. Flask provides a "request" variable that contains all the information that the client sent with the requrest. In particular, the "request.args" attribute exposes the contents of the query string in a dictionary format. There's actually three possible cases that need to be considered to determine where to redirect after a successful login:

  1. If the login URL, doesn't have a "next" argument, then the user is redirected to the index page
  2. If the login URL includes a "next" argument that's set to a relative path (or in other words, a URL without the domain portion), then the user is directed to that URL
  3. If the login URL includes a "next" argument that's set to a full URL that includes a domain name, then the user's redirected to the index page

The first two cases are self-explanatory. The third case is in place to make the application more secure. An attacker could hypothetically insert a URL to a malicious site in the "next" argument, so the application only redirects when the URL is relative, which ensures that the redirect stays within the same site as the application. To determine if the URL is relative of absolute, we parse it with Werkzeug's "url_parse()" function and then check if the "netloc" component is set or not.

Showing Logged-In Users

Instead of using fake users on the homepage, we can now use Flask-Login's "current_user" in the template.

Also, we can remove the "user" template argument in the view function.

User Registration

Now, so we can see our work in action, we need a way to register some users on our website! To do this, we need to make a new web form class in "forms.py".

There is now a second validators to the "email" field after "DataRequired" called "Email". This is another stock validator that comes with WTForms that'll ensure that what the user types in this field matches the structure of an email address.

Since this is a registration form, it is customary to ask the user to tyep the password twice to reduce the risk of a typo. For that reason we have two password fields, that'll then be checked by another stock validator called "EqualTo" to ensure they're the same - or equal to each other.

We've also added two methods to this class called "validate_username" and "validate_email()". In this case, we want to make sure the username and email address entered by the user aren't already in the database. If they are, there'll be a "ValidationError" raised. The message included as the argument in the exception will be the message that'll be displayed next to the field for the user to see.

To display this form on a web page, we need another HTML template, which is going to be called "register.html" and will go inside the "templates" folder.

Now, the login template needs a link that sends new users to the registration form, right below the form, in the same file.

Now, to add another view function for registration.

Pretty self-explanatory, but I'll explain just in case. We first make sure the user that goes to this route isn't logged in. The form is handled in the same way for the one for loggin in. The logic that's done inside the "if validate_on_submit()" conditional creates a new user with the username, email and password provided, writes it to the database, and then redirects to the login prompt so that the user can login.

You can now go ahead and test it out if you want, by creating an account on this application, logging in, and out. Make sure to try out all the validation features we've added to better understand how they work.




Chapter 6: Profile Page and Avatars

User Profile Page

Let's start of with a new view function that maps to the "/user/(username)" URL in the "routes.py" file.

In this @app.route decorator used to declare this view function, we have a dynamic component in it, which is indicated as the /username URL component that's surrounded by < and>. When a route has a dynamic component, Flask will accept any text in that portion of the URL, and will invoke the view function with the actual text as an argument. For example, if the client browser requests URL /user/susan, the view function is going to be called with the argument "username" set to 'susan'. Since, we only want logged in users to access this view function, we've added the @login_required decorator from Flask-Login

We simply do the same as we did earlier, trying to load the user from out database using a query by the username, and if it's not found as "first()", then a 404 error will occur in the browser. Then, if successful, we initialize a fake list of posts for this user. Although these need to be rendered in a new "user.html" template file.

Finally, we need to add another link to our "base.html" file's links div.

Here, we're just using Flask-Login's "curernt_user" to generate the correct URL inside of the "url_for()" function's parameters. This is to point to the right view function in "routes.py" for the appropriate user.

Feel free to test out the application now. This is what you should see once you've registered a user, logged in, and clicked on the profile hyperlink:

Avatars

So far the profile pages we've made for users are pretty boring. Well, it's time to change them up with some avatars. To do this, we'll be using the Gravatar service to provide images for all users, instead of having a possibly large collection of uploaded images in the server.

The Gravatar service is very simple to use. To request an image for a given user, a URL iwth the format "https://www.gravatar.com/avatar/(hash)", where "hash" is the MD5 hash of the user's email address. More on this later in the code.

By default the image size returned is 80x80 pixels, but a different size can be requested by adding an "s" argument to the URL's query string in the URL. Another argument that can be passed to Gravatar as a query string is "d", which determines that image Gravatar provides for users that don't have an avatar registered with the service.

Note that if you don't see avatars in your browser, it may be due to an extension that you've installed in your browser.

Now, we're just going to add an "avatar" method to the "user" class with the "self" and "size" parameters.

This method returns the URL of the user's avatar image, scaled to the requested size in pixels. If they don't have one registered, it'll generate an "identicon" image, an image that'll look different for every user based on their email hash.

To generate the MD5 email hash, the email first gets converted to lowercase by calling Python's "lower()" function. Then, because the MD5 support in Python works on bytes and not on strings, we encode the string as bytes before passing it on to the hash function.

Next up, is inserting the avatar images in the user profile template.

Now, we have a big avatar next to the user name on the top of the page, but we could also use some little avatars next to their posts on the bottom. For this, we just need to make a small change to the template.

Now, if we test this out on our browser, it should look like this:

If you're interested in changing the avatars displayed on your website, just follow this link and sign in with the same email used for your registered user on the web application. Then just upload an image like I have here:

Jinja2 Sub-Templates

We now want for all posts written by the user to be displayed on the "index" page. To do this, we're going to make a sub-template that just renders one post, and then we're oging to reference it from both the "user.html" and "index.html" templates. To begin, we can create the sub-template, with just the HTML for a single post. We're going to be naming this template "_post" in the "templates" folder. The _ prefix to the name is just a convention used to describe sub-templates.

We're now going to include this sub-template into the "user.html" template using Jinja2's "include" statement.

More Profile Additions

We're going to create "about me" and "last_seen" class attributes for the "user" in the "models.py" file.

Now that we've changed the database structure, we need to generate a database migration, and then apply this change to the database.

The next step, is to add these two new fields to the user profile template in its "user.html" file.

We're wrapping these two fields in Jinja2's conditionals, since we only want them to be visible if they're set. Which, they won't be if you fun the application now.

Recording The Last Visit Time

All we want to do for the "last_seen" field of the "User" class is write the current time on this field for a given user whenever that user sends a request to the server.

This new function in the "routes.py" file will execute a bit of generic logic ahead of a request to a view function.

The "@before_request" decorator from Flask executes right before the view function. This means that this code we've just typed up will automatically be inserted before any view function in the application.

The "current_user" is already invoked in the database from referencing it earlier, making it so that we don't need to run a "db.session.add()" command before the commit.

Now, if you go check your profile, you'll notice this new feature implemented, and if you navigate away from this page and come back, also notice that it constantly updates.

Profile Editor

Now that we've added an "about_me" field to the "User" class, we need to give users a form in which they can enter some information about themselves. The form is going to let users change their username, and also write something about themselves. So, let's now write a new form class in the "forms.py" file.

This new "TextAreafield" field type is used to provide a multi-line box in which the user can enter text into. To validate this form, we've also added the "Length" validator, which will make sure that the text entered is between 0 and 140 characters, which is the space we've allocated for the corresponding field in the database.

Now, for the new "edit_profile.html" file in the "templates" folder.

Finally, here's the view function that'll tie everything altogether:

There's a few differences between this form and the other two we've previously made. If "validate_on_submit()" returns True, we copy the data from the form into the user object and then write the objec to the database. However, when it returns False, it can be due to 2 different reasons:

  1. It can be because the browser just send a "GET" request, which we need to respond by providing an initial version of the form template
  2. It can also be when the browser sends a "POST" request with form data, but something in that data is invalid.

For this form, we need to treat these two cases separately. When the form is being requested for the first time with a "GET" request, we want to pre-populate the fields with the data that's stored in the database, so we need to do the reverse of what we did on the submission case and move the data stored in the user fields to the form, as this will ensure that those form fields have the current data sotred for the user. In the case of a validation eror, though, we don't want to write anything to the form fields, because those were already populated by WTForms. To distinguish between these two cases, we check "request.method".

To make this new page easily-accessible, we can add a link in their profile page.

We added this conditional beside it that that the link appears only when you're viewing your own profile, but not when you're viewing someone else's.




Chapter 7: Error Handling

Flask Error Handling

As it stands right now, we have a little bug in our code. It occurs when there's two or more users registered on the website, you click on the edit_profile button, and then you try changing the username of another user that's alread registered. What'll happen now, is you'll see this message pop up in your browser:

This error will show a stack trace of itself in your terminal session. As well as, point to the line it came from.

Debug Mode

Another thing you can see in the terminal that'll help with debugging errors is whether you have Debug mode set to "on" or "off". This can be changed by setting it to 1 (on) or 0 (off). This is how you do it on Microsoft Windows:

On any other system (Mac or Linux), here's how to do it:

Now, if you run the appplication, the output will be slightly different, being that Debugger will be activate and you'll get a "Debugger PIN".

Now, if we make the application crash once more, you get to see the interactive debugger in your browser, instead of terminal.

Custom Error Pages

Being that the two most common errors are HTTP errors 404 and 500, we're going to define custom pages for them, although it'll work for any type of error.

To declare a custom error handler, the "@erorrhandler" decorator is used. We're going to put our error handlers in a new "errors.py" module inside of the "app" folder.

These error functions work very similary to view functions. For these two errors, we're returning the contents of their respective templates.

Note that both functions return a second value after the template and its arguments, which is the error code number. This is because, for all the view functions that've been created so far, we didn't need to add a second return value because the default of 200 (the status code for a successful response) is what we've wanted.

To make sure any failed database sessions don't interfere with any database accesses triggered by the template, we issue a session rollback. This resets the session to a clean state.

Here's the new template file called "404.html" inside of the "templates" folder:

Here's the other one for "500.html":

To get these error handlers registered with Flask, we need to import the "error.py" module after the application instance is created in the "__init__.py" file.

Now, if you set "FLASK_DEBUG=0" again in your terminal session and then trigger the duplicate username bug one more time, you're going to see a more friendly error page.

Sending Error Emails

Constantly monitoring the terminal and see what errors occur that way is a hassle. It's for that reason that we would want a feature implemented to just notify us by email, or at least in a more convenient manner than through the terminal.

Another reason for this, is because there won't be any output in the terminal while your web application is desployed in a production server, unlike in a development server, like what we've been using.

So, we want to receive an email with the error right away, along with the stack trace of the error in the email body. The first step is to add the email server details to the configuration file like so:

You can set the admins' emails to whichever you'd like, and have multiple of them in this list.

The configuration variables for email include:

  1. The mail server, which, if not set in the environment, will be used as a sign that emailing errors needs to be disabled
  2. The mail port, that can also be given in an environment variable, but if not set, the standard port 25 is used
  3. A boolean flag to enable encrypted connections using TLS (Transport Layer Security) from server to server
  4. An optional username
  5. An optional password

Flask uses Python's "logging" package to write its logs, which already has the ability to send logs by email. All we need to do to get emails sent out on errors is to add a SMTPHandler instance to the Flask logger object, which is "app.logger".

As indicated by "app.debug" being True, we'll only have the email logger enabled when the application is running without debug mode (on a production server), and also when the email server exists in the configuration.

In essense, the code above creates a STMPHandler instance, sets its level so that it only reports errors and not warnings, informational or debugging messages, and finally attaches it to the "app.logger" object from Flask.

Testing This Feature

There's actually two methods to testing out this feature that we'll see right below.

  1. First approach:
  2. The easier one is to use the SMTP debugging server from Python. This is a fake email server that accepts emails, but instead of sending them, it prints them to the console. To run this server, open a second terminal session and run the following command on it:

    Leave this debugging SMTP server running and go back to your first terminal and set the "FLASK_DEBUG" variable to 0. Since, the application won't send emails in debug mode. Again, this is how you do it on windows:

    Remember that on Mac/Linux, you need to type "export" instead of "set".

    Now, set the MAIL_SERVER=localhost and MAIL_PORT=8025 in the virtual environment.

    Now, it's time to run the application and trigger the error once again, and see what arrives in your email inbox.

    Don't worry if you didn't get an email, though. Since, your Gmail account may prevent the application from sending emails through it unless you explicitly allow "less secure apps" access to your Gmail account. You should get an email saying that there's a "Google Security Alert", which leads you to information on how to allow this option.

  3. Second approach
  4. This second approach is a little bit harder, since you're configuring a real email server. Below is the configuration to use your Gmail account's email server.

    Remember to use "export" instead of "set" if you're not on Microsoft Windows, and that it's okay if you don't get an email after running the application again and triggering the error.

Logging To Files

Receiving errors via email is okay, but having a file to store errors via logging is even better. There's some failure conditions that don't end in a Python exception and aren't a major problem, per say, but may still be useful to save for debugging purposes. For this reason, we're also going to maintain a log file for the application.

To enable a file based log, another handler, called "RotatingFileHandler", needs to be attached to the application logger, in a similar way to the email handler (SMTPHandler). This'll be done in the "__init__.py" file.

We're going to be writing this into a file. Which, first means we have to create it. We've done this by simply using the "os" library in python (which deals with operating system things, like making directories and such) to make a new log file with the name "microblog.log" in the "logs" directory.

The "RotatingFileHandler" actually helps us deal with memory usage in this file. Seeing as it ensures that the log files don't grow too large when the application runs for a long time. In this case, we're limiting the size of the log file to 10KB, and we're keeping the last 10 log files as backup.

The "logging.Formatter" class provides custom formatting for the log messages. Since, these messages are going to a file, I want them to have as much information as possible. So, I'm using a format that includes:

  • The timestamp
  • The logging level
  • The message
  • The source file
  • The line number from where the log entry originated

To make the logging more useful, and not just displaying the very blatant errors in our program, we're lowered the logging level to the "INFO" category, both in the application logger and the file logger handler. In case you're unaware with the logging categories, here they are in order from least to most severe:

  1. DEBUG
  2. INFO
  3. WARNING
  4. ERROR
  5. CRITICAL

When this application runs on a production server, there'll be log entries from the server indicating each time it starts in a line in the log file. These will tell you when the server was restarted.

Fixing The Bug

Now, that we have our error handling system all figured out for the future, let's focus on fixing the error at hand.

Here in the "EditProfileForm" class inside of the "forms.py" file, we have to implement a username validation. This'll be much like the one in the "RegistrationForm", except that if the user leaves the original username untouched, then the validation should allow it, since that username is already assigned to that user form being logged in.

In the "validate_username" method, it takes the original usernamem as an argument, which was defined in the __init__ method above. This prevents from checking the database for duplicates if the username entered in the form is the same as the original username.

Now, we simply have to add the original username argument in the view function, where the "EditProfileForm" object is created.




Chapter 8: Followers

More Database Relationships

This chapter is going to really complicate what you learned in Chapter 4 about database relationships.

For every user, we want to maintain a list of "followed" and "follower" users for each user. Unfortunately, a relational database doesn't have a list type that we can use for these lists, all there is are tables with records and relationships between said records.

The database we've implemented already has a table that represents users, so what's left is to come up with the proper relationship type that can model the follower/followed link.

Alright, before going into what's next for the database, I'm just going to go over every basic database relationship type to stay organized.

One-To-Many

Well, we've already covered this one in Chapter 4, right here

Many-To-Many

This relationship is a bit more complex. For example, let's consider a database that has "students" and "teachers". We can say that a student has 'many' teachers, and a teacher has 'many' students. As Miguel Grinberg describes it, it's like two overlapped one-to-many relationships from both ends.

For this kind of relationship, we should be able to query the database and obtain the list of teachers that teach a given student, and the list of students in a teacher's class. This is actually difficult to represent in a relational database, as it can't be done by adding foreign keys to the existing tables.

A reason why a many-to-many relationship can't be defined using two tables alone is because we can't have more than one teacher_id column for the student's multiple teachers.

It's just not the way databases work, and I'll show you why.

This kind of relationship doesn't work in this manner, since we can't have multiple columns for the "teacher_id"s. The teacher_id column can only hold one value, so separating with commas like I did in that diagram also doesn't work. Finally, we can't add another row, since it would be duplication of the students.

All of these things would break the rules of normalization. It's for all these reasons that we have a joining/association/junction/bridging table.

Here is how the database would look for the students and teachers example with an auxiliary association table:

Instead of going straight into the teachers table, there's a foreign key with the teacher's id established inside of the association table, that's then used to connect with the students table. This is how it becomes similar to a one-to-many relationship. Now, the association table with its two foreign keys is able to efficiently answer all the queries about the relationship.

Here's a full example of what this association table diagram would look like with the same instances from before:

Many-To-One

This is essentially the same thing as a one-to-many relationship, only that you're looking at this relationship from the 'many' side. In other words, the local table is the one with 'many' now, and the other table has 'one'.

Both tables have a primary key, like the one-to-many relationship. The foreign key goes on the 'many' side of the relationship. The foreign key points to a primary key in a table on the 'one' side of the relationship.

One-To-One

This is actually a special case of a one-to-many relationship. The representation is similar, but a constraint is added to the database to prevent the 'many' side to have more than one link. It's not a very common type of relationship, but it may come up some day for you.

Representing Followers

Alright, now that we've established and hopefully understood the basic data relationships there are, let's get back to our application. For microblog, we're going to need a "many-to-many" relationship to track followers, because a user follows 'many' users, and a user has 'many' followers. However, since users are just following each other, and not another table like in the student_teacher example, we need to do it slightly differently.

The second entity of the relationship (instead of teachers being the second entity for students) is also the users. A relationship in which instances of a class are linked to other instances of the same class is called a self-referential relationship.

Here's a diagram of the self-referential "many-to-many" relationship that keeps track of followers:

In this case the "followers" table is the association table of the relationship. The foreign keys in this table are both pointing at entries in the user table, since it's linking users to users. Each record in this table represents one line between a follower user and a followed user. Like the students and teachers example, a setup like this one allows the database to answer all queries about followed and follower users that we'll need.

Adding Followers Table

Now, we need to add the "followers" association table to the database in the "models.py" file.

This is just translating the followers association table diagram into code.

Note that we're not declaring this table as a model, like we did for the users and posts tables. Since, this is an auxiliary table that has no data other than the foreign keys, we created it without an associated model class.

Now, we can declare the "many-to-many" relationship in the users table in the "models.py" file.

Here, we're just using the "db.relationship()" function to define the relationship in the "User" model class, just like we did for the posts "one-to-many" relationship. This relationship links "User" instances to other "User" instances. Let's just say that for a pair of users linked by this relationship, the left side user is following the right side user. We're defining this relationship as seen from the left side user with the name "followed", because when we query this relationship from the left side we'll get the list of followed users (those on the right side).

Let's just go over all arguments to the "db.relationship()" call one at a time:

  • "User" is the right side entity of the relationship (the left side entity is the parent class). Since this is a self-referential relationship, we have to use the same class on both sides.
  • "secondary" configures the association table that's used for this relationship, which we defined right above this class.
  • "primaryjoin" indicates the condition that links the left side entity (the follower user) with the association table. The join condition for the left side of the relationship is the user ID matching the "follower_id" field of the association table. The "followers.c.follower_id" expression references the "follower_id" column of the association table.
  • "secondaryjoin" indicates the condition that links the right side entity (the followed user) with the association table. This condition is similar to the one for "primaryjoin", with the only difference being that we're now using "followed_id", which is the other foreign key in the association table.
  • "backref" defines how this relationship will be accessed from the right side entity. From the left side, the relationship is named "followed", so from the right side we're going to use the name "followers" to represent all the left side users that are linked to the target user in the right side. The additional "lazy" argument indicates the execution mode for this query. A mode of "dynamic" sets up the query to not run until specifically requested, which is also how we set up the posts one-to-many relationship.
  • "lazy" is similar to the paramter of the same name in the "backref", but this one applies to the left side query instead of the right side.

These new changes to the database need to be recorded in a new database migration.

Adding & Removing 'Follows'

Thanks to the SQLAlchemy ORM, a user following another user can be recorded in the database working with the "followed" relationship as if it was a list. This is done just by appending the followed users to a list and removing them to 'unfollow'.

Even though this method of adding and and removing followers is fairly easy, we want to promote reusability in our code, so we're not going to put a bunch of 'appends' and 'removes' though the code. Instead, we're going to implement the "follow" and "un follow" functionality as methods in the "User" class inside the "models.py" file (or, the "User" model). It's always best to move the application logic away form view functions and into models or other auxiliary classes or modules, because it makes unit testing much easier, as you'll see later in this chapter.

The "follow()" and "unfollow()" methods justuse the "append()" and "remove()" methods of the relationship object as explained above, but beofre they affect the relationship, they use the "is_following()" supporting method to make sure the requested action makes sense, meaning they're not already following this user to 'follow' them, or are already following the user to 'unfollow' them.

The "is_following()" method issues a query on the "followed" relationship to check if a link between two users already exists. We're used the "filter_by()" method of the SQLAlchemy query object before, from when we found a user given its username. The "filter()" method that we're using here is similar, but can include arbitrary filtering conditions, unlike "filter_by()" which can only check for equality to a constant value. The condition that we're using in the "is_following()" method looks for items in the association table that have the left side foreign key set to the "self" user, and the right side set to the "user" argument. the query is terminated with a "count()" method, which returns the number of results (which will either be 0 or 1). Other query terminators we've used in the past have been "all()" and "first()".

Getting Posts From Followed Users

In the index page of the application, we want to show blog posts written by all the people that are followed by the logged in user, much like you'd see on Instagram. To do this, we need to come up with a database query that returns these posts.

An obvious way to do this is to obtain all followed users by "user.followed.all()" and then iterating through each user to display their posts. Then, we could merge these posts into a single list and sort them by date.

However, this approach has a few of problems:

  1. Executing an infinitely large amount of database queries depending on how many users the logged in user's following would take a while.
  2. We'd need to merge and sort these infinite number of posts in memory.
  3. Consider that the home page will eventually have 'pagination' implemented, so it won't display all the available posts, but just the first few, with a link to get more if desired (like Instagram)

There's actually no way to avoid this merging and sorting of blog posts, but doing it in the application itself results in a very inefficient process. This kind of work is what relational databases excel at, and can help us with. The database has indexes that allow it to perform the queries and the sorting in a much more efficient way. So, what we really want is to come up with a single database query that defines the information that we want to get, and then let the database figure out how to extract that information in the most efficient way on its own. Let's go ahead and write this query under a new method in the "User" model.

Okay, since this is a fairly complex query, I'll go through it one method of the SQLAlehcmy query object at a time.

Join()

Let's look at an example of what the join operation does. Let's assume that we have a "User" table with the following contents:

For this example, we'll only be looking at these two fields of the user, since they're the only ones that matter right now. However, if there were to be more fields to add, you'd just add more columns to each user.

This is what the association table would then look like for this example, rather than the "student_teacher" many-to-many relationship.

This just shows practical examples of who's following who modeled in another diagram.

Finally, the posts table would contain one post from each user.

This table leaves out some fields, though. These being, who's the 'follower' user and who's the 'followed' user, which we can obtain by using the "join()" method of the SQLAlchemy query object.

Once again, here's the join() call that was defined for this query:

This is why we are invoking the join operatin gon the posts table. The first argument is the followers association table, and the second argument is the join condition. What we're saying with this call is that we want the database to create a temporary table that combines data form posts and followers tables. This data will be merged according to the condition.

The condition that we used states that the "followed_id" field of the followers table must be equal to the "user_id" of the posts table. To perform this merge, the database will take each record from the posts table (left side of the join) and append any records from the "followers" table (the right side of the join) that match the condition. If multiple records in "followers" match the condition, then the post entry will be repeated for each. If for a given post there's no match in 'followers', then that post record is not part of the join.

Now, the result of the join operation with the example data defined above will result in a table that looks like this:

If these values seem complicated to you, just backtrack and look at the previous followers and posts tables to see how they merged together. If the values in the followers table don't make sense to you, just look back at the original users table with just the ids and usernames.

Note that the "user_id" and "followed_id" columns are always equal, as this was the result of the join condition being True.

Filter()

The join operation gave us a list of all the posts that are followed by some user, which is a lot more data than what's needed for one specific user. We're only really interested in the posts from followed users as it pertains to the logged in user. Since, we're only interested in a subset of this list, the posts followed by a single user, we need to trim all the entries that aren't needed, which can be done with a "filter()" call.

Once again, here's the filter() call that was defined for this query:

Since, this query is in a method of class "User", the "self.id" expression refers to the user ID of the user that's currently logged in. The "filter()" call selects the items in the joined table that have the "follower_id" column set to this user, which in other words means that we're keeping only the entries that have this user as a follower (or, are followed by this user).

Let's say the user that's logged in is "bogar", who's "user_id" field is set to 1. Here,s how the joined table would look after applying the filtering method:

Remember that the query was issued on the "Post" class, so even though we ended up with a temporary table that was created by the database as part of this query, the result will be the posts that are included in this temporary table, without the extra 'followers' columns added by the join operation.

Order_by()

The last step of this giant query is to sort the results of it. Once again, here's the order_by() call that was defined for this query:

Here, we're just saying that we want the results sorted by the timestamp field of the post in descending order. With this order, the first result will be the most recent blog post.

Combining Own & Followed Posts

Although, the query we've setup to retrieve all posts from followed users is great, we also need to add one more thing to the index page of our web application. People expect to see their own posts included in their timeline of followed users, and the query itself doesn't have that capability.

There's two possible ways of expanding this query to include the user's own posts:

  1. The mose straightforward way is to leave the query as it is, but make sure all users are following themselves. If you're your own follower, then the query as shown above will find your own posts along with those of all the people you follow. Though, this solution isn't optimal, since it'll affect the stats you have regarding followers. Although, the inflation would only be +1, so you could constantly subtract the follower and followed numbers by 1.
  2. The second way to do this is by creating a second query that returns the user's own posts, and then uses the "union" operator to combine the two queries into a single one.

We're going to go with the second approach. Below you can see the "followed_post()" function after it's been expanded to include the user's posts through a union:

Note that the "followed" and "own" queries are combined into one before order_by is called.

Unit Testing

To make sure that this code will continue to work in the future, as we make modifications on different parts of the application, is to write and execute unit tests. These are included with Python in a useful "unittest" package. Let's go ahead and write some unit tests for the existing methods in the "User" class in a "tests.py" file at the top-level of the application's files.



I know it's a lot of code just for some tests, but here we've added four tests that exercise the password hashing, user avatar and followers functinality in the "User" model. The "setUp()" and "tearDown()" methods are special methods that the unit testing framework execute before and after each test...much like when Mr. Morrison says to tear down and get set up.

We've implemented a little feature in "setUp()" to prevent the unit tests from using the regular database that we use for development. by changing the application configuration to "sqlite://" we get SQLAlchemy to use an in-memory SQLite database during the tests. The "db.create_all()" call creates all the database tables. This is a quick way to create a database from scratch that's useful for testing. For development nad production use we've already seen how to create database migrations.

Now that the tests are set up, we can run this file using the "python" command in the terminal like so:

Integrating Followers

After all that database stuff, we can finally actually implement this new system for followers into our web application. Though, there aren't any new concepts here for you to understand. We just need to add two new routes in the application to follow and unfollow a user in the "routes.py" file.

These should be self-explanatory, but take notice to all the error checking that we're doing to prevent unexpected issues and try to provide a useful message to the user when a problem has occured.

With these new view function (or routes) in place, we can link to them form pages in the application. we're going to add links to follow and unfollow a user in the profile page of each user.

This now adds a line below the last seen timestamp that shows how many followers and followed users this user has.

The other line that has the "Edit" link when you are viewing your own profile can now have one of three possible links:

  1. If the user is viewing their own profile, the "Edit" link shows as before.
  2. If the user is viewing a user that's not currently followed, the "Follow" link shows.
  3. If the user is viewing a user that's currently followed, the "Unfollow" link shows.




Chapter 9: Pagination

Blog Post Submission

Let's start with a form in which users can type new posts. First we create a form class in the "forms.py" file.

Next, we need a new form on the home page of the application in which users can type new posts in the "index.html" file.

Now, we need to add the form creation and handling in the view function in the "routes.py" file.

These are all the changes made in this view function:

  • We're now importing the "Post" and "PostForm" classes.
  • We accept "POST" requests in both routes associated with the "index" view function in addition to "GET" requests, since this view function will now receive form data.
  • The form processing logic inserts a new "Post" record into the database.
  • The template receives the "form" object as an additional argument, so that it can render the text field.

You may be wondering why there's a redirect back to the same page that the user's already going to be on. Well, this is done to mitigate an annoyance with how the refresh command is implemented in web browsers. All the web browser does when you hit the refresh key is to re-issue the last request. If a "POST" request with a form submission returns a regular response, then a refresh will re-submit the form. Since, this is unexpected, the browser is going to ask the user to confirm the duplicate submission, but most users will not understand what the browser is asking from them. However, if a "POST" request is answered with a redirect like the one we have, the browser is now instructed to send a "GET" request to grav the page indicated in the redirect, making our application work in a more predictable way.

This simple trick is called the Post/Redirect/Get pattern.

Removing Fake Blog Posts

We can now replace our two fake blog posts in the "index" view function with real ones.

Explore Page

We're now going to make a page that'll address the issue of not being able to find other users (without manually typing in their profile URLs). Here's the new explore view function:

We're actually reusing the "index" template in rendering this one's template, since they'll be so similar. Hence, why we added 'index.html' into the arguments of the "render_template()" function. One difference with the main page is that in the explore page, we don't want to have a form to write blog posts, so in this view function we didn't include the "form" argument in the template call.

To prevent the "index.html" template from crashing when it tries to render a web form that doesn't exist, we're going to add a conditional that only renders the form if it's defined.

We're also going to add a link to this new page in the navigation bar in the "base.html" file.

We're going to go back to the "post.html" sub-template from earlier. This was a small template that was included form the user profile page template, and was separate so that it can also be used from other templates. We're now going to make a small improvement to it, which is to show the username of the blog post author as a link.

We can now use this sub-template to render blog posts in the home and explore pages.

This is now what the explore page is looking like after adding a few posts from different registered users:

Blog Post Pagination

Now, it's time to implement pagination, which will limit the amount of posts per page at one time, to avoid clutter, run time being too long, and other issues that could arise with having all of them displayed at once.

In paginating the post list, we're going to show just a limited number of posts at a time, and include links to navigate through the entire list of posts. Flask-SQLAlchemy supports pagination natively with the "paginate()" query method.

The "paginate()" method can be called on any query object form Flask-SQLAlchemy. It takes these three arguments:

  1. The page number, starting from 1.
  2. The number of items per page.
  3. An error flag. If True, when an out of range page is requested a 404 error will be automatically returned to the client. If False, an empty list will be returned for out of range pages.

The return value from "paginate()" is a Pagination object. The "items" attribute of this object contains the list of items in the requested page.

Now, let's start by adding a configuration item to the application that determines how many items will be displayed per page.

This single adjustment to the Config class will be applied throughout the entire web application.

Next, we need to use a query string argument to specify an optional page number, defaulting to page 1 if it's not given. Here are some example URLs that show how we're going to implement this:

  • Page 1, implicit: http://localhost:5000/index
  • Page 1, explicit: http://localhost:5000/index?page=1
  • Page 3: http://localhost:5000/index?page=3
  • In order to access arguments given in the query string, we can use the Flask's "request.args" object. We've seen this already in Chapter 5, where we implemented user login URLs from Flask-Login that can include a "next" query string argument.

    Below, you can see how we're adding pagination to the home and explore view functions:

    Make sure you have at least 3 blog posts, then go ahead and try this out in your browser's address bar like so:



    Page Navigation

    The next change we need to implement is to add links at the bottom of the blog post list that allow users to navigate to the next and/or previous pages.

    Earlier you learned that the return value from a "paginate()" call is an object of a Pagination class form Flask-SQLAlchemy, and that one of its attributes was "items". Now, we're going to use another 4 of its attributes:

    • "has_next": True if there's at least one more page after the current one
    • "has_prev": True if there's at least one more page before the current one
    • "next_num": The page number for the next page
    • "prev_num": The page number for the previous page

    With these four attributes, we can generate next and previous page links and pass them down to the templates for rendering in the "routes.py" file.

    The "next_url" and "prev_url" in these two view functions are going to be set to a URL reutrned by "url_for()" only if there's a page in that direction. If the current page is at one of the ends of the collection of posts (beginning or end), then the "has_next" or "has_prev" attributes of the Pagination object will be False, and in that case the link in that direction will be set to None.

    The pagination links are being set to the "index.html" template, so now let's render them on the page, right below the post list:

    Here are the changes we've just made to the application in action:



    Profile Page Pagination

    There's also a list of posts in the user profile page, which shows only posts from the owner of the profile. To be consistent, the user profile page should also be changed to match the pagination style of the "index" page. We begin by updating the user profile view function, which still has a list of fake post object in it.

    To get the lists of posts from the user, we take advantage of the fact that the "user.posts" relationship is a query that's already set up by SQLAlchemy as a result of the "db.relationship" definition in the "User" model. We take this query and add an "order_by()" clause so that we get the newest pages first, and then do the pagination exactly like we did for the posts in the index and explore pages. Note that the pagination links that are generated by the "url_for()" function need the extra "username" argument, becuase they're pointing back at the user profile page, which has the username as a dynamic component of the URL.

    Finally, the changes to the "user.html" template are identical to those we made on the "index" page.

    Now, as an optional addition, you can set the POSTS_PER_PAGE value to something different in the "config.py" file if you'd like.




Chapter 10: Email Support

Introduction To Flask-Mail

Let's start off by installing a couple extensions from Flask and Python. The first is called "flask-mail"

The second is called "pyjwt". This will help to generate secure tokens for the password reset links.

The configuration variables we set earlier in the "config.py" file will work with Flask-Mail.

Like the other Flask extensions we've installed, we need to create an instance right after the Flask application is created in the "__init__.py" file. In this case this it's an object of class "Mail".

Since we're planning to test sending of emails we have the same two options mentioned in Chapter 7. We're going to go with the second one, which means actually sending an email to ourselves. This requires setting these environment variables like so (use "export" instead of "set" if you're not on Microsoft Windows):

Once again, don't worry if you don't get an email, though. Since, your Gmail account may prevent the application from sending emails through it unless you explicitly allow "less secure apps" access to your Gmail account. You should get an email saying that there's a "Google Security Alert", which leads you to information on how to allow this option.

Flask-Mail Usage

To learn how Flask-Mail works, you'll see how to send an email from a Python shell. After typing in the "flask shell" command into your terminal, run the following commands:

Now, just check your inbox and you'll have the email sent to you and any other email addresses you included in the "recipients" argument.

We put the sender as the first configured admin. The email will have plain text and HTML versions, so depending on how your email client is configured you may see one or the other. For Gmail, I believe it's the HTML version.

Simple Email Framework

Now, let's integrate emails directly into our application.

We'll begin by writing a helper function that sends an email in a new file/module called "email.py" inside of the "app" folder. This is the same thing as what we saw in the previous section in the python shell.

Password Reset Request

For the purpose of allowing users to have the option to request their password to be reset, we're going to add a link in the login page.

When the user clicks the link, a new web form will appear that requests the user's email address as a way to initiate the password reset process. This will be created in the "forms.py" file as a new form class.

Now, we need to also create a new corresponding HTML template called "reset_password_request.html".

We also need a view function to handle this form in the "routes.py" file.

This view function is fairly similar to others that process a form. We start it off by making sure the user isn't logged in. If they are, then there's no point in using the password reset functionality, so we simply redirect them to the "index" page.

When the form is submitted and valid, we look up the user by the email provided by the user in the form. If we find the user, we send a password reset email, which'll be done using a "send_password_reset_email" function that we'll make later.

After the email's sent, we flash a message directing the user to look for the email for further instructions, and then redirect them back to the login page. We are actually displaying the flashed message even if the email provided by the user is unknown. This is so that clients can't use this form to figure out if a given user is a member or not.

Password Reset Tokens

Before we implement the "send_password_reset_email" function, we need to have a way to generate a password request link. This is going to be the link that's sent to the user via email. When the link's clicked, a page where a new password can be set is presented to the user.

The links are going to be supplied with a token, and this token will be validated before allowing the password change, as proof that the user that requested the email has access to the email address on the account. A very popular token standard for this type of process if the JSON Web Token, or JWT. These tokens are self contained, which means you can send a token to a user in an email, and when the user clicks the link that feeds the token back into the application, it can be verified on its own.

We're going to launch a Python shell session to understand them by just typing in "python" or "python3" into the terminal.

The {'a': 'b'} dictionary is an example payload that's going to be written into the token. To make the token more secure, a secret key needs to be provided to be used in creating a cryptographic signature. For this example, we've used the string 'my-secret', but with the appliation we're going to use the "SECRET_KEY" from the "config.py" file. The "algorithm" argument specifies how the token is to be generated, HS256 being the most widely used one.

The resulting token is a long sequence of characters. However, it's not an encrypted token. The contents of the token, including the paylod, can be decoded easily by anyone. What makes the token secure is that the payload is signed. If somebody tried to forge or tamper with the payload in a token, then the signature would be validated, and to generate a new signature the secret key is needed. When a token is verified, the contents of the payload are decoded and returned back to the caller. If the token's signature was validated, then the payload can be trusted as authentic.

The payload that we're going to use for the password reset tokens is going to have the format {'reset_password': user_id, 'exp': token_expiration}. the "exp" field is standard for JWTs, and if present, it indicates an expiration time for the token. If a token has a valid signatiure, but it's past its expiration timestamp, then it'll also be considered invalid. For the password reset feature, we'll give these tokens 10 minutes till their expiration.

When the user clicks on the emailed link, the token's going to be sent back to the application as part of the URL, and the first thing the view function that handles this URL will do is verify it. If the signature is valid, then the user can be identified by the ID stored in the payload. Once the user's identity is known, the application can ask for a new password and set it on the user's account.

Since, these tokens belong to users, we're going to write the token generation and verification functions as methods in the "User" model in the "models.py" file.

The "get_reset_password_token()" function generates a JWT token as a string. Note that the "decode ('utf-8')" is necessary becuase the "jwt.encode()" function returns the token as a byte sequence, but in the application it's more convenient to have the token as a string.

The "verify_reset_password_token()" is a static method, which means that it can be invoked directly from the class. A static method is similar to a class method, with the only difference being that static methods don't receive the class as a first argument. This method takes a token and attempts to decode it by invoking PyJWT's "jwt.decode()" function. If the token can't be validated or is expired, an exception will be raised, and in that case we'll catch it to prevent the error, and then return None to the caller of the function. If the token is valid, then the value of the "reset_password" key from the token's payload is the ID of the user, so we can load the user and return it.

Password Reset Email

Now, that we have the tokens, we can generate the password reset emails. The "send_password_reset_email()" function relies on the "send_email()" function written earlier. Here's the new function in the "email.py" file:

Now, we just have to make the two files that the render_template() function renders from. One for .txt format and one in .html format. Since, we take the user and token as arguments, there can be a personalized email message generated by these files. These two files are going to be made inside the "email" folder inside the "templates" folder and are both going to be called "reset_password", just with two different file extensions: .txt and .html.



The "reset_password" route that's referenced in the "url_for()" call in these two email templates doesn't exist yet, but will be added later. The "_external=True" argument that's included in the "url_for()" calls in both templates is also new. The URLs that are generated by "url_for" by default are relative URLs, which have been explained in Chapter 5. When "_external=True" is passed as an argument, complete URLs are generated.

User Password Resetting

When the user clicks on the email link, we want for there to be a second route associated with this feature. We're going to make the password request view function in the "routes.py" file.

In this view function, we first make sure the user isn't logged in, and then we determine who the user is by invoking the token verification method in the "User" class. This method returns the user if the token is valid, or None if not. If the token is invalid we redirect them to the home page.

If the token is valid, then we present the user with a second form, in which the new password is requested. This form is processed in a way similar to previous forms, and as a refult of a valid form submission, we invoke the "set_password" method of "User" to change the password, and then redirect them to the login page, where the user can now login.

Here's the new ResetPasswordForm class in the "forms.py" file:

Here's the corresopnding HTML template file named "rest_password" in the "templates" folder:

You can now go ahead and try out the new password reset feature if you'd like

Asynchronous Emails

Sending an email slows the application down considerably. All the interactions that need to happen when sending an email make the task slow, since all these things can take some time:

  • Getting the email out.
  • Waiting for the possibly slow email server of the recipient.
  • Sending the email to multiple addresses.

What we really want here is for the "send_email()" function to be asynchronous. This means that when this function is called, the task of sending the email is schedule to happen in the background, freeing the "send_email()" to return immediately so that the application can continue running concurrently with the email being sent. Basically, the application's code still keeps running while the email gets sent.

Python has support for running asynchronous tasks, in multiple ways. The "threading" and "multiprocessing" modules can both do this. Starting a background thread for email being sent is much less resource intensive than starting a brand new process, so that's the approach we're going with.

The "send_async_email" function now runs in a background thread, invoked via the "Thread()" class in the last line of "send_mail()". With this change, the sending of the email will run in the thread, and when the process completes, the thread will end and clean itself up.

Along with the "msg" argument being sent to the thread, there's also the application instance. When working with threads, Flask uses contexts to avoid having to pass arguments across functions. There are two tpyes of contexts, the application context and the request context. In most cases, these contexts are automatically managed by the framework, but when the application starts custom threads, contexts for those threads may need to be manually created.

Many extensions require an application context to be in place to work, because that allows them to find the Flask application instance without it being passed as an argument. The reason many extensions need to know the application instance is becuase they have their configuration sotred in the "app.config" object, like what's the case with Flask-Mail. The "mail_send()" method needs to access the configuration variable values for the email server.




Chapter 11: Styling

CSS Frameworks

If you just want to create decent looking web pages, but don't want to do it by yourself by writing raw HTML and CSS code, then the only practical solution is to use a CSS framework to simplify the task. You'll lose some of your creative freedom by going down this path, but your web pages will look good in all browsers without a lot of effort. A CSS framework provides a collection of high-level CSS classes with pre-made styles for common types of user interface elements.

Introducing Bootstrap

This is actually the CSS framework I personally used to help me design the website you're reading this off of right now. Although, I did add an extra ~100 lines of code in two CSS files I made.

Here are some of the benefits of using Bootstrap to style your web pages:

  • There'll be a similar look in all major web browsers.
  • It can handle desktop, tablet, and phone screen sizes (which is what makes this website mostly cross-platform, aside from pictures).
  • There are customizable layouts
  • It has nicely styled navigation bars, forms, buttons, alerts, popups, etc.

The most direct way to use Bootstrap is to simply import the "bootstrap.min.css" file in your base template. You can either download a copy of this file and add it to your project, or import it directly from a CDN (Content Delivery Network). Then, you can start using the general purpose CSS classes it provides.

fortunately for us, there's a Flask extension called "Flask-Bootstrap" that provides a ready to use base template that has the Bootstrap framework already installed on it. To install it, type this command into your terminal window:

Using Flask-Bootstrap

Let's go ahead and initialize this extension in the "__init__.py" file.

With the exntension initialized, a "base.html" templte becomes available in the "bootstrap" folder, and can be referenced from application templates with the "extends" clause.

However, we're already using the "extends" clause with our own "base.html" template. Our template also deifned the navigation bar, which included a few links, ans also exported a "content" block. All other templates in our application inherit from the "base.html" template and provide the "content" block with the main content of the page.

So, the idea of how we're going to fit the Bootstrap base template is to use a three-lvel hierarchy instead of just two (being the base template and the templates that inherit from it). The "base.html" template in the "bootstrap" folder provides the basic structure of the page, which includes the Bootstrap framework files. This template exports a few blocks for derived templates such as "title", "navbar", and "content" blocks. In turn, the bootstrap base template will export its own "app_content" block for its derived templates to define the page content.

Now, we need to make our "base.html" file in the "templates" folder inherit from the Bootstrap base template.

Here you can see how we made this template derive form "base.html" file in the "bootstrap" folder, followed by the three blocks that implement the: page title, navigation bar and page content.

The "title" block needs to define the text that'll be used for the page title, with the "title" tags. For this block, we simply moved the logic that was inside the "title" tag in the original base template.

The "navbar" block is an optional block that can be used to define a navigation bar. For this block, we included a site branding on the left end, followed by the "Home" and "Explore" links. We then added the "Profile" and "Login"/"Logout" links aligned with the right border of the page.

Finally, in the "content" block we're defining a "top-level" container, and inside it we have the logic that renders flashed messages, which are now going to appear styled as Bootstrap alerts automatically. That's followed with a new "app_content" block that's defined just so that derived templates can define their proper content.

The original version of all the page templates defined their content in a block named "content". Since, the block named "content" is now used by Flask-Bootstrap, we renamed our content block as "app_content". So, all our templates have to be renamed to use "app_content" as their content block.

To do this, we can just do a search from all our files and replace "{% block content %}" with "{% block app_content %}". This can be done in Visual Studio Code, by first clicking on the 'Search' button:

Then, we need to indicate what we want to find, and what we want to replace it with in each of our files. To do this, type this into each of the boxes:

Now, we've found all files including the text we searched for, but we actually found a file called "METADATA", which we don't want to touch. We also don't want to modify the "base.html" file, since this is the only file in which we're supposed to have "{% block conent %}" in, and we've already changed the second block to "{% block app_content %}". So, to remove these two files from your operation, simply right click on them and click "dismiss".

Now, just click on the "replace all" button found here:

Rendering Bootstrap Forms

Instead of having to style the form fields one by one, Flask-Bootstrap comes with a macro that accepts a Flask-WTF form object as an argument and renders the complete form using Bootstrap styles. Now, to implement this feature form Flask-Bootstrap, we need to change up the register.html template:

The "import" statement near the top works similarly to a Python import. That adds a "wtf.quick_form()" macro that, in a single line of code, renders the complete form, including support for display validation errors.

Blog Post Rendering

The presentation logic that renders a single blog post was abstracted into a sub-template file called "_post.html". All we need to do with this template is make some minor adjustments so that it looks good under Bootstrap.

Rendering Pagination Links

For pagination links, we're just going to adapt one of the examples in the Bootstrap documentation. Here's what they look like in the "index.html" page:

Note that in this implementation, instead of hiding the next or previous link when that direction doesn't have any more content, we're applying a disabled state, which will make the link appear grayed out.

We also need to implement a similar change in the some other files.









Now, let's finally go ahead and run the application to see the improvements in design and what it looks like now.














Chapter 12: Dates and Times

Timezone Conversions

Using Python on the server to render dates and times that are presented to users on their web browsers isn't great. Since, it either displays the current time appropriate to your current timezone with the "datetime.now()" function, or displays the current time regardless of location with the "datetime.utcnow()" function.

It's evident then, that the server must manage times that are consistent and independent of location. So, we're obviously going to be using the utc (universal time coordinated) standard method of displaying current date and time.

However, there is one problem with this approach. For users in different timezones, it'll be awfully difficult to figure out when a post was made if they see times in the UTC timezone. To resolve this issue, we're going to address this problem while keeping all the timestamps managed in the server in UTC.

What's really tough about converting UTC times to localized ones for each user's location, is knowing the location of the user. Many webistes simply have a configuration page where users can specify their timezone. This would require us to add a new page with a new form to be presented to users, as well as, the inconvenience for users to require them to set this up when they register their accounts.

As it turns out, though, the web browser knows the user's timezone, and exposes it through the standard data dn time JavaScript API (Application Programming Interface)s. So, using JavaScript, we're simply not going to change a thing in the server, and let the conversion from UTC to local time happen in the client. In addition to just the date and time of the user, the browser also has access to the system locale configuration, which specifies things like AM/PM vs 24 hour clock, DD/MM/YYYY vs. MM/DD/YYYY and many other cultural or regional styles to formatting the data and time.

Introducing Moment.js & Flask-Moment

Moment.js is a small open-source JavaScript library that provides every imaginable formatting option. Flask-Moment is a small Flask extension that makes it very easy to incorporate moment.js into our application. Let's start by installing it.

Now, as usual, just add an instance of this class to the "__init__.py" file.

To ensure that this library is always available in all pages, we'll add it in the base template. Normally, to add JavaScript to an HTML file, you'd have to incorporate a "script" tag and then enter your code inside, but Flask-Moment makes it easier, by exposing a "moment.include_moment()" function that generates the "script" tag for you.

The "scripts" block that we've added here is another block supported by Flask-Bootstrap's base template. This is where JavaScript imports are to be included. This block is different from previous ones in that it already comes with some content defined in the base template. All we want to do here is add the moment.js library, without losing the base contents. This is done with the "super()" statement. If we were to have defined a block in our template without using "super()", then any content defined for this block in the base template would've been lost.

Using Moment.js

Moment.js makes a "moment" class available to the browser. The first step to rendering a timestamp is to create an object of this class, passing the desired timestamp in this format. The format is as follows: (year)-(month)-(day)T(hour):(minute):(second)(timezone). An example is as follows:

The last part is "Z", which represents the UTC in the ISO 8601 standard format.

The "moment" object provides several methods for different rendering options. Below are some of the most common options:

These examples all create a moment object initialized to March 27th 2020 at 12:30am UTC. YOu can see that all the options tried above are rendered in UTC-7, which is the timezone configured on our computers.

If we were working directly in JavaScript, the above calls would've returned a string that had the rendered timestamp. Then, it's up to us to insert this text in the proper place on the page. This whole process is greatly simplified by the Flask-Moment extension, though, by enabling a "moment" object similar to the JavaScript one in our templates.

Let's take a look at the timestamp that appears in the profile page. The current "user.html" template allows Python to generate a string representation of the time. We can now render this timestamp using Flask-Moment as follows:

The second place where we can take advantage of the Flask-Moment and moment.js is in the "_post.html" sub-template, which is invoked from the "index" and "user" pages. In the current version of the template, each post preceded with a "(username) says:" line. Now, we can add a timestamp rendered with "fromNow()".

Now, this is what shows up in the browser: