Python3 Flask Template
A starter template for Flask web apps using Python 3.8. This starter template includes:
- Dynamic frontend templates (not React)
- Database support
- Imperial LDAP user authentication
- Multiple environments (development and production)
This template is useful if you would like to create a backend REST API, optionally coupled with a simple dynamic frontend.
Getting started
Once you have created your new app, take a few minutes to look through the files to familiarise yourself with the project structure.
-
app.py
: entry point to the Flask app -
templates/
: contains the frontend dynamic HTML files -
static/
: contains the static frontend assets (images, stylesheets and scripts) -
blueprints/
: contains all the application routes -
models/
: contains all the database models -
database/
: contains the database creation -
config/
: contains the app settings for the different environments
The first change you should make is to set the APP_NAME
variable in config/config.py
to whatever you app's name is.
To start the application locally, you can just run python3 app.py
and this will launch the app on port 5000 (by default).
You will notice a message in the console saying:
WARNING: Could not connect to the given database URL!
To fix this, you should set the environment variable DATABASE_URL accordingly. If you have PostgreSQL running locally, you can use that. Alternatively, you could use SQLite which is much simpler and does not require installation.
If you do not want to use a database yet, you can ignore this warning and delete any routes that interact with the database.
If you navigate to http://localhost:5000
, you will see the response created by the route defined in blueprints/home.py
.
You will also notice the lines Environment: production
and Debug mode: off
when the Flask application starts in the console. To enable debug mode, you must set the environment variable ENV
to dev
, ie: export ENV=dev
(see config/config.py
for more details on different environments).
Flask Routes
A "route manager" ("router") in Flask is called a "Blueprint", it has the role of handling requests from all routes that start with the provided "url_prefix". It can be thought of as a "subrouter", with the "superrouter" being the Flask app itself, checking for a match against all registered "subrouters".
All blueprints need to be "registered" in app.py
, otherwise they will not be considered when path matching, and therefore will not be accessible.
Flask Migrations and Databases
A table in flask is called a "Model", all models need to extend "db.Model". You have been provided with two models, "Person" and "PhoneNumber" which is a 1-many relationship; a "Person" can be many "PhoneNumber".
Setup
It is very useful to have some view of your database as you develop to see whether migrations have actually been performed, and to view your data. We recommend "PgAdmin" if you are going to use PostgreSQL connection strings.
Download link: https://www.pgadmin.org/download/
Tutorial: Setup remote connection to database with PgAdmin
-
Right-click on "Servers".
-
Create => Server...
-
Go to "Connection"
-
Enter for "Host" the IP address (or hostname) of the server which runs PostgreSQL Server.
-
Enter the "Port", default is
5432
. -
Enter the "Username", default is
postgres
. -
Enter the "Password", no default, the server should've been setup with one.
-
Hit "Save".
Tutorial: Create a database remotely with PgAdmin
-
Click the server you remotely connected to in the previous tutorial.
-
Right-click on "Databases".
-
Create two new databases: one called "first_db_prod", and another called "first_db_debug".
-
Form two connection strings using the format below:
postgresql://<username>:<password>@<host>:<port>/<db_name>
Example: postgresql://postgres:sAmPlE>@82.36.12.1:5432/first_db
- Go to the next tutorial below.
Tutorial: connect to your database
You will need to specify the database connection strings for development ("debug" or "dev") and for production ("prod").
-
Navigate to:
config/config.py
-
CTRL + F, then type in: db_configs
-
You'll see a single entry with two specified environment variables.
-
For each environment variable, configure your computer to set these as user environment variables. In Windows do the following: Control Panel => Search: environment => Edit the environment variables for your account => "New" => ...
-
Reload your IDE so the new environment variables values take effect.
Notes
- To speed up development you may use the "force" optional parameter variants, however this is discouraged as it exposes your connection string in your git repository.
- The two connection strings must NOT be the same!
- Do not use SQLite if you plan to make use of flask migrations.
- If you do add another database, make sure you add a static variable as well under the "DB Bindings" region, as you'll need to be able to reference this binding from the outside when creating models for the database this binding will be for.
- All binding key names must be unique; they are the identifiers Flask uses to uniquely identify each database amongst all other databases in your application.
Adding a new table
-
Navigate to:
models
; you're models should all be made here. -
Modify the "models"
"__init__.py"
to import your model, otherwise flask will not see it and not be able to migrate it. -
Run a new migration when you're happy with your model to auto add the table for you.
Running a Migration
The "migrations" directory contains the following scripts you'll need to use: RunDowngradeMigration.sh
and RunUpgradeMigration.sh
.
When you run an "upgrade" migration for the first time subdirectories migrations/dev
and migrations/prod
should be constructed.
Managing Migrations
The migrations
directory contains all migrations you have performed on every database your app uses. It contains the subdirectories dev
and prod
, then subdirectories identified by the binding keys used.
You have been provided with the binding first_db
. Running an "upgrade" migration will result in the following path being accessible:
migrations/dev/first_db/
It is very important that you do not delete anything from this directory, since flask migrate will also store a migration history in correspondance to the directory's contents in your database, under the alembic
table.
Tutorial: running an "upgrade" migration script
Flask migrate will automatically generate a migration script based on the differences between your database represented in flask (this application) and the actual database. It may be incorrect, or may not fully complete the migration you want. To resolve this, you should check the migration script, and verify it does what you want it to do.
-
Navigate to:
models/first_db_models/phone_number.py
-
It is very useful for rows in a database to have a
created_at
attribute so you know when the row was first inserted. Comment out the new under the TODO, and remove the TODO. -
Navigate and edit the file:
migrations/RunUpgradeMigration.sh
-
Change the value of the 3rd argument to either
dev
orprod
. -
Change the value of the 4th argument to be the binding key name of the database you want to migrate. Since "PhoneNumber" is a model that exists in the database bound to the binding key name "first_db", change the value to "first_db".
-
Save the file.
-
Navigate and edit the file:
migrations/RunMigration.sh
-
Change the migration description, that is replace "In this migration we did..." with "Added 'created_at' column to 'phoneNumber'."
-
Save the file.
-
Run:
migrations/RunUpgradeMigration.sh
-
Navigate to:
migrations/dev/first_db/versions
-
You should see a new python file created in here under some hash code, open the file.
-
Verify the file is doing an
add_column
operation in theupgrade
method. -
Complete the migration by pressing ENTER.
-
Repeat for the other database, i.e.: if you migrated on
dev
, then you should also migrate onprod
at some point.
Tutorial: running an "downgrade" migration script
Example: you performed an "upgrade" migration but realize that it wasn't what you wanted, or could improve the column definition, for example you realize "phone_number" should not be a column part of "Person" but rather a separate table called "PhoneNumber" with a 1-many relation.
-
Edit
migrations/RunDowngradeMigration.sh
. -
Change the value of the 3rd argument to either
dev
orprod
. -
Change the value of the 4th argument to be the binding key name of the database you want to migrate. Since
PhoneNumber
is a model that exists in the database bound to the binding key namefirst_db
, change the value tofirst_db
. -
Save the file, then run it.
Troubleshooting
Consistency violation, cannot find version
Delete/drop the alembic_version
table from the database, and delete the associated migration directory for this table only.
Migration script says "No changes in schema detected"
(1) Ignore it
This is perfectly fine, this is just Flask Migrate checking for a migration. If you just want to downgrade from a previously run migration just ignore the output, it'll take the HEAD of the migration scripts applied, and call the "downgrade" method, then move the HEAD down to the next version.
Likewise, if you ran "RunUpgradeMigration" but cancelled before continuing with the autogenerated migration script created, you can ignore the output and continue; it'll take the HEAD of the migration scripts applied, and call the "upgrade" method, then move the HEAD to that script version.
(2) Check whether or not you "registered" the model
Whenever you add a new model you must also modify the upper package's __init__.py
files to import it.
Working with the CLI
This template comes with a CLI that is enabled when your environment is in the "dev" mode. It assumes all arguments provided on running "app.py" are CLI arguments.
If you haven't already configured a "second_db", you will need to configure one for the next tutorials.
Tutorial: Inserting Rows
You can insert rows using raw SQL queries via PgAdmin, however you can also insert rows using the CLI.
-
Navigate to "cli_handler.py", let's see what commands are available to us.
-
CTRL + F: CLI Argument Names
-
This region contains all arguments you are allowed to specify, for example:
app.py action=insert-iris-class binding=second_db ...
. -
The valid "binding" values are those associated with your databases, it'll allow the CLI to determine which db to operate on.
-
Now checkout the available "action" names => CTRL + F: ACTION handlers
-
Lets populate our "IrisClass" table.
-
Using the CLI type in:
app.py action=populate-iris-class binding=second_db
. After running this command you should see the table populated in PgAdmin. -
Lets add a new "Iris Class" to the "IrisClass" table. You can inspect its associated model in:
/models/second_db_models/iris_class.py
. Use the CLI to do this. The new iris class should be calledMy Lovel Iris
, and note that to specify strings with space-separation we need to wrap the value in double quotes.
py app.py action=insert-iris-class "My Lovely Iris" binding=second_db
NOTICE: the CLI assumes values not containing '=' are arguments for the "action" handler.
- Lets add a new "Iris" to the "Iris" table. You can inspect its associated model in:
/models/second_db_models/iris.py
. Use the CLI to do this.
py app.py action=insert-iris 1.1 2.2 3.3 4.4 "My Lovely Iris" binding=second_db
NOTE: specifying class names that are not in "IrisClass" will raise an insertion error, as its a foreign key dependency value.