钩子函数和上下文处理器和退出登录
from flask import Flask, Response, request, session, render_template,g
import config
from exts import db,mail
from blueprints import qa_bp
from blueprints import user_bp
from flask_migrate import Migrate
from models import UserModel
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
mail.init_app(app)
migrate=Migrate(app,db)
app.register_blueprint(qa_bp)
app.register_blueprint(user_bp)
@app.before_request
def before_request():
user_id = session.get("user_id")
if user_id:
try:
user = UserModel.query.get(user_id)
g.user = user
except:
g.user = None
@app.context_processor
def context_processor():
if hasattr(g,"user"):
return {"user": g.user}
else:
return {}
if __name__ == '__main__':
app.run()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static',filename='bootstrap/bootstrap.4.6.css') }}">
</head>
<link rel="stylesheet" href="{{ url_for('static',filename='css/init.css') }}">
{% block head %}{% endblock %}
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">?表白墙</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">首页 <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">树洞</a>
</li>
<li class="nav-item ml-3">
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="关键字" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
</form>
</li>
</ul>
<ul class="navbar-nav ">
{% if user %}
<li class="nav-item">
<span class="nav-link">{{ user.username }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.logout') }}">退出登录</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container">
{% block body %}
{% endblock %}
</div>
</body>
</html>
import random
import string
from flask import (Blueprint,
render_template,
request, redirect,
url_for,
jsonify,
session,
flash)
from blueprints.forms import RegisterForm,LoginForm
from exts import mail, db
from flask_mail import Message
from models import EmailCaptchaModel, UserModel
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
bp = Blueprint("user", __name__, url_prefix="/user")
@bp.route("/login",methods=['GET','POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
else:
form = LoginForm(request.form)
if form.validate():
email = form.email.data
password = form.password.data
user = UserModel.query.filter_by(email=email).first()
if user and check_password_hash(user.password, password):
session['user_id'] = user.id
return redirect("/")
else:
flash("邮箱和密码不匹配")
return redirect(url_for("user.login"))
else:
flash("邮箱或者密码格式错误")
return redirect(url_for("user.login"))
@bp.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template("register.html")
else:
form = RegisterForm(request.form)
if form.validate():
email = form.email.data
username = form.username.data
password = form.password.data
hash_password = generate_password_hash(password)
user = UserModel(email=email, username=username, password=hash_password)
db.session.add(user)
db.session.commit()
return redirect(url_for("user.login"))
else:
return redirect(url_for("user.register"))
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for('user.login'))
@bp.route("/captcha", methods=['POST'])
def get_captcha():
email = request.form.get("email")
letters = string.ascii_letters + string.digits
captcha = "".join(random.sample(letters, 4))
if email:
message = Message(
subject="这是一封很有味道的邮件",
recipients=[email],
body=f"哈咯哈咯,您宝贵的码是:{captcha},请不要告诉任何人哦!"
)
mail.send(message)
captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
if captcha_model:
captcha_model.captcha = captcha
captcha_model.create_time = datetime.now()
db.session.commit()
else:
captcha_model = EmailCaptchaModel(email=email, captcha=captcha)
db.session.add(captcha_model)
db.session.commit()
print("captcha:", captcha)
return jsonify({"code": 200})
else:
return jsonify({"code": 400, "message": "请先传递邮箱!"})
访问限制
功能:完善用户未登录即可发布评论的功能。 用一个decorators.py装饰器,再在qa.py中导入该功能即可。 decorators.py:
from flask import g,redirect,url_for
from functools import wraps
def login_required(func):
@wraps(func)
def wrapper(*args,**kwargs):
if hasattr(g,'user'):
return func(*args,**kwargs)
else:
return redirect(url_for("user.login"))
return wrapper
user.py
import random
import string
from flask import (Blueprint,
render_template,
request, redirect,
url_for,
jsonify,
session,
flash)
from blueprints.forms import RegisterForm,LoginForm
from exts import mail, db
from flask_mail import Message
from models import EmailCaptchaModel, UserModel
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
bp = Blueprint("user", __name__, url_prefix="/user")
@bp.route("/login",methods=['GET','POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
else:
form = LoginForm(request.form)
if form.validate():
email = form.email.data
password = form.password.data
user = UserModel.query.filter_by(email=email).first()
if user and check_password_hash(user.password, password):
session['user_id'] = user.id
return redirect("/")
else:
flash("邮箱和密码不匹配")
return redirect(url_for("user.login"))
else:
flash("邮箱或者密码格式错误")
return redirect(url_for("user.login"))
@bp.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template("register.html")
else:
form = RegisterForm(request.form)
if form.validate():
email = form.email.data
username = form.username.data
password = form.password.data
hash_password = generate_password_hash(password)
user = UserModel(email=email, username=username, password=hash_password)
db.session.add(user)
db.session.commit()
return redirect(url_for("user.login"))
else:
return redirect(url_for("user.register"))
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for('user.login'))
@bp.route("/captcha", methods=['POST'])
def get_captcha():
email = request.form.get("email")
letters = string.ascii_letters + string.digits
captcha = "".join(random.sample(letters, 4))
if email:
message = Message(
subject="这是一封很有味道的邮件",
recipients=[email],
body=f"哈咯哈咯,您宝贵的码是:{captcha},请不要告诉任何人哦!"
)
mail.send(message)
captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
if captcha_model:
captcha_model.captcha = captcha
captcha_model.create_time = datetime.now()
db.session.commit()
else:
captcha_model = EmailCaptchaModel(email=email, captcha=captcha)
db.session.add(captcha_model)
db.session.commit()
print("captcha:", captcha)
return jsonify({"code": 200})
else:
return jsonify({"code": 400, "message": "请先传递邮箱!"})
退出登录之后无法访问。
发布
1.在树洞创建一个前端界面public_question.html:
<!DOCTYPE html>
{% extends "base.html" %}
{% block title %}?表白墙-首页{% endblock %}
{% block body %}
<div class="row" style="margin-top: 20px;">
<div class="col"></div>
<div class="col-8">
<h1 style="text-align: center;">树洞</h1>
<form action="{{ url_for('qa.public_question') }}" method="post">
<div class="form-group">
<input type="text" name="title" class="form-control" placeholder="你想对谁说">
</div>
<div class="form-group">
<textarea name="content" class="form-control" rows="10" placeholder="请输入内容,至少5个字哦"></textarea>
</div>
{% for message in get_flashed_messages() %}
<div class="form-group">
<div class="text-danger">{{ message }}</div>
</div>
{% endfor %}
<div class="form-group" style="text-align: right;">
<button class="btn btn-primary">发布</button>
</div>
</form>
</div>
<div class="col"></div>
</div>
{% endblock %}
2.需要在数据库中创建一个表储存用户发的树洞。 利用model.py创建表的模板
from exts import db
from datetime import datetime
class EmailCaptchaModel(db.Model):
__tablename__ = "email_captcha"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(100), nullable=False, unique=True)
captcha = db.Column(db.String(10), nullable=False)
create_time = db.Column(db.DateTime, default=datetime.now)
class UserModel(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(200), nullable=False, unique=True)
email = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(200), nullable=False)
join_time = db.Column(db.DateTime, default=datetime.now)
class QuestionModel(db.Model):
__tablename__ = "question"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text,nullable=False)
create_time = db.Column(db.DateTime,default=datetime.now)
author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
author = db.relationship("UserModel",backref="questions")
利用flask.db.migrate和flask.db.upgrade在数据库中创建该表
3.因为用户发表的树洞需要显示在主页上,所以修改一下主页的前端界面。 index.css
.question-ul li{
padding: 10px;
overflow: hidden;
display: flex;
background-color: #fff;
border-bottom:1px solid #eee;
}
.side-question{
flex-basis: 38px;
height:100%;
}
.side-question-avatar{
width: 38px;
height:38px;
border-radius: 3px;
}
.question-main{
flex: 1;
width: 660px;
margin-left:10px;
overflow: hidden;
}
.question-title a{
color: #259;
font-size:14px;
font-weight: 900;
}
.question-author{
font-size: 12px;
margin-top: 5px;
}
.question-content{
margin-top: 5px;
font-size:12px;
}
.question-detail{
text-align: right;
margin-top: 10px;
}
.question-detail .question-author{
margin-right:10px;
}
index.html
{% extends "base.html" %}
{% block title %}知了问答-首页{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}">
{% endblock %}
{% block body %}
<div class="row" style="margin-top: 20px;">
<div class="col"></div>
<div class="col-8">
<ul class="question-ul">
{% for question in questions %}
<li>
<div class="side-question">
<img class="side-question-avatar" src="{{ url_for('static',filename='images/img.png') }}" alt="">
</div>
<div class="question-main">
<div class="question-title"><a
href="#">{{ question.title }}</a></div>
<div class="question-content">{{ question.content }}</div>
<div class="question-detail">
<span class="question-author">{{ question.author.username }}</span>
<span class="question-time">{{ question.create_time }}</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="col"></div>
</div>
{% endblock %}
4.在forms.py中设QuestionModels的类,查询用户输入的话是否符合格式:
import wtforms
from wtforms.validators import length,email,EqualTo,InputRequired
from models import EmailCaptchaModel,UserModel
class QuestionForm(wtforms.Form):
title = wtforms.StringField(validators=[length(min=3, max=200)])
content = wtforms.StringField(validators=[length(min=5)])
进入发布内容详情和评论
1.在model.py创建一个AnswerModel的模型,映射到数据库中
class AnswerModel(db.Model):
__tablename__ = "answer"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
content = db.Column(db.Text,nullable=False)
create_time = db.Column(db.DateTime,default=datetime.now)
question_id = db.Column(db.Integer,db.ForeignKey("question.id"))
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
question = db.relationship("QuestionModel",backref=db.backref("answers",order_by=create_time.desc()))
author = db.relationship("UserModel",backref="answers")
2.在qa.py中添加可视化
@bp.route("/answer/<int:question_id>",methods=['POST'])
@login_required
def answer(question_id):
form = AnswerForm(request.form)
if form.validate():
content = form.content.data
answer_model = AnswerModel(content=content,author=g.user,question_id=question_id)
db.session.add(answer_model)
db.session.commit()
return redirect(url_for("qa.question_detail",question_id=question_id))
else:
flash("表单验证失败!")
return redirect(url_for("qa.question_detail", question_id=question_id))
3.新建detail.html显示详情界面
{% extends "base.html" %}
{% block title %}{{ question.title }}{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/detail.css') }}">
{% endblock %}
{% block body %}
<div class="row" style="margin-top: 20px;">
<div class="col"></div>
<div class="col-8" style="background-color: #fff;padding: 20px;">
<h3 class="page-title">{{ question.title }}</h3>
<p class="question-info">
<span>发布人:{{ question.author.username }}</span>
<span>时间:{{ question.create_time }}</span>
</p>
<hr>
<p class="question-content">{{ question.content }}</p>
<hr>
<h4 class="comment-group-title">评论({{ question.answers|length }}):</h4>
<form action="{{ url_for('qa.answer',question_id=question.id) }}" method="post">
<div class="form-group">
<input type="text" placeholder="请填写评论" name="content" class="form-control">
</div>
{% for message in get_flashed_messages() %}
<div class="form-group">
<div class="text-danger">{{ message }}</div>
</div>
{% endfor %}
<div class="form-group" style="text-align: right;">
<button class="btn btn-primary">评论</button>
</div>
</form>
<ul class="comment-group">
{% for answer in question.answers %}
<li>
<div class="user-info">
<img class="avatar" src="{{ url_for('static',filename='images/img.png') }}" alt="">
<span class="username">{{ answer.author.username }}</span>
<span class="create-time">{{ answer.create_time }}</span>
</div>
<p class="comment-content">{{ answer.content }}</p>
</li>
{% endfor %}
</ul>
</div>
<div class="col"></div>
</div>
{% endblock %}
4.在index.html主页中添加进入详情页的连接
{% extends "base.html" %}
{% block title %}?表白墙-首页{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}">
{% endblock %}
{% block body %}
<div class="row" style="margin-top: 20px;">
<div class="col"></div>
<div class="col-8">
<ul class="question-ul">
{% for question in questions %}
<li>
<div class="side-question">
<img class="side-question-avatar" src="{{ url_for('static',filename='images/img.png') }}" alt="">
</div>
<div class="question-main">
<div class="question-title"><a
href="{{ url_for('qa.question_detail',question_id=question.id) }}">{{ question.title }}</a></div>
<div class="question-content">{{ question.content }}</div>
<div class="question-detail">
<span class="question-author">{{ question.author.username }}</span>
<span class="question-time">{{ question.create_time }}</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="col"></div>
</div>
{% endblock %}
|