笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~
【论坛项目实战】
【python】Flask网页开发——论坛项目实战(完整代码)【python】Flask网页开发——论坛项目实战(1.导航条实现)【python】Flask网页开发——论坛项目实战(2.登录与注册)【python】Flask网页开发——论坛项目实战(3.问答模块)
登录与注册
- 登录与注册
- 使用Flask-Mail发送邮件
- 注册功能
- blueprints
- forms.py
- user.py
- models.py
- stattic
- jquery.3.6.min.js
- register.js
- templates
- base.html
- register.html
- index.html
- app.py
- exts.py
- 登录功能
- blueprints
- forms.py
- user.py
- templates
- base.html
- login.html
- app.py
- config.py
登录与注册
使用Flask-Mail发送邮件
config.py
# 邮箱配置
MAIL_SERVER = "smtp.qq.com" #项目中用QQ邮箱
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_DEBUG = True #发送邮件会提示日志信息
MAIL_USERNAME = "qq邮箱"
MAIL_PASSWORD = "授权码" #登录qq邮箱——>设置——>账号——>POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务——>获取授权码
MAIL_DEFAULT_SENDER = "qq邮箱"
user.py
@bp.route("/mail")
def my_mail():
message = Message(
subject="邮箱主题",
recipients=['收件邮箱'],
body ="这是一篇测试邮件"
)
mail.send(message)
return "邮件发送成功"
exts.py
from flask_mail import Mail
mail =Mail()
app.py
from exts import mail
mail.init_app(app)
测试邮件发送
http://127.0.0.1:5000/user/mail
注册功能
blueprints
forms.py
import wtforms
from wtforms.validators import length, email, EqualTo
from models.models import EmailCaptchaModel, UserModel
class RegisterForm(wtforms.Form):
username = wtforms.StringField(validators=[length(min=3, max=20)])
email = wtforms.StringField(validators=[email()])
captcha = wtforms.StringField(validators=[length(min=4, max=4)])
password = wtforms.StringField(validators=[length(min=6, max=20)])
confirm_password = wtforms.StringField(validators=[EqualTo("password")])
def validate_captcha(self, field):
captcha = field.data
email = self.email.data
captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
if not captcha_model or captcha_model.captcha.lower() != captcha.lower():
raise wtforms.ValidationError("邮箱验证码错误!")
def validate_email(self, field):
email = field.data
user_model = UserModel.query.filter_by(email=email).first()
if user_model:
raise wtforms.ValidationError("邮箱已存在!")
user.py
Werkzeug.security模块
Werkzeug.security 是 Python Web 开发框架 Flask 中的一个模块,它提供了一些常用的密码哈希函数和工具函数,用于进行密码的加密、解密和验证。这些函数基于安全的密码哈希算法,可以帮助开发者在应用中存储用户密码时提供更高的安全性。
Werkzeug.security 模块中最常用的函数是 generate_password_hash() 和 check_password_hash()。generate_password_hash() 函数可以将原始密码转换为安全的哈希值,而 check_password_hash() 函数用于验证哈希密码的正确性。
使用 Werkzeug.security 模块,开发者可以轻松地处理用户密码的安全性,确保用户密码在存储和传输过程中不会被泄露或破解。在 Flask 中,很多 Web 应用都会用到 Werkzeug.security 模块来增强密码的安全性。
from datetime import datetime
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
from exts import mail
from flask_mail import Message
from models.models import EmailCaptchaModel, UserModel
import string
import random
from exts import db
from .forms import RegisterForm
from werkzeug.security import generate_password_hash
bp = Blueprint("user", __name__, url_prefix="/user")
@bp.route("/login")
def login():
return render_template("login.html")
@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("/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)
# 200 成功、正常的请求
return jsonify({"code": 200})
else:
# 400 客户端错误
return jsonify({"code": 400, "message": "请先传递邮箱!"})
models.py
from exts import db
from datetime import datetime
class UserModel(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
password = db.Column(db.String(200), nullable=False)
email = db.Column(db.String(100), nullable=False, unique=True)
join_time = db.Column(db.DateTime, default=datetime.now)
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(100), default=datetime.now)
-
flask db migrate
:生成一个迁移脚本,用于将数据库模式更新到最新版本。 -
flask db upgrade
:将数据库迁移到最新版本。
stattic
jquery.3.6.min.js
https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
register.js
function bindCaptchaBtnClick(){
$("#captcha-btn").on("click",function(event){
var $this = $(this);
var email = $("input[name='email']").val();
// alert(email);
if(!email){
alert("请输入邮箱!");
return;
}
// 通过js发送网络请求,ajax:Async Javascript and XML(JSON)
$.ajax({
url:"/user/captcha",
method:"POST",
data:{
"email":email
},
success:function(res){
var code = res['code'];
if(code == 200){
// 取消点击事件
$this.off("click");
// 开始倒计时
var countDown = 60;
var timer = setInterval(function(){
countDown -= 1;
if(countDown> 0){
$this.text(countDown+"秒后重新发送");
}else{
$this.text("获取验证码");
bindCaptchaBtnClick();
clearInterval(timer);
}
},1000);
alert("验证码发送成功!");
}else{
alert(res['message']);
}
}
})
});
}
// 等网页文档所有元素都加载完成后再执行
$(function(){
bindCaptchaBtnClick();
});
templates
base.html
<!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.min.css')}}">
{% block head %}{% endblock %}
</head>
<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-2">
<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>
</div>
<div class="collapse navbar-collapse" id="dengluzhuce" mr-2>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="#">登录<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">注册</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block body %}{% endblock %}
</div>
</body>
</html>
register.html
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static',filename='bootstrap/bootstrap@4.6.min.css') }}">
<script src="{{ url_for('static',filename='jquery/jquery.3.6.min.js')}}"></script>
<script src="{{ url_for('static',filename='js/register.js')}}"></script>
{% endblock %}
{% block body %}
<div class="row mt-4 justify-content-md-center">
<div class="col"></div>
<div class="col-6">
<h1 style="text-align:center">注册</h1>
<form action="{{ url_for('user.register') }}" method="post" id="register-form">
<!-- <input type="hidden" name="csrf_token" value="">-->
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="email" placeholder="邮箱">
<div class="input-group-append">
<button type="button" id="captcha-btn" class="btn btn-outline-secondary">发送验证码</button>
</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" name="captcha" placeholder="邮箱验证码">
</div>
<div class="form-group">
<input type="text" class="form-control" name="username" placeholder="用户名">
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" placeholder="密码">
</div>
<div class="form-group">
<input type="password" class="form-control" name="confirm_password" placeholder="确认密码">
</div>
<div class="form-group">
<button type="submit" class="btn btn-warning btn-block" id="submit-btn">立即注册</button>
</div>
<div class="form-group">
<a href="{{ url_for('user.login') }}" class="signup-link">返回登录</a>
<a href="#" class="resetpwd-link" style="float:right;">找回密码</a>
</div>
</form>
</div>
<div class="col"></div>
</div>
{% endblock %}
index.html
{% extends "base.html" %}
{% block title %}论坛首页{% endblock %}
{% block head %}{% endblock %}
{% block body %}首页{% endblock %}
app.py
from flask import Flask
import config
from exts import db,mail
from models.models import UserModel
from flask_migrate import Migrate
from blueprints import qa_bp,user_bp
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
mail.init_app(app)
app.register_blueprint(qa_bp)
app.register_blueprint(user_bp)
migrate = Migrate(app, db)
if __name__ == '__main__':
app.run(debug=True)
exts.py
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
db = SQLAlchemy()
mail =Mail()
登录功能
blueprints
forms.py
import wtforms
from wtforms.validators import length, email, EqualTo
from models.models import EmailCaptchaModel, UserModel
class LoginForm(wtforms.Form):
email = wtforms.StringField(validators=[email()])
password = wtforms.StringField(validators=[length(min=6, max=20)])
user.py
from datetime import datetime
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, session, flash
from exts import mail
from flask_mail import Message
from models.models import EmailCaptchaModel, UserModel
import string
import random
from exts import db
from .forms import RegisterForm, LoginForm
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("/logout")
def logout(): # 退出登录
session.clear()
return redirect(url_for("user.login"))
templates
base.html
<!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.min.css')}}">
{% block head %}{% endblock %}
</head>
<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-2">
<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>
</div>
<div class="collapse navbar-collapse" id="dengluzhuce" mr-2>
<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>
login.html
{% extends "base.html" %}
{% block title %}
登录
{% endblock %}
{% block head %}
{% endblock %}
{% block body %}
<div class="row mt-4 justify-content-md-center">
<div class="col"></div>
<div class="col-6">
<h1 style="text-align:center">登录</h1>
<form action="{{ url_for('user.login') }}" method="post">
<!-- <input type="hidden" name="csrf_token" value="##">-->
<div class="form-group">
<input type="text" class="form-control" name="email" placeholder="邮箱">
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" placeholder="密码">
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember" value="1">记住我
</label>
</div>
{% for message in get_flashed_messages() %}
<div class="form-group">
<div class="text-danger">{{ message }}</div>
</div>
{% endfor %}
<div class="form-group">
<button type="submit" class="btn btn-warning btn-block" id="submit-btn">立即登录</button>
</div>
<div class="form-group">
<a href="{{ url_for('user.register') }}" class="signup-link">没有账号?立即注册</a>
<a href="#" class="resetpwd-link" style="float:right;">找回密码</a>
</div>
</form>
</div>
<div class="col"></div>
</div>
{% endblock %}
app.py
from flask import Flask, session, g
import config
from exts import db, mail
from models.models import UserModel
from flask_migrate import Migrate
from blueprints import qa_bp, user_bp
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
mail.init_app(app)
app.register_blueprint(qa_bp)
app.register_blueprint(user_bp)
migrate = Migrate(app, db)
# 流程:请求——>before_request——>视图函数——>视图函数返回模板——>context_processor
@app.before_request
def before_request():
user_id = session.get("user_id")
if user_id:
try:
user = UserModel.query.get(user_id)
# 给g绑定一个叫做user的变量
# setattr(g,"user",user)
# 全局变量
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(debug=True)
config.py
# 应用中使用 Flask-Session 等会话扩展时,需要设置一个随机的密钥,以防止会话数据被篡改。
# 可以在 Flask 应用的配置参数中添加 SECRET_KEY 变量,并设定一个复杂的随机字符串作为密钥
SECRET_KEY = 'a random string'