Skip to content

Commit fe67c99

Browse files
acsanyKateFinegan
andauthored
Add material for the Flask series (#472)
* Add material for the Flask series * Add block name to endblock tags * Fix headline levels * Remove database section from readme of part 1 * Renamed folders to match slugs --------- Co-authored-by: KateFinegan <[email protected]> Co-authored-by: KateFinegan <[email protected]>
1 parent d7defbe commit fe67c99

39 files changed

+962
-0
lines changed

flask-database/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Flask Series: Sample Project
2+
3+
This repository contains the code for the `board` Flask sample project.
4+
5+
## Setup
6+
7+
You can run the provided example project on your local machine by following the steps outlined below.
8+
9+
Create a new virtual environment:
10+
11+
```bash
12+
python3 -m venv venv/
13+
```
14+
15+
Activate the virtual environment:
16+
17+
```bash
18+
source ./venv/bin/activate
19+
```
20+
21+
Navigate to the folder for the step that you're currently on.
22+
23+
Install the dependencies for this project if you haven't installed them yet:
24+
25+
```bash
26+
(venv) $ python -m pip install -r requirements.txt
27+
```
28+
29+
### Environment Variables
30+
31+
This project works with environment variables that the application expects in a `.env` file inside the root directory of your project.
32+
33+
Create a `.env` file with this content:
34+
35+
```
36+
FLASK_SECRET_KEY="mysecretkey"
37+
FLASK_DATABASE="board.sqlite"
38+
```
39+
40+
You can add your own content there, but you must define it before running the Flask application.
41+
42+
#### Secret Key
43+
44+
If you want to deploy your Flask app later, then it's a good idea to generate a proper secret key.
45+
46+
If you need to create cryptographically sound data like a Flask secret key, then you can use Python's [`secrets`](https://docs.python.org/3/library/secrets.html) module:
47+
48+
```pycon
49+
>>> import secrets
50+
>>> secrets.token_hex()
51+
'2e9ac41b1e0b66a8d93d66400e2300c4b4c2953f'
52+
```
53+
54+
The `.token_hex()` method returns a [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) string containing random numbers and letters from `0` to `9` and `a` to `f`. Use the value that `secrets.token_hex()` outputs for you and add it to your Flask project's `.env` file:
55+
56+
```
57+
# .env
58+
59+
FLASK_SECRET_KEY="2e9ac41b1e0b66a8d93d66400e2300c4b4c2953f"
60+
FLASK_DATABASE="board.sqlite"
61+
62+
```
63+
64+
To avoid saving the secret key directly in your code, it may be a good idea to work with [environment variables](https://12factor.net/config). You can learn more about that in the Flask documentation on [configuration handling](https://flask.palletsprojects.com/en/2.3.x/config/).
65+
66+
### Database
67+
68+
To initialize the database, run this command:
69+
70+
```bash
71+
(venv) $ python -m flask --app board init-db
72+
```
73+
74+
If you used the content for the `.env` file from above, then you can find a `board.sqlite` database in the root directory of your project.
75+
76+
## Development Server
77+
78+
To run the Flask development server, enter this command in your terminal while being in the root directory of your project:
79+
80+
```bash
81+
(venv) $ python -m flask --app board run --debug
82+
```
83+
84+
Now you can navigate to the address that's shown in the output when you start the server. Commonly, that's `http://localhost:5000/`.

flask-database/board/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
from dotenv import load_dotenv
3+
from flask import Flask
4+
5+
from board import pages, database, posts
6+
7+
load_dotenv()
8+
9+
10+
def create_app():
11+
app = Flask(__name__)
12+
app.config.from_prefixed_env()
13+
14+
database.init_app(app)
15+
16+
app.register_blueprint(pages.bp)
17+
app.register_blueprint(posts.bp)
18+
print(f"Current Environment: {os.getenv('ENVIRONMENT')}")
19+
print(f"Using Database: {app.config.get('DATABASE')}")
20+
return app

flask-database/board/database.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import sqlite3
2+
import click
3+
from flask import current_app, g
4+
5+
6+
def init_app(app):
7+
app.teardown_appcontext(close_db)
8+
app.cli.add_command(init_db_command)
9+
10+
11+
@click.command("init-db")
12+
def init_db_command():
13+
db = get_db()
14+
15+
with current_app.open_resource("schema.sql") as f:
16+
db.executescript(f.read().decode("utf8"))
17+
18+
click.echo("You successfully initialized the database!")
19+
20+
21+
def get_db():
22+
if "db" not in g:
23+
g.db = sqlite3.connect(
24+
current_app.config["DATABASE"],
25+
detect_types=sqlite3.PARSE_DECLTYPES,
26+
)
27+
g.db.row_factory = sqlite3.Row
28+
29+
return g.db
30+
31+
32+
def close_db(e=None):
33+
db = g.pop("db", None)
34+
35+
if db is not None:
36+
db.close()

flask-database/board/pages.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from flask import Blueprint, render_template
2+
3+
bp = Blueprint("pages", __name__)
4+
5+
6+
@bp.route("/")
7+
def home():
8+
return render_template("pages/home.html")
9+
10+
11+
@bp.route("/about")
12+
def about():
13+
return render_template("pages/about.html")

flask-database/board/posts.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from flask import (
2+
Blueprint,
3+
redirect,
4+
render_template,
5+
request,
6+
url_for,
7+
)
8+
9+
from board.database import get_db
10+
11+
bp = Blueprint("posts", __name__)
12+
13+
14+
@bp.route("/create", methods=("GET", "POST"))
15+
def create():
16+
if request.method == "POST":
17+
author = request.form["author"] or "Anonymous"
18+
message = request.form["message"]
19+
20+
if message:
21+
db = get_db()
22+
db.execute(
23+
"INSERT INTO post (author, message) VALUES (?, ?)",
24+
(author, message),
25+
)
26+
db.commit()
27+
return redirect(url_for("posts.posts"))
28+
29+
return render_template("posts/create.html")
30+
31+
32+
@bp.route("/posts")
33+
def posts():
34+
db = get_db()
35+
posts = db.execute(
36+
"SELECT author, message, created FROM post ORDER BY created DESC"
37+
).fetchall()
38+
return render_template("posts/posts.html", posts=posts)

flask-database/board/schema.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DROP TABLE IF EXISTS post;
2+
3+
CREATE TABLE post (
4+
id INTEGER PRIMARY KEY AUTOINCREMENT,
5+
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
6+
author TEXT NOT NULL,
7+
message TEXT NOT NULL
8+
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
body {
6+
font-family: sans-serif;
7+
font-size: 20px;
8+
margin: 0 auto;
9+
text-align: center;
10+
}
11+
12+
a,
13+
a:visited {
14+
color: #007BFF;
15+
}
16+
17+
a:hover {
18+
color: #0056b3;
19+
}
20+
21+
nav ul {
22+
list-style-type: none;
23+
padding: 0;
24+
}
25+
26+
nav ul li {
27+
display: inline;
28+
margin: 0 5px;
29+
}
30+
31+
main {
32+
width: 80%;
33+
margin: 0 auto;
34+
}
35+
36+
article, form {
37+
text-align: left;
38+
min-width: 200px;
39+
padding: 20px;
40+
margin-bottom: 20px;
41+
box-shadow: 0px 0px 10px #ccc;
42+
vertical-align: top;
43+
}
44+
45+
aside {
46+
color: #ccc;
47+
text-align: right;
48+
}
49+
50+
form {
51+
display: flex;
52+
flex-direction: column;
53+
margin-top: 20px;
54+
}
55+
56+
.form-group {
57+
margin-bottom: 20px;
58+
}
59+
60+
.form-control {
61+
width: 100%;
62+
padding: 10px;
63+
border: 1px solid #ccc;
64+
font-size: 1em;
65+
}
66+
67+
.submit-btn {
68+
background-color: #007BFF;
69+
color: #fff;
70+
border: none;
71+
border-radius: 4px;
72+
padding: 10px 20px;
73+
cursor: pointer;
74+
font-size: 1em;
75+
}
76+
77+
.submit-btn:hover {
78+
background-color: #0056b3;
79+
}
80+
81+
.message {
82+
font-size: 2.5em;
83+
font-family: serif;
84+
margin: 0;
85+
padding: 0;
86+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<nav>
2+
<ul>
3+
<li><a href="{{ url_for('pages.home') }}">Home</a></li>
4+
<li><a href="{{ url_for('pages.about') }}">About</a></li>
5+
<li><a href="{{ url_for('posts.posts') }}">All Posts</a></li>
6+
<li><a href="{{ url_for('posts.create') }}">Add Post</a></li>
7+
</ul>
8+
</nav>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Message Board - {% block title %}{% endblock title %}</title>
5+
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
6+
</head>
7+
<body>
8+
<h1>Message Board</h1>
9+
{% include("_navigation.html") %}
10+
<section>
11+
<header>
12+
{% block header %}{% endblock header %}
13+
</header>
14+
<main>
15+
{% block content %}<p>No messages.</p>{% endblock content %}
16+
<main>
17+
</section>
18+
</body>
19+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h2>{% block title %}About{% endblock title %}</h2>
5+
{% endblock header %}
6+
7+
{% block content %}
8+
<p>This is a message board for friendly messages.</p>
9+
{% endblock content %}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h2>{% block title %}Home{% endblock title %}</h2>
5+
{% endblock header %}
6+
7+
{% block content %}
8+
<p>Learn more about this project by visiting the <a href="{{ url_for('pages.about') }}">About page</a>.</p>
9+
{% endblock content %}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h2>{% block title %}Add Post{% endblock title %}</h2>
5+
{% endblock header %}
6+
7+
{% block content %}
8+
<form method="post">
9+
<div class="form-group">
10+
<label for="author">Your Name</label>
11+
<input type="text" name="author" id="author" value="{{ request.form['author'] }}" class="form-control">
12+
</div>
13+
<div class="form-group">
14+
<label for="message">Your Message</label>
15+
<textarea name="message" id="message" class="form-control">{{ request.form['message'] }}</textarea>
16+
</div>
17+
<div class="form-group">
18+
<input type="submit" value="Post Message" class="submit-btn">
19+
</div>
20+
</form>
21+
{% endblock content %}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends 'base.html' %}
2+
3+
{% block header %}
4+
<h2>{% block title %}Posts{% endblock title %}</h2>
5+
{% endblock header %}
6+
7+
{% block content %}
8+
{% for post in posts %}
9+
<article>
10+
<p class="message">{{ post["message"] }}<p>
11+
<aside>Posted by {{ post["author"] }} on {{ post["created"].strftime("%b %d, %Y at %-I:%M%p") }}</aside>
12+
</article>
13+
{% endfor %}
14+
{% endblock content %}

flask-database/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
click==8.1.7
2+
Flask==3.0.0
3+
python-dotenv==1.0.0

0 commit comments

Comments
 (0)