Getting Started with Flask: Part 2

By Michael

Welcome to part 2 of my getting started guide. If you missed part 1 you can find it here.

Install XAMPP

We left off with a very static website. Nothing fun going on there yet so lets change that. You'll need to install XAMPP and get it set up. Make sure you can run the Apache and MySQL services. When you have everything going (you may need to run as Administrator) click Admin in the MySQL row to launch phpMyAdmin

Add a user from the User Accounts tab. Make sure to check "Create database with same name and grant all privliges." so we can do what we need. If you run into privliges issues this is where to check later.

Flask Migrate and MySQL Client

Next we will need to install Flask-Migrate via pip: `pip install Flask-Migrate mysqlclient` then edit our `app.py` to include the import and database connection

from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://[username]:[password]@localhost/[database]'

Models and Database Migrations

Before we can do anything with that we need to create our tables and models. Start by editing `app.py` with the following changes. Note we've renamed the `hello_world()` route to `index()` and removed the `lucky_number` variable. I've included a short comment on new components, if you need to know more consult the docs for Declaring Models and Flask Migrate

import random
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://[username]:[password]@localhost/[database]'

db = SQLAlchemy(app)
migrate = Migrate(app, db)

@app.route('/')
def index():
	return render_template('index.html')

@app.route('/about')
def about():
	return render_template('about.html')

