Flask Mega-Tutorial Notes
$ export FLASK_APP=microblog.py
$ export FLASK_DEBUG=1
$ flask run
* Serving Flask app "microblog"
* Forcing debug mode on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 148-568-946
Account Info: sivpack / 123456
The
__name__
variable passed to the Flask class is a Python predefined variable, which is set to the name of the module in which it is used. Flask uses the location of the module passed here as a starting point when it needs to load associated resources such as template files
Import at bottom of __init__.py
is to prevent circular imports
The routes are the different URLs that the application implements. In Flask, handlers for the application routes are written as Python functions, called view functions. View functions are mapped to one or more route URLs so that Flask knows what logic to execute when a client requests a given URL.
Templates help achieve this separation between presentation and business logic.
Jinja2 Placeholders
placeholders for the dynamic content, enclosed in {{ ... }} sections. These placeholders represent the parts of the page that are variable and will only be known at runtime.
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
In essence, what you can do is move the parts of the page layout that are common to all templates to a base template, from which all other templates are derived.
Define a base.html
template that includes all your navigation and header information
Use named block
control statement to define place where templates can be inserted
Insert as follows:
{% extends "base.html" %}
{% block content %}
Template specific content
{% endblock %}
Use {{ super() }}
to not overwrite block
We can link to pages, but we should really link to functions using url_for()
which generates URLs using the internal mapping of URLs to view functions
You can add any keyword arguments to url_for
, and if the names of those arguments are not referenced in the URL directly, then Flask will include them in the URL as query arguments.
If we are reusing parts of the layout over and over again in the same page, it might be worth loooking into sub-templates which can be inserted into each template using `{% include '_page.html' %}
Extensions are a very important part of the Flask ecosystem, as they provide solutions to problems that Flask is intentionally not opinionated about
Flask (and possibly also the Flask extensions that you use) offer some amount of freedom in how to do things, and you need to make some decisions, which you pass to the framework as a list of configuration variables.
For forms, we will be using Flask-WTF
Best Practice: Keep configuration in a separate class
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
The configuration settings are defined as class variables inside the Config class. As the application needs more configuration items, they can be added to this class, and later if I find that I need to have more than one configuration set, I can create subclasses of it. But don't worry about this just yet.
Apply it
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
from app import routes
The Flask-WTF extension uses Python classes to represent web forms. A form class simply defines the fields of the form as class variables.
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 is not submitted empty. There are many more validators available, some of which will be used in other forms.
-
Will need to create a form that inherits
FlaskForm
and uses field objects as building blocks for the form- can attach validators which generate descriptive error messages (stored under
form.<field_name>.errors
)
- can attach validators which generate descriptive error messages (stored under
-
Create a
POST
handler in our view function
Uses SQLAlchemy, well Flask-SQLAlchemy, for ORM. Can plug in any number of relational databases
I have also added two methods to this class called
validate_username()
andvalidate_email()
. When you add any methods that match the patternvalidate_<field_name>
, WTForms takes those as custom validators and invokes them in addition to the stock validators.
a validation error is triggered by raising
ValidationError
. The message included as the argument in the exception will be the message that will be displayed next to the field for the user to see.
The data that will be stored in the database will be represented by a collection of classes, usually called database models. The ORM layer within SQLAlchemy will do the translations required to map objects created from these classes into rows in the proper database tables.
The id field is usually in all models, and is used as the primary key. Each user in the database will be assigned a unique id value, stored in this field.
Flask-Migrate for migrations, based on Alembic which is the migration tool for SQLAlchemy. Migrations are used when we need to change how data is stored.
- Adds
flask db
subcommand to flask CLI flask db init
creates new migration repositoryflask db migrate -m "message"
generates migration scriptflask db upgrade
runs migrationsflask db downgrade
rolls back migrations
Without migrations you would need to figure out how to change the schema of your database, both in your development machine and then again in your server, and this could be a lot of work.
db.relationship
- 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
Changes to a database are done in the context of a session, which can be accessed as
db.session
. Multiple changes can be accumulated in a session and once all the changes have been registered you can issue a singledb.session.commit()
, which writes all the changes atomically. If at any time while working on a session there is an error, a call todb.session.rollback()
will abort the session and remove any changes stored in it. The important thing to remember is that changes are only written to the database whendb.session.commit()
is called. Sessions guarantee that the database will never be left in an inconsistent state.
.first_or_404()
returns a result or sends a 404 back to the client
We can use the flask shell
command to get into the Python shell and have the application context be reloaded.
In our FLASK_APP
script, we can add shell context processors that can load information as required:
@app.shell_context_processor
def make_shell_context():
return {
'db': db,
'User': User,
'Post': Post,
}
We take a password and put it thru a non-reversible cryptographical algorithm so that it's true value is obfuscated.
This extension manages the user logged-in state, so that for example users can log in to the application and then navigate to different pages while the application "remembers" that the user is logged in. It also provides the "remember me" functionality that allows users to remain logged in even after closing the browser window.
Need to add the following to the User
model:
* is_authenticated
a property that is True if the user has valid credentials or False otherwise.
* is_active
a property that is True if the user's account is active or False otherwise.
* is_anonymous
a property that is False for regular users, and True for a special, anonymous user.
* get_id()
a method that returns a unique identifier for the user as a string.
We can add these to our model via flask_login.UserMixin
Flask-Login keeps track of the logged in user by storing its unique identifier in Flask's user session, a storage space assigned to each user who connects to the application. Each time the logged-in user navigates to a new page, Flask-Login retrieves the ID of the user from the session, and then loads that user into memory.
Need to create a method and register it with @flask_login.login.user_loader
so we know which user we are dealing with
We can also rediret users based on next query parameters, but be careful that we only redirect relative URLs not full URLs
current_user
gets the user that is currently logged in from the db
@app.before_request
@app.after_request
Flask can show errors in the browser:
* export FLASK_DEBUG=1
When the application is in production, we need a way to keep track of errors so we can the problems that occur in order to fix them later.
Use standard library logger, it just makes more sense.
This type of relationship allows only one record on each side of the relationship.
The primary key relates to only one record – or none – in another table. For example, in a marriage, each spouse has only one other spouse. This kind of relationship can be implemented in a single table and therefore does not use a foreign key.
a one-to-many relationship exists when one row in table A may be linked with many rows in table B, but one row in table B is linked to only one row in table (Wikipedia)
- property of the relationship
- relationship is represented in the database with the use of a foreign key on the "many" side.
Consider a business with a database that has Customers and Orders tables. A single customer can purchase multiple orders, but a single order could not be linked to multiple customers. Therefore the Orders table would contain a foreign key that matched the primary key of the Customers table, while the Customers table would have no foreign key pointing to the Orders table. (Source)
- This is a complex relationship in which many records in a table can link to many records in another table.
such relationships are usually implemented by means of an associative table (also known as junction table or cross-reference table), say, AB with two one-to-many relationships A -> AB and B -> AB. In this case the logical primary key for AB is formed from the two foreign keys(Wikipedia)
-
While it may not seem obvious at first, the association table with its two foreign keys is able to efficiently answer all the queries about the relationship.
-
A relationship in which instances of a class are linked to other instances of the same class is called a self-referential relationship, and that is exactly what I have here.
- If we are trying to render all the items in the database, this could become a problem if our resultset is large
- this is why we paginate
- Supported natively by Flask-SQLAlchemy
user.followed_posts().paginate([pageNum], [items_per_page], [error_flag]).items
user.followed_posts().paginate(1, 20, False).items
Pagination object has other attributes:
has_next
: True if there is at least one more page after the current onehas_prev
: True if there is at least one more page before the current onenext_num
: page number for the next pageprev_num
: page number for the previous page
Post/Redirect/Get - web design pattern that prevents some duplicate form submissions,c reating a more intuitive interface for user agents
Can use Flask-Mail to send emails
Sending email is going to block the request so it's probably a good idea to use some sort of asynchronous process to managing the sending and receiving of email.
You probably expected that only the msg argument would be sent to the thread, but as you can see in the code, I'm also sending the application instance. When working with threads there is an important design aspect of Flask that needs to be kept in mind. Flask uses contexts to avoid having to pass arguments across functions. I'm not going to go into a lot of detail on this, but know that there are two types 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.
There are many extensions that 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 because they have their configuration stored in the app.config object. This is exactly the situation with Flask-Mail. The mail.send() method needs to access the configuration values for the email server, and that can only be done by knowing what the application is. The application context that is created with the with app.app_context() call makes the application instance accessible via the current_app variable from Flask.
From docs:
- While a request is active, the context local objects (
flask.request
and others) point to the current request
The main reason for the application’s context existence is that in the past a bunch of functionality was attached to the request context for lack of a better solution. Since one of the pillars of Flask’s design is that you can have more than one application in the same Python process.
-
never moves between threads and is not shared between requests so perfect place to store configuration information
-
typically used to cache resources that need to be created on a per-request or usage case (i.e. database conenctions)
-
_app_ctx_Stack.top
is for Flask and its extensions -
Creating an Application Context:
- Whenever request ontext is pushed, application context will be created if necessary
with app.app_context()
andcurrent_app
points to app in context manager
A compact, self-contained way to securely transmit information between parties as a JSON object.
-
Three components: header, payload, signature.
- Header contains information about the algorithm and token type
- Payload contains claims about the sending entity and additional metadata (standard JWT fields)
- Signature is used to verify that the sender of the JWT is who they say they are and that the message wasn't changed
-
Anybody can read the header and payload so make sure to encrypt the payload as needed.
- CSS Frameworks make everybody a designer. Bootstrap is good enough for backend developers
- Flask-Bootstrap provides base template that allows us to extend blocks
An area where Flask-Bootstrap does a fantastic job is in rendering of 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.
{% import 'bootstrap/wtf.html' as wtf %}
and{{ wtf.quick_form(form) }}
- always use UTC time in database
- browser has information about user's timezone, datetime format, etc so let's use JavaScript to show the right time
- Flask-Moment is a wrapper around moment.js which lets us display times in the user's local timezone
- We have a very English-centric view of the world, but our users can come from anywhere. Use Flask-Babel to support
Accept-Language
header can be used to give a list of preferred languages with weights based on preferences (set in browser)- Flask Babel's
best_match()
can be used to figure out what language the user prefers
The normal workflow when making an application available in multiple languages is to mark all the texts that need translations in the source code. After the texts are marked, Flask-Babel will scan all the files and extract those texts into a separate translation file using the gettext tool. Unfortunately this is a tedious task that needs to be done to enable translations.
The way texts are marked for translation is by wrapping them in a function call that as a convention is called
_()
, just an underscore._()
wraps the base langauge and the function will use the best language selected by the function decorated withlocaleselector
There is a lot more, but don't need to worry about until we do.
- Flask relies on Click for all its command-line operations
@app.cli.group()
def translate():
"""
Translation and localization commands.
"""
pass
Look into Click if we want to do more here