• Python后端Flask学习项目实践---搭建一个问答网站


    1.项目效果展示

    这里主要以后端为主,前端的代码不做展示,如果需要代码可以评论或者私信

    用户注册、登录相关:

    用邮箱进行注册,并通过向邮箱发送验证码来进行判断,一个邮箱只能注册一个账号

     

     首页相关:

    用户登录后可以进行发布问题和回答,同时也提供搜索功能,在首页展示所有问题

     搜索:

     

     评论:

     

    2.项目工程目录

    blueprints:

    项目蓝图包括问答的逻辑实现和用户的逻辑时间

    migrations:

    项目数据库迁移

    static、templates:

    前端相关,css、html文件等

    3.项目实现:

    3.1数据库

    主要是需要设计一个用户表user表,一个邮箱对应验证码EmailCaptcha的表,一个问题question表,一个评论Answer表

    利用flask中提供的SQLAlchemy不用我们自己手动写SQL代码,用面向对象的思维解决就好

    (1)新建db对象

    因为在很多文件中都需要用到db对象,所以用一个专门的文件etx.py储存,并在app.py中进行初始化:

    etx.py:

    1. from flask_mail import Mail
    2. from flask_sqlalchemy import SQLAlchemy
    3. db = SQLAlchemy()
    4. mail = Mail()

    app.py:

    1. app = Flask(__name__)
    2. # 配置项
    3. app.config.from_object(config)
    4. db.init_app(app)
    5. mail.init_app(app)
    6. migrate = Migrate(app, db)
    7. # 组装蓝图 将book、course、user模块都组装在main.py中
    8. app.register_blueprint(qa_bp)
    9. app.register_blueprint(user_bp)

    (2)配置数据库:

    1. # 数据库的配置变量
    2. HOSTNAME = '127.0.0.1'
    3. PORT = '3306'
    4. DATABASE = 'flask'
    5. USERNAME = 'root'
    6. PASSWORD = '*****'
    7. DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
    8. SQLALCHEMY_DATABASE_URI= DB_URI
    9. # 关闭数据库修改跟踪操作[提高性能],可以设置为True,这样可以跟踪操作:
    10. SQLALCHEMY_TRACK_MODIFICATIONS=False
    11. # 开启输出底层执行的sql语句
    12. SQLALCHEMY_ECHO = True

    (3)设计models:

    1. from exts import db
    2. class EmailCaptchaModel(db.Model):
    3. __tablename__="email_captcha"
    4. id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    5. email=db.Column(db.String(100),nullable=True,unique=True)
    6. captcha=db.Column(db.String(10),nullable=False)
    7. create_time=db.Column(db.DateTime)
    8. class UserModel(db.Model):
    9. __tablename__ = "user"
    10. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    11. username = db.Column(db.String(200),nullable=False,unique=True)
    12. email = db.Column(db.String(100),nullable=False,unique=True)
    13. password = db.Column(db.String(200),nullable=False)
    14. join_time = db.Column(db.DateTime)
    15. class QuestionModel(db.Model):
    16. __tablename__ = "question"
    17. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    18. title = db.Column(db.String(200), nullable=False)
    19. content = db.Column(db.Text,nullable=False)
    20. create_time = db.Column(db.DateTime)
    21. author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    22. author = db.relationship("UserModel",backref="questions")
    23. class AnswerModel(db.Model):
    24. __tablename__ = "answer"
    25. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    26. content = db.Column(db.Text,nullable=False)
    27. create_time = db.Column(db.DateTime)
    28. question_id = db.Column(db.Integer,db.ForeignKey("question.id"))
    29. author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    30. question = db.relationship("QuestionModel",backref=db.backref("answers",order_by=create_time.desc()))
    31. author = db.relationship("UserModel",backref="answers")

    (4)生成数据库

    利用flask中提供的数据库迁移功能可以在更新数据库后直接迁移

    在命令行进行输入

    step1:

    flask db init

    这条语句执行完后会生成上述文件中的migrate文件夹

    step2:

    flask db migrate

    step3:

    flask db upgrade

    更新完成!

    3.2 发送邮件功能

    在注册时点击发送验证码后利用一个固定的邮箱给注册的邮箱发送验证码信息,同时存储到数据库中验证输入的验证码是否与收到的验证码一致。

    主要是利用flask_mail进行邮箱发送。

    (1)邮箱设置

    1. # 邮箱配置
    2. # 项目中用的是QQ邮箱
    3. MAIL_SERVER = "smtp.qq.com"
    4. MAIL_PORT = 465
    5. MAIL_USE_TLS = False
    6. MAIL_USE_SSL = True
    7. MAIL_DEBUG = True
    8. MAIL_USERNAME = "774747245@qq.com"
    9. MAIL_PASSWORD = "*****"
    10. MAIL_DEFAULT_SENDER = "774747245@qq.com"

    (2)得到验证码

    可以在浏览器中进行测试

    1. @bp.route("/captcha", methods=['POST'])
    2. def get_captcha():
    3. email = request.form.get("email")
    4. # 从letters集合中随机取出4个生成验证码 letters集合是英文和数字的集合
    5. letters = string.ascii_letters + string.digits
    6. captcha = "".join(random.sample(letters, 4))
    7. if email:
    8. message = Message(
    9. subject="邮箱测试",
    10. recipients=[email],
    11. body=f"您的注册验证码是:{captcha}"
    12. )
    13. mail.send(message)
    14. # 存放到数据库中:先通过email进行查询,如果存在该email就直接更新captcha就行,如果不存在就添加一个记录
    15. captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
    16. if captcha_model:
    17. captcha_model.captcha = captcha
    18. # captcha_model.create_time=datetime.time.now()
    19. db.session.commit()
    20. else:
    21. captcha_model = EmailCaptchaModel(email=email, captcha=captcha)
    22. db.session.add(captcha_model)
    23. db.session.commit()
    24. print("captcha:", captcha)
    25. return jsonify({"code": 200, "message": "suceess"})
    26. else:
    27. return jsonify({"code": 400, "message": "mail为空"})

    3.3 用户注册和登录、登出

    (1)判断注册和登录填写的表单是否符合要求

    可以直接利用flask中的wtforms进行格式限制:

    新建一个forms.py:

    1. import wtforms
    2. from wtforms.validators import length,email,EqualTo,InputRequired
    3. from models import EmailCaptchaModel,UserModel
    4. class LoginForm(wtforms.Form):
    5. email = wtforms.StringField(validators=[email()])
    6. password = wtforms.StringField(validators=[length(min=6,max=20)])
    7. class RegisterForm(wtforms.Form):
    8. username = wtforms.StringField(validators=[length(min=3,max=20,message="长度在3和20之间")])
    9. email = wtforms.StringField(validators=[email()])
    10. captcha = wtforms.StringField(validators=[length(min=4, max=4)])
    11. password = wtforms.StringField(validators=[length(min=6,max=20,message="长度在6和20之间")])
    12. password_confirm = wtforms.StringField(validators=[EqualTo("password")])
    13. def validate_captcha(self,field):
    14. captcha = field.data
    15. email = self.email.data
    16. captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
    17. print(captcha_model.captcha)
    18. if not captcha_model or captcha_model.captcha.lower() != captcha.lower():
    19. print("验证码错误")
    20. raise wtforms.ValidationError("邮箱验证码错误!")
    21. def validate_email(self,field):
    22. email = field.data
    23. user_model = UserModel.query.filter_by(email=email).first()
    24. if user_model:
    25. print("邮箱已存在")
    26. raise wtforms.ValidationError("邮箱已经存在!")
    27. class QuestionForm(wtforms.Form):
    28. title = wtforms.StringField(validators=[length(min=3, max=200)])
    29. content = wtforms.StringField(validators=[length(min=5)])
    30. class AnswerForm(wtforms.Form):
    31. content = wtforms.StringField(validators=[length(min=1)])

    (2)注册

    注册主要是判断格式是否正确,并判断验证码是否正确,如果正确后,新生成一个user对象插入到user表中。同时在存储时进行密码加密存储

    1. @bp.route('/register', methods=['GET', 'POST'])
    2. def register():
    3. if request.method == 'GET':
    4. return render_template("register.html")
    5. else:
    6. form = RegisterForm(request.form)
    7. if form.validate():
    8. print("验证成功")
    9. username = form.username.data
    10. email = form.email.data
    11. password = form.password.data
    12. # 密码加密
    13. hash_password = generate_password_hash(password=password)
    14. captcha = form.captcha.data
    15. create_time = datetime.datetime.now()
    16. # 1.通过email查询user表 如果存在就通知已存在该用户 不存在就新建
    17. user_model = UserModel.query.filter_by(email=email).first()
    18. if user_model:
    19. print("该邮箱已被注册,请重新输入")
    20. return redirect(url_for("user.register"))
    21. user = UserModel(username=username, email=email, password=hash_password, join_time=create_time)
    22. db.session.add(user)
    23. db.session.commit()
    24. return redirect(url_for("user.login"))
    25. else:
    26. print("注册验证失败")
    27. return redirect(url_for("user.register"))

    (3)登录

    先查询是否存在这个用户,如果存在进行密码验证

    1. @bp.route('/login', methods=['GET', 'POST'])
    2. def login():
    3. """登录:guest1:123456
    4. 0.通过验证
    5. 1.通过邮箱查找出user_model
    6. 2.如果存在就比较密码是否正确 正确:登录成功 不正确:密码错误
    7. 3.不存在直接提示用户不存在并返回到注册页面"""
    8. if request.method == 'GET':
    9. return render_template("login.html")
    10. else:
    11. form = LoginForm(request.form)
    12. if form.validate():
    13. email = form.email.data
    14. password_input = form.password.data
    15. user_model = UserModel.query.filter_by(email=email).first()
    16. if user_model:
    17. if check_password_hash(user_model.password, password=password_input):
    18. print("登录成功")
    19. session['user_id'] = user_model.id
    20. return redirect("/")
    21. else:
    22. print("密码输入错误")
    23. flash("密码输入错误")
    24. return redirect(url_for("user.login"))
    25. else:
    26. print("该用户不存在,请注册")
    27. flash("该用户不存在,请注册")
    28. return redirect(url_for("user.register"))
    29. else:
    30. print("请输入正确格式的账号或密码")
    31. flash("请输入正确格式的账号或密码")
    32. return redirect(url_for("user.login"))

    (4)登出

    删除session即可

    1. @bp.route('/logout')
    2. def logout():
    3. session.clear() # 清除session就可
    4. return redirect(url_for("user.login"))

    3.4 访问页面权限管理

    用户在未登录时不能进行发布问答的功能,这里可以利用一个装饰器进行实现

    1. """装饰器"""
    2. from flask import g, redirect, url_for
    3. from functools import wraps
    4. """如果没有登录就不能访问,跳转到登录页面 如果登录了就正常逻辑处理"""
    5. def login_required(func):
    6. @wraps(func)
    7. def wrapper(*args, **kwargs):
    8. if hasattr(g, "user"):
    9. return func(*args, **kwargs)
    10. else:
    11. print("未登录不能进行访问")
    12. return redirect(url_for("user.login"))
    13. return wrapper

    在需要进行访问限制的方法上面加上

    1. @bp.route('/public_question', methods=['GET', 'POST'])
    2. @login_required
    3. def public_question():

    就可以实现访问权限的设置啦!!!

    3.5 问答查看、搜索

    问答功能:实际上就是生成一个QuestionModel,将符合要求的Question存储到数据库中

    (1)QuestionModel:

    1. class QuestionModel(db.Model):
    2. __tablename__ = "question"
    3. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    4. title = db.Column(db.String(200), nullable=False)
    5. content = db.Column(db.Text,nullable=False)
    6. create_time = db.Column(db.DateTime)
    7. author_id = db.Column(db.Integer,db.ForeignKey("user.id"))
    8. author = db.relationship("UserModel",backref="questions")

    (2)表单验证:

    1. class QuestionForm(wtforms.Form):
    2. title = wtforms.StringField(validators=[length(min=3, max=200)])
    3. content = wtforms.StringField(validators=[length(min=5)])

    (3)问答功能

    1. @bp.route('/public_question', methods=['GET', 'POST'])
    2. @login_required
    3. def public_question():
    4. """发布问答"""
    5. if request.method == 'GET':
    6. return render_template("public_question.html")
    7. else:
    8. form = QuestionForm(request.form)
    9. if form.validate():
    10. title = form.title.data
    11. content = form.content.data
    12. create_time = datetime.datetime.now()
    13. question = QuestionModel(title=title, content=content, author=g.user, create_time=create_time)
    14. db.session.add(question)
    15. db.session.commit()
    16. print("发布问答成功")
    17. return redirect(url_for("qa.index"))
    18. else:
    19. flash("格式不正确")
    20. return redirect(url_for("qa.public_question"))

    (4)问题细节查看

    1. @bp.route("/question/<int:question_id>")
    2. def question_detail(question_id):
    3. question = QuestionModel.query.get(question_id)
    4. return render_template("detail.html", question=question)

    (5)问题搜索

    在标题和内容中都可以进行查询搜索,只有符合其一就输出

    1. @bp.route('/search')
    2. def search():
    3. q = request.args.get("q")
    4. questions = QuestionModel.query.filter(
    5. or_(QuestionModel.title.contains(q), QuestionModel.content.contains(q))).order_by(db.text("create_time"))
    6. print("搜索结果", questions == None)
    7. if questions:
    8. return render_template("index.html", questions=questions)
    9. else:
    10. print("搜索结果为空")
    11. return "搜索结果为空"

    3.6 评论功能

    在发布问题后可以进行评论,与问题一致的流程

    (1)生成AnswerModel:

    1. class AnswerModel(db.Model):
    2. __tablename__ = "answer"
    3. id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    4. content = db.Column(db.Text,nullable=False)
    5. create_time = db.Column(db.DateTime)
    6. question_id = db.Column(db.Integer,db.ForeignKey("question.id"))
    7. author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    8. question = db.relationship("QuestionModel",backref=db.backref("answers",order_by=create_time.desc()))
    9. author = db.relationship("UserModel",backref="answers")

    (2)生成表单验证:

    1. class AnswerForm(wtforms.Form):
    2. content = wtforms.StringField(validators=[length(min=1)])

    (3)进行评论

    1. @bp.route("/answer/<int:question_id>", methods=['POST'])
    2. @login_required
    3. def answer(question_id):
    4. form = AnswerForm(request.form)
    5. if form.validate():
    6. content = form.content.data
    7. create_time = datetime.datetime.now()
    8. answer_model = AnswerModel(content=content, author=g.user, question_id=question_id, create_time=create_time)
    9. db.session.add(answer_model)
    10. db.session.commit()
    11. return redirect(url_for("qa.question_detail", question_id=question_id))
    12. else:
    13. flash("表单验证失败!")
    14. return redirect(url_for("qa.question_detail", question_id=question_id))

    4.总结

    就是简单的一个小项目练练手,新手的可以看这个直接入门,但是项目也需要进行完善啦,比如注册也可以设置头像之类的,这样在首页就可以显示自己的头像之类的。

  • 相关阅读:
    C语言tips-字符串处理函数及其实现
    “易+”开源网易易盾 GameSentry 正式开源,做游戏安全保障的尖兵利刃
    如何正确的防止服务器被攻击?103.216.153.x
    5步绘制软件开发流程图
    多元线性回归方法的应用,人工神经网络回归分析
    Android Jetpack Compose之UI的重组和自动刷新
    MATLAB实战Sobel边缘检测(Edge Detection)
    Mysql.索引存储结构演进
    解决安装apex报错:No module named ‘packaging‘
    Vue2和Vue3响应式区别和理解
  • 原文地址:https://blog.csdn.net/m0_57098080/article/details/125468227