class User(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	name = db.Column(db.String(128))
	
class Post(db.Model):
	id = db.Column(db.Integer, primary_key=True) # This is our primary key used by SQL Alchemy to update the tables
	name = db.Column(db.String(256)) # This will be our title, db.String(265) sets a varchar type column with a lenlth of 256 characters
	slug = db.Column(db.String(256)) # This will be the URL that we will use to find our post http://127.0.0.1:5000/blog/ 
	body = db.Column(db.Text) # This is our post's body. db.Text gives us a long text column to use.
	user_id = db.Column(db.Integer) # This is how we know who wrote the post.
	
if __name__ == '__main__':
	app.run()

Migrate Our Changes

Now in the console type the following commands. I've ommited the output to reduce clutter that will be on this page but be sure to check each step is successful.

Windows
# (venv) C:\Users\aipiggy\Documents\blog> flask db init
# (venv) C:\Users\aipiggy\Documents\blog> flask db migrate -m "First migration"
# (venv) C:\Users\aipiggy\Documents\blog> flask db upgrade

The first command created a folder `migrations` with files that flask needs to create the database as needed. The `flask db migrate -m "First migration"` command created a the base for what is needed to create the tables while the last `flask db upgrade` writes the changes to the database.

Now we have a Post and User table. Lets use phpMyAdmin to put in some data to work with.

phpMyAdmin

First lets open phpMyAdmin if you haven't already. Navigate to your database and open up the user table we created and insert your name into the table.

Next we will enter a post. I've created one with the slug 'first', title of 'My First Post', and body of 'This is my first post. WooHoo!'

Update our views

Now we need a way to view the post and a way to navigate to them. Lets add `post.html` to our templates folder and add the following to the file.

{% extends "layout.html" %}

{% block content %}

	<h1<{{ post.name }}</h1<
	<p<{{ post.body|safe }}</p<
	
{% endblock %}

In `layout.html` lets add the following to make the container look like this

<div class="container">
	<div class="row">
		<div class="col-lg-9">
		{% block content%}
		{% endblock %}
		</div>
		<div class="col-lg-3 d-md-hide d-lg-block">
			<ul>
				{% for post in posts %}
					<li><a href="/blog/{{ post.slug }}">{{ post.name }}</a></li>
				{% else %}
					<li>None</li>
				{% endfor %}
				<li><a href="/getting_started_part_2">Getting Started Part 2</a></li>
			</ul>
		</div>
	</div>
</div>

This will give us a list of posts on the right column of all pages that use `layout.html` as the base. Now lets update our `app.py` to provide the information we need.

First we will give our code the `posts` object to every page. We could do that manually but Flask provides us a nice and easy way to pass an object to all routes. Put the following anywhere befor our __name__ = '__name__' line.

@app.context_processor
def inject_posts():
    posts = Post.query.with_entities(Post.name, Post.slug).order_by(Post.id.desc()).limit(5)
    return dict(posts=posts)

SQLAlchemy Magic

Now is a good time to explain what is going on here with the query. Through some magic SQLAlchemy is interpreting our Post.query statement and translating it into a SQL statement which it returns an itterable dictionary object.

The `with_entities` of `Post.query.with_entities(Post.name, Post.slug).order_by(Post.id.desc()).limit(5)` is telling the statement we only want those columns. `.order_by(Post.id.desc())` orders the results by Post id in a decending order.`.limit(5)` gets 5 records .

Blog View

Now lets create `templates/blog.html` and open it up. Put the following code inside.

{% extends "layout.html" %}

{% block content %}

	<h1>{{ post.name }}</h1>
	<p>{{ post.body|safe }}</p>

{% endblock %}

There is something new here. `post.body|safe` displays what is inside `post.body` no matter what is inside. Leaving this decorator off would prevent HTML from being rendered from the database.

Lets create some endpoints so users can see our posts. Open up `app.py` and add the following somewhere before the model classes.

@app.route('/blog', defaults={"slug":None})
@app.route('/blog/≶slug>')
def blog(slug):
    if slug is None:
        return render_template("index.html")
    else:
        post = Post.query.filter_by(slug=slug).first()
        return render_template("blog.html", post=post)

We have 2 routes stacked on each other here. The first one will intercept http://127.0.0.1:5000/blog and show the index page. The second will take the token after /blog/<slug> and use that to find the Post object we want. After finding it it will pass the post to the template for display.

From here we should be able to run and visit our first post at http://127.0.0.1:5000/blog/first. That works well but we will have a problem if the user tries to go to anything ohter than /blog/first. Lets create an error handler for 404 Not Found and make an adjustment to our route, ammend `app.py` like so.

@app.route('/blog', defaults={"slug":None})
@app.route('/blog/')
def blog(slug):
    if slug is None:
        return render_template("index.html")
    else:
        post = Post.query.filter_by(slug=slug).first()
        if post is not None:
            return render_template("blog.html", post=post)
        else:
            abort(404)

@app.errorhandler(404)
def page_not_found(e):
    return render_template("404.html"), 404

And stick this before `app.run()`

	app.register_error_handler(404, page_not_found)

We need to create a template for this now. Create `templates/404.html` and add the following code.

{% extends "layout.html" %}

{% block content %}

	<h1>404 Not Found</h1>
	<p>Not sure what you're looking for there bud.</p>

{% endblock %}

Try to visit http://127.0.0.1:5000/blog/second and you should be greeted with a friendly 404 page.

Welcome to part 2 of my getting started guide. If you missed part 1 you can find it here.

Install XAMPP

We left off with a very static website. Nothing fun going on there yet so lets change that. You'll need to install XAMPP and get it set up. Make sure you can run the Apache and MySQL services. When you have everything going (you may need to run as Administrator) click Admin in the MySQL row to launch phpMyAdmin

Add a user from the User Accounts tab. Make sure to check "Create database with same name and grant all privliges." so we can do what we need. If you run into privliges issues this is where to check later.

Flask Migrate and MySQL Client

Next we will need to install Flask-Migrate via pip: `pip install Flask-Migrate mysqlclient` then edit our `app.py` to include the import and database connection

from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://[username]:[password]@localhost/[database]'

Models and Database Migrations

Before we can do anything with that we need to create our tables and models. Start by editing `app.py` with the following changes. Note we've renamed the `hello_world()` route to `index()` and removed the `lucky_number` variable. I've included a short comment on new components, if you need to know more consult the docs for Declaring Models and Flask Migrate

import random
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://[username]:[password]@localhost/[database]'

db = SQLAlchemy(app)
migrate = Migrate(app, db)

@app.route('/')
def index():
	return render_template('index.html')

@app.route('/about')
def about():
	return render_template('about.html')

class User(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	name = db.Column(db.String(128))
	
class Post(db.Model):
	id = db.Column(db.Integer, primary_key=True) # This is our primary key used by SQL Alchemy to update the tables
	name = db.Column(db.String(256)) # This will be our title, db.String(265) sets a varchar type column with a lenlth of 256 characters
	slug = db.Column(db.String(256)) # This will be the URL that we will use to find our post http://127.0.0.1:5000/blog/ 
	body = db.Column(db.Text) # This is our post's body. db.Text gives us a long text column to use.
	user_id = db.Column(db.Integer) # This is how we know who wrote the post.
	
if __name__ == '__main__':
	app.run()

Migrate Our Changes

Now in the console type the following commands. I've ommited the output to reduce clutter that will be on this page but be sure to check each step is successful.

Windows
# (venv) C:\Users\aipiggy\Documents\blog> flask db init
# (venv) C:\Users\aipiggy\Documents\blog> flask db migrate -m "First migration"
# (venv) C:\Users\aipiggy\Documents\blog> flask db upgrade

The first command created a folder `migrations` with files that flask needs to create the database as needed. The `flask db migrate -m "First migration"` command created a the base for what is needed to create the tables while the last `flask db upgrade` writes the changes to the database.

Now we have a Post and User table. Lets use phpMyAdmin to put in some data to work with.

phpMyAdmin

First lets open phpMyAdmin if you haven't already. Navigate to your database and open up the user table we created and insert your name into the table.

Next we will enter a post. I've created one with the slug 'first', title of 'My First Post', and body of 'This is my first post. WooHoo!'

Update our views

Now we need a way to view the post and a way to navigate to them. Lets add `post.html` to our templates folder and add the following to the file.

{% extends "layout.html" %}

{% block content %}

	<h1<{{ post.name }}</h1<
	<p<{{ post.body|safe }}</p<
	
{% endblock %}

In `layout.html` lets add the following to make the container look like this

<div class="container">
	<div class="row">
		<div class="col-lg-9">
		{% block content%}
		{% endblock %}
		</div>
		<div class="col-lg-3 d-md-hide d-lg-block">
			<ul>
				{% for post in posts %}
					<li><a href="/blog/{{ post.slug }}">{{ post.name }}</a></li>
				{% else %}
					<li>None</li>
				{% endfor %}
				<li><a href="/getting_started_part_2">Getting Started Part 2</a></li>
			</ul>
		</div>
	</div>
</div>

This will give us a list of posts on the right column of all pages that use `layout.html` as the base. Now lets update our `app.py` to provide the information we need.

First we will give our code the `posts` object to every page. We could do that manually but Flask provides us a nice and easy way to pass an object to all routes. Put the following anywhere befor our __name__ = '__name__' line.

@app.context_processor
def inject_posts():
    posts = Post.query.with_entities(Post.name, Post.slug).order_by(Post.id.desc()).limit(5)
    return dict(posts=posts)

SQLAlchemy Magic

Now is a good time to explain what is going on here with the query. Through some magic SQLAlchemy is interpreting our Post.query statement and translating it into a SQL statement which it returns an itterable dictionary object.

The `with_entities` of `Post.query.with_entities(Post.name, Post.slug).order_by(Post.id.desc()).limit(5)` is telling the statement we only want those columns. `.order_by(Post.id.desc())` orders the results by Post id in a decending order.`.limit(5)` gets 5 records .

Blog View

Now lets create `templates/blog.html` and open it up. Put the following code inside.

{% extends "layout.html" %}

{% block content %}

	<h1>{{ post.name }}</h1>
	<p>{{ post.body|safe }}</p>

{% endblock %}

There is something new here. `post.body|safe` displays what is inside `post.body` no matter what is inside. Leaving this decorator off would prevent HTML from being rendered from the database.

Lets create some endpoints so users can see our posts. Open up `app.py` and add the following somewhere before the model classes.

@app.route('/blog', defaults={"slug":None})
@app.route('/blog/≶slug>')
def blog(slug):
    if slug is None:
        return render_template("index.html")
    else:
        post = Post.query.filter_by(slug=slug).first()
        return render_template("blog.html", post=post)

We have 2 routes stacked on each other here. The first one will intercept http://127.0.0.1:5000/blog and show the index page. The second will take the token after /blog/<slug> and use that to find the Post object we want. After finding it it will pass the post to the template for display.

From here we should be able to run and visit our first post at http://127.0.0.1:5000/blog/first. That works well but we will have a problem if the user tries to go to anything ohter than /blog/first. Lets create an error handler for 404 Not Found and make an adjustment to our route, ammend `app.py` like so.

from flask import Flask, render_template, abort
	...
	
@app.route('/blog', defaults={"slug":None})
@app.route('/blog/')
def blog(slug):
    if slug is None:
        return render_template("index.html")
    else:
        post = Post.query.filter_by(slug=slug).first()
        if post is not None:
            return render_template("blog.html", post=post)
        else:
            abort(404)

@app.errorhandler(404)
def page_not_found(e):
    return render_template("404.html"), 404

And stick this before `app.run()`

	app.register_error_handler(404, page_not_found)

We need to create a template for this now. Create `templates/404.html` and add the following code.

{% extends "layout.html" %}

{% block content %}

	<h1>404 Not Found</h1>
	<p>Not sure what you're looking for there bud.</p>

{% endblock %}

Try to visit http://127.0.0.1:5000/blog/second and you should be greeted with a friendly 404 page.

Other posts by Michael

Getting Started with Flask: Part 5

In our final installment we will set up Apache and WSGI to make our app run in a production environment.

Getting Started with Flask: Part 3

Admin page and authentication!