Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add material for the Flask series #472

Merged
merged 6 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions flask-database/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Flask Series: Sample Project

This repository contains the code for the `board` Flask sample project.

## Setup

You can run the provided example project on your local machine by following the steps outlined below.

Create a new virtual environment:

```bash
python3 -m venv venv/
```

Activate the virtual environment:

```bash
source ./venv/bin/activate
```

Navigate to the folder for the step that you're currently on.

Install the dependencies for this project if you haven't installed them yet:

```bash
(venv) $ python -m pip install -r requirements.txt
```

### Environment Variables

This project works with environment variables that the application expects in a `.env` file inside the root directory of your project.

Create a `.env` file with this content:

```
FLASK_SECRET_KEY="mysecretkey"
FLASK_DATABASE="board.sqlite"
```

You can add your own content there, but you must define it before running the Flask application.

#### Secret Key

If you want to deploy your Flask app later, then it's a good idea to generate a proper secret key.

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:

```pycon
>>> import secrets
>>> secrets.token_hex()
'2e9ac41b1e0b66a8d93d66400e2300c4b4c2953f'
```

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:

```
# .env

FLASK_SECRET_KEY="2e9ac41b1e0b66a8d93d66400e2300c4b4c2953f"
FLASK_DATABASE="board.sqlite"

```

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/).

### Database

To initialize the database, run this command:

```bash
(venv) $ python -m flask --app board init-db
```

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.

## Development Server

To run the Flask development server, enter this command in your terminal while being in the root directory of your project:

```bash
(venv) $ python -m flask --app board run --debug
```

Now you can navigate to the address that's shown in the output when you start the server. Commonly, that's `http://localhost:5000/`.
20 changes: 20 additions & 0 deletions flask-database/board/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
from dotenv import load_dotenv
from flask import Flask

from board import pages, database, posts

load_dotenv()


def create_app():
app = Flask(__name__)
app.config.from_prefixed_env()

database.init_app(app)

app.register_blueprint(pages.bp)
app.register_blueprint(posts.bp)
print(f"Current Environment: {os.getenv('ENVIRONMENT')}")
print(f"Using Database: {app.config.get('DATABASE')}")
return app
36 changes: 36 additions & 0 deletions flask-database/board/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sqlite3
import click
from flask import current_app, g


def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)


@click.command("init-db")
def init_db_command():
db = get_db()

with current_app.open_resource("schema.sql") as f:
db.executescript(f.read().decode("utf8"))

click.echo("You successfully initialized the database!")


def get_db():
if "db" not in g:
g.db = sqlite3.connect(
current_app.config["DATABASE"],
detect_types=sqlite3.PARSE_DECLTYPES,
)
g.db.row_factory = sqlite3.Row

return g.db


def close_db(e=None):
db = g.pop("db", None)

if db is not None:
db.close()
13 changes: 13 additions & 0 deletions flask-database/board/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from flask import Blueprint, render_template

bp = Blueprint("pages", __name__)


@bp.route("/")
def home():
return render_template("pages/home.html")


@bp.route("/about")
def about():
return render_template("pages/about.html")
38 changes: 38 additions & 0 deletions flask-database/board/posts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from flask import (
Blueprint,
redirect,
render_template,
request,
url_for,
)

from board.database import get_db

bp = Blueprint("posts", __name__)


@bp.route("/create", methods=("GET", "POST"))
def create():
if request.method == "POST":
author = request.form["author"] or "Anonymous"
message = request.form["message"]

if message:
db = get_db()
db.execute(
"INSERT INTO post (author, message) VALUES (?, ?)",
(author, message),
)
db.commit()
return redirect(url_for("posts.posts"))

return render_template("posts/create.html")


@bp.route("/posts")
def posts():
db = get_db()
posts = db.execute(
"SELECT author, message, created FROM post ORDER BY created DESC"
).fetchall()
return render_template("posts/posts.html", posts=posts)
8 changes: 8 additions & 0 deletions flask-database/board/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS post;

CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
author TEXT NOT NULL,
message TEXT NOT NULL
);
86 changes: 86 additions & 0 deletions flask-database/board/static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
* {
box-sizing: border-box;
}

body {
font-family: sans-serif;
font-size: 20px;
margin: 0 auto;
text-align: center;
}

a,
a:visited {
color: #007BFF;
}

a:hover {
color: #0056b3;
}

nav ul {
list-style-type: none;
padding: 0;
}

nav ul li {
display: inline;
margin: 0 5px;
}

main {
width: 80%;
margin: 0 auto;
}

article, form {
text-align: left;
min-width: 200px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0px 0px 10px #ccc;
vertical-align: top;
}

aside {
color: #ccc;
text-align: right;
}

form {
display: flex;
flex-direction: column;
margin-top: 20px;
}

.form-group {
margin-bottom: 20px;
}

.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
font-size: 1em;
}

.submit-btn {
background-color: #007BFF;
color: #fff;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
font-size: 1em;
}

.submit-btn:hover {
background-color: #0056b3;
}

.message {
font-size: 2.5em;
font-family: serif;
margin: 0;
padding: 0;
}
8 changes: 8 additions & 0 deletions flask-database/board/templates/_navigation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<nav>
<ul>
<li><a href="{{ url_for('pages.home') }}">Home</a></li>
<li><a href="{{ url_for('pages.about') }}">About</a></li>
<li><a href="{{ url_for('posts.posts') }}">All Posts</a></li>
<li><a href="{{ url_for('posts.create') }}">Add Post</a></li>
</ul>
</nav>
19 changes: 19 additions & 0 deletions flask-database/board/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Message Board - {% block title %}{% endblock title %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<h1>Message Board</h1>
{% include("_navigation.html") %}
<section>
<header>
{% block header %}{% endblock header %}
</header>
<main>
{% block content %}<p>No messages.</p>{% endblock content %}
<main>
</section>
</body>
</html>
9 changes: 9 additions & 0 deletions flask-database/board/templates/pages/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends 'base.html' %}

{% block header %}
<h2>{% block title %}About{% endblock title %}</h2>
{% endblock header %}

{% block content %}
<p>This is a message board for friendly messages.</p>
{% endblock content %}
9 changes: 9 additions & 0 deletions flask-database/board/templates/pages/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends 'base.html' %}

{% block header %}
<h2>{% block title %}Home{% endblock title %}</h2>
{% endblock header %}

{% block content %}
<p>Learn more about this project by visiting the <a href="{{ url_for('pages.about') }}">About page</a>.</p>
{% endblock content %}
21 changes: 21 additions & 0 deletions flask-database/board/templates/posts/create.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% extends 'base.html' %}

{% block header %}
<h2>{% block title %}Add Post{% endblock title %}</h2>
{% endblock header %}

{% block content %}
<form method="post">
<div class="form-group">
<label for="author">Your Name</label>
<input type="text" name="author" id="author" value="{{ request.form['author'] }}" class="form-control">
</div>
<div class="form-group">
<label for="message">Your Message</label>
<textarea name="message" id="message" class="form-control">{{ request.form['message'] }}</textarea>
</div>
<div class="form-group">
<input type="submit" value="Post Message" class="submit-btn">
</div>
</form>
{% endblock content %}
14 changes: 14 additions & 0 deletions flask-database/board/templates/posts/posts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends 'base.html' %}

{% block header %}
<h2>{% block title %}Posts{% endblock title %}</h2>
{% endblock header %}

{% block content %}
{% for post in posts %}
<article>
<p class="message">{{ post["message"] }}<p>
<aside>Posted by {{ post["author"] }} on {{ post["created"].strftime("%b %d, %Y at %-I:%M%p") }}</aside>
</article>
{% endfor %}
{% endblock content %}
3 changes: 3 additions & 0 deletions flask-database/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
click==8.1.7
Flask==3.0.0
python-dotenv==1.0.0
Loading
Loading