From fc1af0edf28fb78d264cc007790f5ca9476d7693 Mon Sep 17 00:00:00 2001 From: Mike Shoup Date: Thu, 1 Nov 2018 19:53:30 -0600 Subject: [PATCH] things mostly work --- flaskr/__init__.py | 7 +++ flaskr/auth.py | 90 +++++++++++++++++++++++++++ flaskr/blog.py | 96 +++++++++++++++++++++++++++++ flaskr/static/style.css | 26 ++++++++ flaskr/templates/auth/login.html | 15 +++++ flaskr/templates/auth/register.html | 15 +++++ flaskr/templates/base.html | 24 ++++++++ flaskr/templates/blog/create.html | 15 +++++ flaskr/templates/blog/index.html | 28 +++++++++ flaskr/templates/blog/update.html | 20 ++++++ 10 files changed, 336 insertions(+) create mode 100644 flaskr/auth.py create mode 100644 flaskr/blog.py create mode 100644 flaskr/static/style.css create mode 100644 flaskr/templates/auth/login.html create mode 100644 flaskr/templates/auth/register.html create mode 100644 flaskr/templates/base.html create mode 100644 flaskr/templates/blog/create.html create mode 100644 flaskr/templates/blog/index.html create mode 100644 flaskr/templates/blog/update.html diff --git a/flaskr/__init__.py b/flaskr/__init__.py index ec1942c..f711944 100644 --- a/flaskr/__init__.py +++ b/flaskr/__init__.py @@ -32,4 +32,11 @@ def create_app(test_config=None): from . import db db.init_app(app) + from . import auth + app.register_blueprint(auth.bp) + + from . import blog + app.register_blueprint(blog.bp) + app.add_url_rule('/', endpoint='index') + return app diff --git a/flaskr/auth.py b/flaskr/auth.py new file mode 100644 index 0000000..b481433 --- /dev/null +++ b/flaskr/auth.py @@ -0,0 +1,90 @@ +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from flaskr.db import get_db + +bp = Blueprint('auth', __name__, url_prefix='/auth') + +@bp.route('/register', methods=('GET', 'POST')) +def register(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + + if not username: + error = 'Username is required.' + elif not password: + error = 'Password is required.' + elif db.execute( + 'SELECT id FROM user WHERE username = ?', (username,) + ).fetchone() is not None: + error = 'User {} is already registered.'.format(username) + + if error is None: + db.execute( + 'INSERT INTO user (username, password) VALUES (?, ?)', + (username, generate_password_hash(password)) + ) + db.commit() + return redirect(url_for('auth.login')) + + flash(error) + + return render_template('auth/register.html') + +@bp.route('/login', methods=('GET', 'POST')) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + user = db.execute( + 'SELECT * FROM user WHERE username = ?', (username,) + ).fetchone() + + if user is None: + error = 'Incorrect username.' + elif not check_password_hash(user['password'], password): + error = 'Incorrect password.' + + if error is None: + session.clear() + session['user_id'] = user['id'] + return redirect(url_for('index')) + + flash(error) + + return render_template('auth/login.html') + +@bp.before_app_request +def load_logged_in_user(): + user_id = session.get('user_id') + + if user_id is None: + g.user = None + else: + g.user = get_db().execute( + 'SELECT * FROM user WHERE id = ?', (user_id,) + ).fetchone() + +@bp.route('/logout') +def logout(): + session.clear() + return redirect(url_for('index')) + +def login_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + return view(**kwargs) + + return wrapped_view diff --git a/flaskr/blog.py b/flaskr/blog.py new file mode 100644 index 0000000..9094789 --- /dev/null +++ b/flaskr/blog.py @@ -0,0 +1,96 @@ +from flask import ( + Blueprint, flash, g, redirect, render_template, request, url_for +) +from werkzeug.exceptions import abort + +from flaskr.auth import login_required +from flaskr.db import get_db + +bp = Blueprint('blog', __name__) + +@bp.route('/') +def index(): + db = get_db() + posts = db.execute( + 'SELECT p.id, title, body, created, author_id, username' + ' FROM post p JOIN user u ON p.author_id = u.id' + ' ORDER BY created DESC' + ).fetchall() + return render_template('blog/index.html', posts=posts) + +@bp.route('/create', methods=('GET', 'POST')) +@login_required +def create(): + if request.method == 'POST': + title = request.form['title'] + body = request.form['body'] + error = None + + if not title: + error = 'Title is required.' + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + 'INSERT INTO post (title, body, author_id)' + ' VALUES (?, ?, ?)', + (title, body, g.user['id']) + ) + db.commit() + return redirect(url_for('blog.index')) + + return render_template('blog/create.html') + +def get_post(id, check_author=True): + post = get_db().execute( + 'SELECT p.id, title, body, created, author_id, username' + ' FROM post p JOIN user u ON p.author_id = u.id' + ' WHERE p.id = ?', + (id,) + ).fetchone() + + if post is None: + abort(404, "Post id {0} doesn't exist.".format(id)) + + if check_author and post['author_id'] != g.user['id']: + abort(403) + + return post + +@bp.route('//update', methods=('GET', 'POST')) +@login_required +def update(id): + post = get_post(id) + + if request.method == 'POST': + title = request.form['title'] + body = request.form['body'] + error = None + + if not title: + error = 'Title is required.' + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + 'UPDATE post SET title = ?, body = ?' + ' WHERE id = ?', + (title, body, id) + ) + db.commit() + return redirect(url_for('blog.index')) + + return render_template('blog/update.html', post=post) + +@bp.route('//delete', methods=('POST',)) +@login_required +def delete(id): + get_post(id) + db = get_db() + db.execute('DELETE FROM post WHERE id = ?', (id,)) + db.commit() + return redirect(url_for('blog.index')) diff --git a/flaskr/static/style.css b/flaskr/static/style.css new file mode 100644 index 0000000..a45a73b --- /dev/null +++ b/flaskr/static/style.css @@ -0,0 +1,26 @@ +html { font-family: sans-serif; background: #eee; padding: 1rem; } +body { max-width: 960px; margin: 0 auto; background: white; } +h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } +a { color: #377ba8; } +hr { border: none; border-top: 1px solid lightgray; } +nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } +nav h1 { flex: auto; margin: 0; } +nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } +nav ul { display: flex; list-style: none; margin: 0; padding: 0; } +nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } +.content { padding: 0 1rem 1rem; } +.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } +.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } +.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } +.post > header { display: flex; align-items: flex-end; font-size: 0.85em; } +.post > header > div:first-of-type { flex: auto; } +.post > header h1 { font-size: 1.5em; margin-bottom: 0; } +.post .about { color: slategray; font-style: italic; } +.post .body { white-space: pre-line; } +.content:last-child { margin-bottom: 0; } +.content form { margin: 1em 0; display: flex; flex-direction: column; } +.content label { font-weight: bold; margin-bottom: 0.5em; } +.content input, .content textarea { margin-bottom: 1em; } +.content textarea { min-height: 12em; resize: vertical; } +input.danger { color: #cc2f2e; } +input[type=submit] { align-self: start; min-width: 10em; } diff --git a/flaskr/templates/auth/login.html b/flaskr/templates/auth/login.html new file mode 100644 index 0000000..b326b5a --- /dev/null +++ b/flaskr/templates/auth/login.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/flaskr/templates/auth/register.html b/flaskr/templates/auth/register.html new file mode 100644 index 0000000..4320e17 --- /dev/null +++ b/flaskr/templates/auth/register.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Register{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/flaskr/templates/base.html b/flaskr/templates/base.html new file mode 100644 index 0000000..55c35a6 --- /dev/null +++ b/flaskr/templates/base.html @@ -0,0 +1,24 @@ + +{% block title %}{% endblock %} - Flaskr + + +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
diff --git a/flaskr/templates/blog/create.html b/flaskr/templates/blog/create.html new file mode 100644 index 0000000..88e31e4 --- /dev/null +++ b/flaskr/templates/blog/create.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}New Post{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/flaskr/templates/blog/index.html b/flaskr/templates/blog/index.html new file mode 100644 index 0000000..3481b8e --- /dev/null +++ b/flaskr/templates/blog/index.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Posts{% endblock %}

+ {% if g.user %} + New + {% endif %} +{% endblock %} + +{% block content %} + {% for post in posts %} +
+
+
+

{{ post['title'] }}

+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body'] }}

+
+ {% if not loop.last %} +
+ {% endif %} + {% endfor %} +{% endblock %} diff --git a/flaskr/templates/blog/update.html b/flaskr/templates/blog/update.html new file mode 100644 index 0000000..af66322 --- /dev/null +++ b/flaskr/templates/blog/update.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+
+
+ +
+{% endblock %}