learning from 100 Days of Code: The Complete Python Pro Bootcamp for 2022
Learning Point
01. create decorator @admin_only
Login Required Decorator
functools.wraps
02. Set Error Pages
Custom Error Pages
03. Build One To Many relationship
Basic Relationship Patterns
relationship.back_populates
relationship.backref
04. Set random Gravatar
Flask-Gravatar
main.py
from flask import Flask, render_template, redirect, url_for, flash, abort
from flask_bootstrap import Bootstrap
from flask_ckeditor import CKEditor
from datetime import date
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import relationship
from flask_login import UserMixin, login_user, LoginManager, login_required, current_user, logout_user
from forms import CreatePostForm, RegisterForm, LoginForm, CommentForm
from flask_gravatar import Gravatar
from functools import wraps
app = Flask(__name__)
app.config['SECRET_KEY'] = '8BYkEfBA6O6donzWlSihBXox7C0sKR6b'
ckeditor = CKEditor(app)
Bootstrap(app)
##CONNECT TO DB
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
##CONNECT TO FLASK LOGIN
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
# if user not valid, return None
return User.query.get(user_id)
##CONFIGURE TABLES
class User(db.Model, UserMixin):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(250), unique=True, nullable=False)
password = db.Column(db.String(80), nullable=False)
# posts = relationship("BlogPost", back_populates="author")
# comments = relationship("Comment", back_populates="comment_author")
class BlogPost(db.Model):
__tablename__ = "blog_posts"
id = db.Column(db.Integer, primary_key=True)
author = relationship("User", backref="blog_posts")
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
title = db.Column(db.String(250), unique=True, nullable=False)
subtitle = db.Column(db.String(250), nullable=False)
date = db.Column(db.String(250), nullable=False)
body = db.Column(db.Text, nullable=False)
img_url = db.Column(db.String(250), nullable=False)
# comments = relationship("Comment", back_populates="parent_post")
class Comment(db.Model):
__tablename__ = "comments"
id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(db.Integer, db.ForeignKey('blog_posts.id'))
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
parent_post = relationship("BlogPost", backref="comments")
author = relationship("User", backref="comments")
text = db.Column(db.Text, nullable=False)
# db.create_all()
################################################
def admin_only(f):
'''
@wrap(add_new_)
def check_if_admin(*args, **kwargs):
if current_user.get_id() != '1':
return "503"
return add_new_post(*args, **kwargs)
decorator 功用:
1. 先整理方程式
@admin_only(add_new_post) = admin_only(add_new_post) = check_if_admin
2. 再呼叫啟動
add_new_post() = check_if_admin()
3. wrap(f) = wrap(add_new_post)
If WITH => 最後會回歸 add_new_post => @app.route("/new-post") 會觸發 add_new_post
If WITHOUT => add_new_post 被 check_if_admin 取代 => @app.route("/new-post") 會觸發 check_if_admin
'''
@wraps(f)
def check_if_admin(*args, **kwargs):
if current_user.get_id() != '1':
abort(403)
return f(*args, **kwargs)
return check_if_admin
################################################
@app.route('/')
def get_all_posts():
posts = BlogPost.query.all()
user_id = current_user.get_id()
return render_template("index.html", all_posts=posts, user_id=user_id)
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
if User.query.filter_by(email=form.email.data).first() is not None:
flash("You've already sign up with that email, log in instead!")
return redirect(url_for('login'))
hash_and_salted_psw = generate_password_hash(
form.password.data,
method='pbkdf2:sha256',
salt_length=8
)
new_user = User(
name=form.name.data,
email=form.email.data,
password=hash_and_salted_psw
)
db.session.add(new_user)
db.session.commit()
login_user(new_user)
return redirect(url_for('get_all_posts'))
return render_template("register.html", form=form)
@app.route('/login', methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None:
flash("That email does not exist, please try again.")
return redirect(url_for('login'))
elif not check_password_hash(user.password, form.password.data):
flash("Password incorrect, please try again.")
return redirect(url_for('login'))
else:
login_user(user)
return redirect(url_for('get_all_posts'))
return render_template("login.html", form=form)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('get_all_posts'))
@app.route("/post/<int:post_id>", methods=['GET', 'POST'])
def show_post(post_id):
form = CommentForm()
user_id = current_user.get_id()
requested_post = BlogPost.query.get(post_id)
gravatar = Gravatar(app,
size=150,
rating='g',
default='retro',
force_default=False,
force_lower=False,
use_ssl=False,
base_url=None)
if form.validate_on_submit():
if not current_user.is_authenticated:
flash("You need to login or register to comment.")
return redirect(url_for('login'))
else:
new_comment = Comment(
text=form.comment.data,
parent_post=BlogPost.query.get(post_id),
author=current_user
)
db.session.add(new_comment)
db.session.commit()
return render_template("post.html", post=requested_post, form=form, user_id=user_id, gravatar=gravatar)
@app.route("/about")
def about():
return render_template("about.html")
@app.route("/contact")
def contact():
return render_template("contact.html")
@app.route("/new-post", methods=['GET', 'POST'])
@admin_only
def add_new_post():
form = CreatePostForm()
if form.validate_on_submit():
new_post = BlogPost(
title=form.title.data,
subtitle=form.subtitle.data,
body=form.body.data,
img_url=form.img_url.data,
author=current_user,
date=date.today().strftime("%B %d, %Y")
)
db.session.add(new_post)
db.session.commit()
return redirect(url_for("get_all_posts"))
return render_template("make-post.html", form=form)
@app.route("/edit-post/<int:post_id>", methods=['GET', 'POST'])
@admin_only
def edit_post(post_id):
post = BlogPost.query.get(post_id)
edit_form = CreatePostForm(
title=post.title,
subtitle=post.subtitle,
img_url=post.img_url,
author=current_user,
body=post.body
)
if edit_form.validate_on_submit():
post.title = edit_form.title.data
post.subtitle = edit_form.subtitle.data
post.img_url = edit_form.img_url.data
post.body = edit_form.body.data
db.session.commit()
return redirect(url_for("show_post", post_id=post.id))
return render_template("make-post.html", form=edit_form, is_edit=True)
@app.route("/delete/<int:post_id>")
@admin_only
def delete_post(post_id):
post_to_delete = BlogPost.query.get(post_id)
db.session.delete(post_to_delete)
db.session.commit()
return redirect(url_for('get_all_posts'))
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)