So, I’ve been upskilling with Python a lot, and, as the rule goes, the best way to learn to code is to code, so I’ve been seeking out some projects to put into place as I continue to develop my Python skills. This is the first such example, which came from the Digital Ocean website. In this example,
1: Setting Up
The first thing that I needed to do was create a virtual environment to do my work in. See this blog post (XXXX) for more info about that. I then activated my virtual environment and installed flask, before checking that it had been installed correctly.
source my_env/bin/activate
pip install Flask
python -c “import flask; print(flask.__version__)”
2: Creating a Base Application
This all went pretty well. The next step was just to set up the base application. The first step was to create a python file called hello.py, with this in it:
from flask import Flask
//import the Flask object from flask package
app = Flask(__name__)
//create a function that returns an HTTP response.
//pass the special variable __name__<- holds the name of the current Python module
//tells the instance where it’s located.
//use the app instance to handle incoming web request and send responses.
//app.route is a decorator – turns a regular Python function into a Flask view function.
//converts the functions return value into an HTTP response
// / responds to / <- main URL.
// hello() function <- returns the string as a response.
@app.route(‘/’)
def hello():
return ‘Hello, World!’
To run this application, I needed to set the app to hello and the environment to development and then run it.
FLASK_APP=hello
FLASK_ENV=development
flask run
This starts the app running, and also tells us where to find it. I can then open that on my browser, and see ‘Hello, World’. I can now leave this terminal window open, and open another one to continue working on my app. However, I noticed that if the server is kept running for a while, the site doesn’t update anymore – and I need to stop and restart the server for the site to update.
3: Creating our Index page
The next step is to start working on the actual app (which will be a blog). To do this, we will create a new app (app.py) and then tell it where to find templates to render the webpage it is serving. The first thing to do is to create a new file called app.py and put this code in it:
from flask import Flask, render_template
app = Flask(__name__)
@app.route(‘/’)
def index():
return render_template(‘index.html’)
This assigns app to the name of our app, and then creates a function called index which calls the function render_template on index.html, and returns the outcome. Render_template is a function that uses template HTML files. These are stored in the templates folder – need to create that.
The @app.route function is a view function that handles requests to anything on the / route.
In templates/index.html, we need to do the following:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<link rel=”stylesheet” href=”{{ url_for(‘static’, filename= ‘css/style.css’) }}”>
<title>FlaskBlog</title>
</head>
<body>
<h1>Welcome to FlaskBlog</h1>
</body>
</html>
We can also create a folder called static, where we can store our CSS files. In this folder, in a file called styles.css, write this:
h1 {
border: 2px #eee solid;
color: brown;
text-align: center;
padding: 10px;
}
Before testing it out, we also need to make sure that we set the right app.
FLASK_APP=app
Now, when we run our app, we should have a styled index page.
4: Using HTML Templates
Of course, this is pretty basic stuff. We want our website to look a little more sophisticated than this, so we will be using Bootstrap. Before we get to that, thought, we need to create a base.html file. This will be where most of the pages (or routes?) will inherit from – to stop having to write repeatable code. This will make use of
- Will be using bootstrap in this project.
- Easy to use components for styling application.
- No need to make another HTML template when we already wrote index.html
- Can avoid necessary code repetition – help of a base template file.
- Template inheritance in Jinja: Template Inheritance in Jinja
- Need to create a file called base..html in templates.
- Mostly code required for HTML and Bootstrap.
- Some are related to the Jinja template engine.
- {% block title %} {% endblock %}: A block that serves as a placeholder for a title, you’ll later use it in other templates to give a custom title for each page in your application without rewriting the entire <head> section each time.
- {{ url_for(‘index’)}}: A function call that will return the URL for the index() view function. This is different from the past url_for() call you used to link a static CSS file, because it only takes one argument, which is the view function’s name, and links to the route associated with the function instead of a static file.
- {% block content %} {% endblock %}: Another block that will be replaced by content depending on the child template (templates that inherit from base.html) that will override it.
- Then paste this in the index.html
{% extends ‘base.html’ %}
{% block content %}
<h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
{% endblock %}
- extends tag – inherits from base.html template.
- Extend it via replacing the content block in the base template with what is inside the content block in the code block.
- Content block – contains a <h1> tag – replaces the original title block in base html.
- Can avoid repeating the same text twice.
5: Setting up the database
- Set up a database – store data – blog posts.
- Populate the database with a few sample entries.
- SQLite database to store data – available in the standard Python library.
- Data in SQLite is stored in table sand columns -> since your data consists of blog posts -> need to create a table called posts with columns.
- Create an .sql file – contains SQL commands -> create the posts table.
- Then use the file to create the database.
- Do this is schema.sql
DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
content TEXT NOT NULL
);
- Drop table – deletes posts if it already exists.
- Create tables -> posts.
- Has the following columns -> id, created, title, content
- Now to create the database using python to generate an SQLite .db database file.
- In init_db.py
import sqlite3
connection = sqlite3.connect(‘database.db’)
with open(‘schema.sql’) as f:
connection.executescript(f.read())
cur = connection.cursor()
cur.execute(“INSERT INTO posts (title, content) VALUES (?, ?)”,
(‘First Post’, ‘Content for the first post’)
)
cur.execute(“INSERT INTO posts (title, content) VALUES (?, ?)”,
(‘Second Post’, ‘Content for the second post’)
)
connection.commit()
connection.close()
- import sqlite3 module.
- Open a connection to database file named database.db
- execute its contents -> multiple SQL statements, create the posts table.
- Create a cursor object, uses its execute method to execute two insert SQL to add two blog posts to your posts table.
- Commit the changes, close the connection.
- Run it: python init_db.py
6: Displaying All Posts
- Now have a database – can modify the index() view function to display all the posts.
- In app.py
import sqlite3
from flask import Flask, render_template
def get_db_connection():
conn = sqlite3.connect(‘database.db’)
conn.row_factory = sqlite3.Row
return conn
@app.route(‘/’)
def index():
conn = get_db_connection()
posts = conn.execute(‘SELECT * FROM posts’).fetchall()
conn.close()
return render_template(‘index.html’, posts=posts)
- Functions that creates a database connection and returns it.
- Returns rows that behave like Python dictionaries.
- Then returns the conn object – used later
- In index(), open the connection, execute an SQL query to select all entries from the posts table.
- fetchall() to get all rows, list of posts.
- close the db connection, and return the result of rendering the index.html template.
- Also posts object as an argument – access the blog posts in the index.html template.
- Can now use index.html -> for loop to display each post on your index page.
{% extends ‘base.html’ %}
{% block content %}
<h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1>
{% for post in posts %}
<a href=”#”>
<h2>{{ post[‘title’] }}</h2>
</a>
<span class=”badge badge-primary”>{{ post[‘created’] }}</span>
<hr>
{% endfor %}
{% endblock %}
7: Displaying A Single Posts
In app.py:
import sqlite3
from flask import Flask, render_template
from werkzeug.exceptions import abort
…
def get_post(post_id):
conn = get_db_connection()
post = conn.execute(‘SELECT * FROM posts WHERE id = ?’,
(post_id,)).fetchone()
conn.close()
if post is None:
abort(404)
return post
…
@app.route(‘/<int:post_id>’)
def post(post_id):
post = get_post(post_id)
return render_template(‘post.html’, post=post)
In templates/post.html
{% extends ‘base.html’ %}
{% block content %}
<h2>{% block title %} {{ post[‘title’] }} {% endblock %}</h2>
<span class=”badge badge-primary”>{{ post[‘created’] }}</span>
<p>{{ post[‘content’] }}</p>
{% endblock %}
In templates/index.html:
{% for post in posts %}
<a href=”{{ url_for(‘post’, post_id=post[‘id’]) }}”>
<h2>{{ post[‘title’] }}</h2>
</a>
<span class=”badge badge-primary”>{{ post[‘created’] }}</span>
<hr>
{% endfor %}
Step 7 Modifying Posts
In app.py:
from flask import Flask, render_template, request, url_for, flash, redirect
…
app.config[‘SECRET_KEY’] = ‘your secret key’
…
@app.route(‘/create’, methods=(‘GET’, ‘POST’))
@app.route(‘/create’, methods=(‘GET’, ‘POST’))
def create():
if request.method == ‘POST’:
title = request.form[‘title’]
content = request.form[‘content’]
if not title:
flash(‘Title is required!’)
else:
conn = get_db_connection()
conn.execute(‘INSERT INTO posts (title, content) VALUES (?, ?)’,
(title, content))
conn.commit()
conn.close()
return redirect(url_for(‘index’))
return render_template(‘create.html’)
In templates/create.html
{% extends ‘base.html’ %}
{% block content %}
<h1>{% block title %} Create a New Post {% endblock %}</h1>
<form method=”post”>
<div class=”form-group”>
<label for=”title”>Title</label>
<input type=”text” name=”title”
placeholder=”Post title” class=”form-control”
value=”{{ request.form[‘title’] }}”></input>
</div>
<div class=”form-group”>
<label for=”content”>Content</label>
<textarea name=”content” placeholder=”Post content”
class=”form-control”>{{ request.form[‘content’] }}</textarea>
</div>
<div class=”form-group”>
<button type=”submit” class=”btn btn-primary”>Submit</button>
</div>
</form>
{% endblock %}
And add a list item, too in the templates/base.html:
<li class=”nav-item”>
<a class=”nav-link” href=”{{url_for(‘create’)}}”>New Post</a>
</li>
…
<div class=”container”>
{% for message in get_flashed_messages() %}
<div class=”alert alert-danger”>{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>
And a similar process for editing a post and deleting a post.
Full code is on Github.
Next Steps
While this is quite a nifty application, there’s really only a limited application of Python here – it’s more about Flask and SQL – both of which are good, too. However, I have been talking for a while about creating a kind of online To Do list – this could potentially do the job.
I also need to work out how to go form the development environment to the production one, and perhaps how I might add a password to this site, as well as making it publicly available.