The notes are study notes compiled by myself. If there are any mistakes, please point them out~
[Forum project practice]
[python] Flask web development – forum project practice (complete code)
[python] Flask web development – forum project practice (1. Navigation bar implementation)
[python] Flask web development – forum project practice (2. Login and registration)
[python] Flask web development – forum project practice (3. Question and answer module)
Q&A module
- Q&A module
-
- blueprints
-
- qa.py
- forms.py
- models
-
- models.py
- templates
-
- index.html
- public_question.html
- detail.html
- decorators.py
Q&A module
blueprints
qa.py
from flask import Blueprint, render_template, request, g, redirect, url_for, flash from decorators import login_required from exts import db from .forms import QuestionForm,AnswerForm from models.models import QuestionModel, AnswerModel from sqlalchemy import or_ bp = Blueprint("qa", __name__, url_prefix="/") @bp.route("/") def index(): questions = QuestionModel.query.order_by(db.text("-create_time")).all() return render_template("index.html", questions=questions) @bp.route("/question/public", methods=['GET', 'POST']) @login_required # Decorator def public_question(): # Determine whether you are logged in. If you are not logged in, jump to the login page. if request.method == 'GET': return render_template("public_question.html") else: form = QuestionForm(request.form) if form.validate(): title = form.title.data content = form.content.data question = QuestionModel(title=title, content=content, author=g.user) db.session.add(question) db.session.commit() return redirect("/") else: flash("Title or content format error!") return redirect(url_for("qa.public_question")) @bp.route("/question/<int:question_id>") def question_detail(question_id): question = QuestionModel.query.get(question_id) return render_template("detail.html", question=question) @bp.route("/answer/<int:question_id>", methods=['POST']) @login_required # Decorator def answer(question_id): #Register form = AnswerForm(request.form) #Storage the content of the front-end 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("Please fill in the comments before submitting!") return redirect(url_for("qa.question_detail", question_id=question_id)) @bp.route("/search") def search(): kword = request.args.get("kword") questions = QuestionModel.query.filter(or_(QuestionModel.title.contains(kword),QuestionModel.content.contains(kword))).order_by(db.text("-create_time")) return render_template("index.html",questions = questions)
forms.py
import wtforms from wtforms.validators import length, email, EqualTo from models.models import EmailCaptchaModel, UserModel class QuestionForm(wtforms.Form): title = wtforms.StringField(validators=[length(min=3, max=200)]) content = wtforms.StringField(validators=[length(min=5)]) class AnswerForm(wtforms.Form): content = wtforms.StringField(validators=[length(min=1)])
models
models.py
from exts import db from datetime import datetime 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(100), default=datetime.now) author_id = db.Column(db.Integer, db.ForeignKey("user.id")) author = db.relationship("UserModel", backref="questions") 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(100), 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")
templates
index.html
{% extends "base.html" %} {% block title %}Forum homepage{% endblock %} {% block head %}{% endblock %} {% block body %} <div class="row mt-4 justify-content-md-center"> <div class="col"></div> <div class="col-6"> <div class="card"> {% for question in questions %} <div class="card-header"> <a href="{<!-- -->{ url_for('qa.question_detail',question_id=question.id) }}">{<!-- -->{ question.title}}</a> </div> <div class="card-body"> {<!-- -->{ question.content}} <div class="blockquote-footer" style="text-align:right"> <span>{<!-- -->{ question.author.username}}</span> <span style="text-align:right">{<!-- -->{ question.create_time}}</span> </div> </div> {% endfor %} </div> </div> <div class="col"></div> </div> {% endblock %}
public_question.html
{% extends "base.html" %} {% block title %}Post Q&A{% endblock %} {% block body %} <div class="row mt-4"> <div class="col"></div> <div class="col-8"> <h1 style="text-align:center">Post Q&A</h1> <form action="{<!-- -->{ url_for('qa.public_question') }}" method="post"> <div class="form-group"> <input type="text" name="title" class="form-control" placeholder="Please enter a title"> </div> <div class="form-group"> <textarea name="content" class="form-control" id="" cols="30" rows="10" placeholder="Please enter content"></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">Publish</button> </div> </form> </div> <div class="col"></div> </div> {% endblock %}
detail.html
{% extends "base.html" %} {% block title %}{<!-- -->{ question.title }}{% endblock %} {% block head %}{% endblock %} {% block body %} <div class="row mt-4 justify-content-md-center"> <div class="col"></div> <div class="col-6"> <div class="card"> <div class="card-header" style="text-align:center"> <h3>{<!-- -->{ question.title}}</h3> <span>Author: {<!-- -->{ question.author.username}}</span> <span style="text-align:right">Time: {<!-- -->{ question.create_time}}</span> </div> <div class="card-body"> {<!-- -->{ question.content}} </div> <div class="card-footer"> <h4>Comments ({<!-- -->{ question.answers|length }})</h4> <form action="{<!-- -->{ url_for('qa.answer',question_id=question.id) }}" method="post"> <div class="form-group"> <input type="text" placeholder="Please comment" 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">Comment</button> </div> </form> <div class="list-group"> <div class="border border-bottom-0" style="padding:10px"> {% for answer in question.answers %} <div class="d-flex w-100 justify-content-between"> <h5 class="mb-1">{<!-- -->{ answer.author.username }}</h5> <small>{<!-- -->{ answer.create_time }}</small> </div> <p class="mb-1">{<!-- -->{ answer.content }}</p> <hr color="black"> {% endfor %} </div> </div> </div> </div> </div> <div class="col"></div> </div> {% endblock %}
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