Skip to content

Commit

Permalink
Add notebook example
Browse files Browse the repository at this point in the history
  • Loading branch information
greyli committed Mar 10, 2023
1 parent e3b3793 commit 517074c
Show file tree
Hide file tree
Showing 10 changed files with 445 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/notebook/.flaskenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FLASK_DEBUG=1
# only enable this when testing
# DATABASE_URL='sqlite:///:memory:'
132 changes: 132 additions & 0 deletions examples/notebook/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import os
import sys
from datetime import datetime

import click
from flask import Flask, redirect, url_for, render_template, flash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import select, Column, Integer, String, Text, DateTime
from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField, TextAreaField
from wtforms.validators import DataRequired, Length
from faker import Faker


# SQLite URI compatible
prefix = 'sqlite:///' if sys.platform.startswith('win') else 'sqlite:////'


app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'secret string')
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
'DATABASE_URL', prefix + os.path.join(app.root_path, 'data.db')
)
db = SQLAlchemy(app)


# Models
class Note(db.Model):
id = Column(Integer, primary_key=True)
title = Column(String(50))
body = Column(Text)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, onupdate=datetime.now)

def __repr__(self):
return f'<Note {self.id}: {self.title}>' # pragma: no cover


# Callbacks
@app.shell_context_processor
def make_shell_context():
return dict(db=db, Note=Note) # pragma: no cover


# Commands
@app.cli.command()
@click.option('--drop-table', is_flag=True, help='Re-create the tables.')
def init(drop_table):
"""Initialize the application."""
if drop_table:
click.confirm(
'This operation will delete the tables, do you want to continue?',
abort=True
)
db.drop_all()
click.echo('Dropped tables.')
db.create_all()
click.echo('Initialized.')


@app.cli.command()
@click.option('--count', default=20, help='Quantity of notes, default is 20.')
def fake(count):
"""Generate fake data."""
db.drop_all()
db.create_all()

fake = Faker()

for _ in range(count):
note = Note(
title=fake.sentence(5),
body=fake.text(200),
created_at=fake.date_time_this_year(),
)
db.session.add(note)

db.session.commit()
click.echo(f'Created {count} notes.')


# Forms
class NoteForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(1, 50)])
body = TextAreaField('Body', validators=[DataRequired()])
submit = SubmitField()


# Views
@app.route('/')
def index():
notes = db.session.execute(select(Note)).scalars().all()
return render_template('index.html', notes=notes)


@app.route('/new', methods=['GET', 'POST'])
def new_note():
form = NoteForm()
if form.validate_on_submit():
title = form.title.data
body = form.body.data
note = Note(title=title, body=body)
db.session.add(note)
db.session.commit()
flash('Note saved.')
return redirect(url_for('index'))
return render_template('new_note.html', form=form)


@app.route('/edit/<int:note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
form = NoteForm()
note = db.session.get(Note, note_id)
if form.validate_on_submit():
note.title = form.title.data
note.body = form.body.data
db.session.commit()
flash('Note updated.')
return redirect(url_for('index'))
# Pre-fill form
form.title.data = note.title
form.body.data = note.body
return render_template('edit_note.html', form=form)


@app.route('/delete/<int:note_id>', methods=['POST'])
def delete_note(note_id):
note = db.session.get(Note, note_id)
db.session.delete(note)
db.session.commit()
flash('Note deleted.')
return redirect(url_for('index'))
Binary file added examples/notebook/static/favicon.ico
Binary file not shown.
88 changes: 88 additions & 0 deletions examples/notebook/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
body {
margin: auto;
width: 750px;
}

nav ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}

nav li {
float: left;
}

nav li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}

nav li a:hover {
background-color: #111;
}

main {
padding: 10px 20px;
}

.alert {
position: relative;
padding: 0.75rem 1.25rem;
margin-bottom: 1rem;
border: 1px solid transparent;
border-radius: 0.25rem;
color: #004085;
background-color: #cce5ff;
border-color: #b8daff;
}

.error {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}

.note {
padding: 5px 10px 10px;
border-left: solid 2px #bbb;
margin-bottom: 20px;
}

.note form {
display: inline;
}

.info {
color: grey;
}

.btn {
font-family: Arial;
font-size: 12px;
padding: 5px 8px;
text-decoration: none;
cursor: pointer;
background-color: white;
color: black;
border: 1px solid #999999;
}

.btn:hover {
text-decoration: none;
border: 1px solid black;
}

footer {
font-size: 13px;
color: #888;
border-top: 1px solid #eee;
margin-top: 25px;
text-align: center;
padding: 20px;
}
13 changes: 13 additions & 0 deletions examples/notebook/templates/_macros.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% macro form_field(field) %}
{{ field.label }}<br>
{% if field.flags.required %}
{{ field(required='required', **kwargs) }}<br>
{% else %}
{{ field(**kwargs) }}<br>
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<small class="error">{{ error }}</small><br>
{% endfor %}
{% endif %}
{% endmacro %}
34 changes: 34 additions & 0 deletions examples/notebook/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
{% block head %}
<meta charset="utf-8">
<title>{% block title %}Notebook{% endblock %}</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
{% block styles %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}">
{% endblock %}
{% endblock %}
</head>
<body>
<nav>
<ul>
<li><a href="{{ url_for('index') }}">Notebook</a></li>
</ul>
</nav>
<main>
{% for message in get_flashed_messages() %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
<small> &copy; 2023 <a href="https://greyli.com">Grey Li</a> /
<a href="https://github.com/greyli/helloflask">GitHub</a> /
<a href="https://helloflask.com">HelloFlask</a>
</small>
{% endblock %}
</footer>
</body>
</html>
15 changes: 15 additions & 0 deletions examples/notebook/templates/edit_note.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% from '_macros.html' import form_field %}

{% block title %}Edit Note{% endblock %}

{% block content %}
<h3>Edit Note</h3>

<form method="post">
{{ form.csrf_token }}
{{ form_field(form.title) }}
{{ form_field(form.body, rows=5, cols=50) }}
{{ form.submit }}<br>
</form>
{% endblock %}
19 changes: 19 additions & 0 deletions examples/notebook/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends 'base.html' %}

{% block content %}
<h3>{{ notes|length }} Notes (<a href="{{ url_for('new_note') }}">new</a>):</h3>
{% for note in notes %}
<div class="note">
<strong>{{ note.title }}</strong>
<p>{{ note.body }}</p>
<small class="info">Created at: {{ note.created_at.strftime('%Y/%m/%d %H:%M') }}</small>
{% if note.updated_at %}
<small class="info">Updated at: {{ note.updated_at.strftime('%Y/%m/%d %H:%M') }}</small>
{% endif %}
<a class='btn' href="{{ url_for('edit_note', note_id=note.id) }}">Edit</a>
<form method="post" action="{{ url_for('delete_note', note_id=note.id) }}">
<input class="btn" type="submit" value="Delete" onclick="return confirm('Are you sure?')">
</form>
</div>
{% endfor %}
{% endblock %}
15 changes: 15 additions & 0 deletions examples/notebook/templates/new_note.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% from '_macros.html' import form_field %}

{% block title %}New Note{% endblock %}

{% block content %}
<h3>New Note</h3>

<form method="post">
{{ form.csrf_token }}
{{ form_field(form.title) }}
{{ form_field(form.body, rows=5, cols=50) }}
{{ form.submit }}<br>
</form>
{% endblock %}
Loading

0 comments on commit 517074c

Please sign in to comment.