Flask項目實戰——12—(帖子評論和閱讀數功能、帖子分類功能實現、項目完善、引入Celery異步發送郵件)

1、帖子評論和閱讀數功能

添加閱讀數量字段:前臺模型文件 apps/front/models.py

# 帖子編輯提交模型
class PostModel(db.Model):
    __tablename__ = "post"

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=True)                                      # 帖子標題
    content = db.Column(db.Text, nullable=True)                                           # 帖子內容
    content_html = db.Column(db.Text)
    create_time = db.Column(db.DateTime, default=datetime.now)                            # 默認當前時間
    
    # 閱讀數量字段
    read_count = db.Column(db.Integer, default=0)
    
    # 外鍵,用於查詢排序
    board_id = db.Column(db.Integer, db.ForeignKey('cms_board.id'))      # 'cms_board.id'中cms_board是cms/models.py的表名
    author_id = db.Column(db.String(100), db.ForeignKey('front_user.id'))# 這裏的id使用String是因爲上面定義前臺用戶id時,使用的就是Str類型shortuuid

    # 反向查詢屬性,
    board = db.relationship("BoardModel", backref="posts")              # posts變成cms/models/BoardModel的屬性
    author = db.relationship("Front_User", backref="posts")             # posts變成Front_User的屬性

    # 實現將用戶輸入的content文件text類型轉換成content_html的html文件,再進行存儲
    @staticmethod
    def content_to_content_html(target, value, oldvalue, initiator):
        # content_html文件中允許使用的標籤集合
        allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'ol', 'pre',
                        'strong', 'ul', 'h1', 'h2', 'h3', 'p', 'img', 'video', 'div', 'iframe',
                        'p', 'br', 'span', 'hr', 'src', 'class']
        # content_html文件中允許使用的屬性
        allowed_attrs = {'*': ['class'],
                         'a': ['href', 'rel'],
                         'img': ['src', 'alt']}
        # 目標文件content_html,由bleach庫進行轉換         markdown將源文件顯示成html文件
        target.content_html = bleach.linkify(bleach.clean(
            markdown(value, output_format='html'),                                # output_format='html'輸出格式爲html
            tags=allowed_tags, strip=True, attributes=allowed_attrs))             # strip=True去空格
        
        
# 監聽PostModel.content文件如果調用了set方法,就調用content_to_content_html方法進行轉換格式到html文件
db.event.listen(PostModel.content, 'set', PostModel.content_to_content_html)

映射到數據庫中,同時需要修改下之前已經存在的數據:

在這裏插入圖片描述

2、帖子分類功能實現

前臺藍圖文件:apps/front/views.py,將評論數據和閱讀數據查詢,並傳遞到前端頁面渲染;針對不同功能選擇的狀態碼來選擇排序方式。

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen
前臺藍圖文件:apps/front/views.py
"""
# 前臺的藍圖文件  類視圖函數寫在這裏
from flask import (
    Blueprint,
    render_template,
    views,
    make_response,                  # make_response生成response對象,用於返回前端模板
    request,
    session,
    g,
)

# 導入圖像驗證碼生成文件
from utils.captcha import Captcha

# 圖形驗證碼image是二進制數據,需要轉換成字節流才能使用
from io import BytesIO

# 將圖形驗證碼保存到Redis         restful輸出信息彈窗
from utils import redis_captcha, restful

# 驗證碼錶單信息驗證
from .forms import (
    SignupForm,               # 註冊的Form表單信息收集
    SigninForm,               # 登錄的Form表單信息收集
    AddPostForm,              # 帖子提交表單信息
    AddCommentForm,           # 添加帖子評論
)

# 導入前臺用戶模型
from .models import (
    Front_User,
    PostModel,
    CommentModel,             # 評論模型
)

# 導入數據庫連接 db
from exts import db

# 確保URL安全的文件:utils/safe_url.py
from utils import safe_url

from apps.cms.models import (
    BannerModel,                      # 導入後臺輪播圖模型BannerModel
    BoardModel,                       # 導入後臺板塊管理模型
    HighlightPostModel,               # 帖子加精模型
)
# 導入分頁功能庫
from flask_paginate import Pagination, get_page_parameter
# 導入前臺界面權限驗證裝飾器
from .decorators import login_required
# 導入配置文件
import config
from sqlalchemy import func                      # 導入求和方法

front_bp = Blueprint("front", __name__)          # 前端不用前綴,直接在首頁顯示,front是藍圖,在front_signup.html調用生成圖形驗證碼時候需要用

# 權限驗證  需要在front_bp產生後,再導入
from .hooks import before_request


# BBS的首頁界面路由
@front_bp.route("/")
def index():
    banners = BannerModel.query.order_by(BannerModel.priority.desc()).limit(4)   # 通過權重查詢,每頁顯示4條
    boards = BoardModel.query.all()                                              # 查詢板塊中的所有
    board_id = request.args.get('board_id', type=int, default=None)              # get方法需要使用args,注意這裏的數據類型需要改成int
    
    # 接收參數st的狀態值,用於判斷不同種類的排序
    sort = request.args.get('st', type=int, default=1)
    
    page = request.args.get(get_page_parameter(), type=int, default=1)           # 獲取當前頁碼
    start = (page-1)*config.PER_PAGE                                             # 起始頁碼是(當前頁碼-1)*10
    end = start + config.PER_PAGE                                                # 每頁都是起始頁碼+10
    
    # 接收參數st,根據sort的參數不同,判斷不同種類的排序
    query_obj = None
    if sort == 1:                                                                # 用戶選擇最新帖子的順序
        query_obj = PostModel.query.order_by(PostModel.create_time.desc())       # 帖子創建時間倒序
    elif sort == 2:                                                              # 用戶選擇精華帖子的順序
        query_obj = db.session.query(PostModel).join(HighlightPostModel).order_by(  # 內關聯PostModel,HighlightPostModel交集
            HighlightPostModel.create_time.desc()                                   # 根據創建時間倒序排序
        )                     # 帖子加精模型
    elif sort == 3:                                                              # 用戶選擇閱讀最多的順序
        query_obj = PostModel.query.order_by(PostModel.read_count.desc())        # 查詢閱讀數量
    elif sort == 4:                                                              # 用戶選擇評論最多的順序
        query_obj = db.session.query(PostModel).join(CommentModel).group_by(
            PostModel.id                                                         # 先分組,根據不同帖子id進行分組再排序
        ).order_by(
            func.count(CommentModel.id).desc()                                   # 通過評論總數排序
        )
       
    # 實現根據不同board_id進行帖子分類顯示,即用戶選擇不同板塊,顯示的帖子種類相對應
    if board_id:
        posts = query_obj.filter(PostModel.board_id == board_id).slice(start, end)        # slice(start, end)分頁
        total = query_obj.filter(PostModel.board_id == board_id).count()                  # 計算該板塊的總數
    else:
        posts = query_obj.slice(start, end)                                    # 帖子信息傳輸,如果用戶不選擇板塊,查詢所有
        total = query_obj.count()                                              # 計算帖子總數

    # pagination是一個對象,bs_version=3是bootstrap的版本爲3,per_page參數添加,pagination.links正常顯示所有
    pagination = Pagination(bs_version=3, page=page, total=total,
                            per_page=config.PER_PAGE,                          # config.py中的每頁10條數據
                            inner_window=3, outer_window=1)                    # inner_window=3是內層顯示頁碼的樣式,默認爲2,
    # print(pagination.links)                                                  # 當數據量小的時候,不顯示,添加per_page參數就能解決
    
    context = {                                                                # 多種數據傳輸到前臺界面
        "banners": banners,
        "boards": boards,
        "current_board_id": board_id,
        "posts": posts,
        "pagination": pagination,
        "current_sort": sort,
        
    }
    
    return render_template("front/front_index.html", **context)            # 渲染到首頁界面,查詢數據傳輸到前臺界面


# 圖形驗證碼路由
@front_bp.route("/captcha/")
def graph_captcha():
    try:                                                 # 異常處理
        # 圖像驗證碼生成文件中返回兩個參數   text, image
        text, image = Captcha.gene_graph_captcha()      # 生成圖形驗證碼,image是二進制數據,需要轉換成字節流才能使用
        print("發送的圖形驗證碼是:{}".format(text))
        
        # 將圖形驗證碼保存到Redis數據庫中
        redis_captcha.redis_set(text.lower(), text.lower())  # redis_set中需要傳參key和value,text沒有唯一對應的key,只能都傳參text
        
        # BytesIO是生成的字節流
        out = BytesIO()
        image.save(out, 'png')                          # 把圖片image保存在字節流中,並指定爲png格式
        # 文件流指針
        out.seek(0)                                     # 從字節流最初開始讀取
        # 生成response對象,用於返回前端模板中
        resp = make_response(out.read())
        resp.content_type = 'image/png'                 # 指定數據類型
    except:
        return graph_captcha()                          # 沒有生成驗證碼就再調用一次
        
    return resp                                         # 返回對象


# 測試referrer的跳轉
@front_bp.route("/test/")
def test():
    return render_template("front/front_test.html")


# 用戶註冊類視圖
class SingupView(views.MethodView):
    def get(self):
        # 圖像驗證碼生成文件中返回兩個參數   text, image
        # text, image = Captcha.gene_graph_captcha()
        # print(text)                      # 驗證碼
        # print(image)                     # 圖形文件,圖形類<PIL.Image.Image image mode=RGBA size=100x30 at 0x1EFC9000C88>

        # 從當前頁面跳轉過來就是None   從其他頁面跳轉過來輸出就是上一個頁面信息     referrer是頁面的跳轉
        # print(request.referrer)                           # http://127.0.0.1:9999/test/
        
        return_to = request.referrer
        # 確保URL安全的文件:utils/safe_url.py
        print(safe_url.is_safe_url(return_to))              # 判斷return_to是否來自站內,是否是安全url,防爬蟲
        
        if return_to and return_to != request.url and safe_url.is_safe_url(return_to):       # 跳轉的url不能是當前頁面,request.url是當前的url地址
            return render_template("front/front_signup.html", return_to=return_to)           # return_to渲染到前端界面
        else:
            return render_template("front/front_signup.html")                                # 如果沒獲取url,直接渲染註冊界面
        
    # 驗證碼的form表單信息提交驗證
    def post(self):
        form = SignupForm(request.form)                       # 收集表單信息
        
        # 表單驗證通過
        if form.validate():
            # 保存到數據庫
            telephone = form.telephone.data
            username = form.username.data
            password = form.password1.data                    # forms表單信息
            
            # 前臺用戶模型數據添加到數據庫
            user = Front_User(telephone=telephone, username=username, password=password)
            db.session.add(user)
            db.session.commit()                                                   # 提交到數據庫
            
            # 表單驗證通過,提交到數據庫成功
            return restful.success()
        else:
            return restful.params_error(message=form.get_error())                  # 表單信息驗證出錯


# 用戶登錄的類視圖
class SinginView(views.MethodView):
    def get(self):
        return_to = request.referrer                                                    # referrer是上一個url
    
        if return_to and return_to != request.url and safe_url.is_safe_url(return_to):  # 跳轉的url不能是當前頁面,判斷url是否安全
            return render_template("front/front_signin.html", return_to=return_to)      # return_to渲染到前端界面
        else:
            return render_template("front/front_signin.html")                           # 如果沒獲取url,直接渲染註冊界面
    
    def post(self):
        form = SigninForm(request.form)                                            # 登錄界面的Form表單信息
        
        if form.validate():                                                        # 表單信息存在
            # 收集form表單信息
            telephone = form.telephone.data
            password = form.password.data
            remember = form.remember.data
            
            user = Front_User.query.filter_by(telephone=telephone).first()         # 通過手機號驗證該用戶是否存在數據庫
            if user and user.check_password(password):                             # 判斷密碼和用戶是否正確
                # 'front_user_id'命名防止與後臺驗證session相同,會產生覆蓋情況bug
                session['front_user_id'] = user.id                                 # 用戶的id存儲到session中,用於登錄驗證
                if remember:                                                       # 如果remember狀態是1
                    # session持久化
                    session.permanent = True
                return restful.success()                                           # 成功
            else:
                return restful.params_error(message="手機號或者密碼錯誤")           # 密碼是、用戶不正確
        else:
            return restful.params_error(message=form.get_error())                  # 表單信息不存在,輸出異常信息
        

#  帖子編輯提交  的類視圖     富文本編輯
class PostView(views.MethodView):
    # 登錄驗證,實現帖子編輯前進行權限驗證
    decorators = [login_required]
    # 表單信息收集,傳輸
    def get(self):
        # 查詢boards數據進行傳輸
        boards = BoardModel.query.all()                                           # boards是list類型
        return render_template("front/front_apost.html", boards=boards)           # boards數據傳輸到前端front_apost.html頁面
    
    # 帖子的Form表單信息收集查詢
    def post(self):
        form = AddPostForm(request.form)                     # 查詢帖子提交的Form表單信息
        if form.validate():
            title = form.title.data
            board_id = form.board_id.data                    # 收集表單中提交的信息
            content = form.content.data
            
            # 查詢用戶信息是否在數據庫中存在
            board = BoardModel.query.get(board_id)
            if not board:
                return restful.params_error(message="沒有這個版塊名稱")           # 數據庫中不存在,返回異常信息
            
            # 數據庫中board信息存在,傳輸數據到數據庫表中,並修改名稱
            post = PostModel(title=title, board_id=board_id, content=content)
            post.board = board                                                   # 外鍵中的信息修改賦值
            post.author = g.front_user                                           # g對象

            db.session.add(post)
            db.session.commit()
            return restful.success()                                             # 提交成功,爲json數據
        else:
            return restful.params_error(message=form.get_error())
            

# 前臺 帖子詳情 路由
@front_bp.route("/p/<post_id>")                  # 蹄子詳情路由需要傳參帖子id:post_id
def post_detail(post_id):
    post = PostModel.query.get(post_id)          # 通過post_id查找數據庫中的帖子信息
    if not post:
        return restful.params_error(message="帖子不存在!")
    
    # 閱讀數量計數
    post.read_count += 1
    db.session.commit()
    
    # 評論數量查詢,先查詢帖子模型中的id
    comment_count = db.session.query(PostModel).filter(PostModel.id == post_id).join(
        CommentModel                                                                     # 關聯查詢評論模型
    ).count()                                                                            # 再整體求和
    return render_template("front/front_detail.html", post=post, comment_count=comment_count)  # 查找到帖子信息,傳輸數據到帖子詳情頁渲染
 

# 添加評論  的路由
@front_bp.route("/acomment/", methods=['POST'])
@login_required                                      # 登錄驗證
def add_comment():
    form = AddCommentForm(request.form)              # 網頁發送的request.form表單信息放入AddCommentForm進行驗證
    if form.validate():
        content = form.content.data                  # form表單信息
        post_id = form.post_id.data
        
        post = PostModel.query.get(post_id)          # 通過post_id查詢帖子信息
        if post:
            comment = CommentModel(content=content)  # 將AddCommentForm中驗證後的content信息傳給CommentModel模型
            # 外鍵關聯,反向屬性backref,從CommentModel中調用
            comment.post = post                      # 外鍵關聯的是post表中的id字段
            comment.author = g.front_user            # 將apps/front/hooks.py中的g對象賦值外鍵的作者的id
            db.session.add(comment)                  # 添加對象信息到數據庫
            db.session.commit()
            return restful.success()                 # 提交成功
        else:
            return restful.params_error(message="沒有這篇帖子")  # 數據庫中查詢不到信息
    else:
        return restful.params_error(message=form.get_error())   # 表單驗證失敗
            
        
# 綁定類視圖的路由
front_bp.add_url_rule("/signup/", view_func=SingupView.as_view("signup"))          # "signup"視圖中不需要反斜線,決定了url_for的路由地址
front_bp.add_url_rule("/signin/", view_func=SinginView.as_view("signin"))          # "signin"視圖中不需要反斜線
front_bp.add_url_rule("/apost/", view_func=PostView.as_view("apost"))              # 綁定帖子編輯提交路由

前臺首頁頁面文件:templates/front/front_index.html,接收傳遞的評論數據和閱讀數據,不同排序方式的狀態碼定義。

<!-- 前臺首頁頁面文件:templates/front/front_index.html  -->

{% extends 'front/front_base.html' %}

{% block title %}
首頁
{% endblock %}

<!-- 模板繼承 -->
{% block main_content %}
<!--   居中樣式  -->
<div class="main-container">
    <div class="lg-container">
        <!-- bootstrop中複製來的輪播圖  -->
        <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
            <!-- 指令 -->
            <ol class="carousel-indicators">
                <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
                <li data-target="#carousel-example-generic" data-slide-to="1"></li>
                <li data-target="#carousel-example-generic" data-slide-to="2"></li>
            </ol>

            <!-- 輪播圖 -->
            <div class="carousel-inner" role="listbox">
                <!--    循環apps/front/views.py文件傳輸的banners數據      -->
                {% for banner in banners %}
                <!--    判斷是否第一次循環      -->
                {% if loop.first %}
                <div class="item active">
                    {% else %}
                    <div class="item">
                        {% endif %}
                        <!--    輪播圖路徑,style="width: 300px;height: 300px"輪播圖大小 -->
                        <img src="{{ banner.image_url }}" alt="..." style="width: 300px;height: 300px">
                        <div class="carousel-caption">
                        </div>
                    </div>
                    {% endfor %}
                </div>

                <!-- 輪播圖左右切換按鈕 -->
                <a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
                    <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
                    <span class="sr-only">Previous</span>
                </a>
                <a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
                    <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
                    <span class="sr-only">Next</span>
                </a>
            </div>
            <!-- bootstrop中複製來的輪播圖 代碼結束   -->

            <!--   帖子排序方式     -->
            <div class="post-group">
                <ul class="post-group-head">
                    <!--        根據不同種類進行排序,url中傳輸兩個參數st和 board_id           -->
                    {% if current_sort == 1 %}
                        <li class="active"><a href="{{ url_for('front.index', st=1, board_id=current_board) }}">最新</a></li>
                    {% else %}
                        <li class=""><a href="{{ url_for('front.index', st=1, board_id=current_board) }}">最新</a></li>
                    {% endif %}

                    {% if current_sort == 2 %}
                        <li class="active"><a href="{{ url_for('front.index', st=2, board_id=current_board) }}">精華帖子</a></li>
                    {% else %}
                        <li class=""><a href="{{ url_for('front.index', st=2, board_id=current_board) }}">精華帖子</a></li>
                    {% endif %}

                    {% if current_sort == 3 %}
                        <li class="active"><a href="{{ url_for('front.index', st=3, board_id=current_board) }}">閱讀最多</a></li>
                    {% else %}
                        <li class=""><a href="{{ url_for('front.index', st=3, board_id=current_board) }}">閱讀最多</a></li>
                    {% endif %}

                    {% if current_sort == 4 %}
                        <li class="active"><a href="{{ url_for('front.index', st=4, board_id=current_board) }}">評論最多</a></li>
                    {% else %}
                        <li class=""><a href="{{ url_for('front.index', st=4, board_id=current_board) }}">評論最多</a></li>
                    {% endif %}

                </ul>
                <ul class="post-list-group">
                    <!--         循環帖子信息,首頁渲染           -->
                    {% for post in posts %}
                        <li>
                        <div class="author-avatar-group">
                            <img src="#" alt="">
                        </div>
                        <div class="post-info-group">
                            <p class="post-title">
                                <!--  front.post_detail反轉需要寫的是路由的函數名,post_id=post.id傳輸帖子id,post_id是名字,post.id是value      -->
                                <a href="{{ url_for('front.post_detail', post_id=post.id) }}">{{ post.title }}</a>

                            <!--   精華帖判定條件:當前帖子存在highlight這個外聯屬性     -->
                                {% if post.highlight %}
                                    <span class="label label-danger">精華帖</span>
                                {% endif %}
                            </p>
                            <p class="post-info">
                                <!-- post模型中的author外鍵調用Front_User中的username信息  -->
                                <span>作者:{{ post.author.username }}</span>
                                <span>發表時間:{{ post.create_time }}</span>
                                <span>評論:{{ comment_count }}</span>
                                <span>閱讀:{{ post.read_count }}</span>
                            </p>
                        </div>
                    </li>
                    {% endfor %}

                </ul>
                <div style="text-align:center;">
                <!--       頁碼分頁展示, pagination.links數據由 apps/front/views.py傳輸過來           -->
                    {{ pagination.links }}
                </div>
            </div>
        </div>

        <!--      帖子標籤內容      -->
        <div class="sm-container">
            <div style="padding-bottom:10px;">
                <!--       重定向到/apost/路由,文本編輯界面        -->
                <a href="{{ url_for('front.apost') }}" class="btn btn-warning btn-block">發佈帖子</a>
            </div>
            <div class="list-group">
                <!--      保留st=current_sort用戶當前選擇的排序方式          -->
                <a href="{{ url_for('front.index', st=current_sort ) }}" class="list-group-item active">所有板塊</a>
                <!--     循環顯示前臺藍圖文件:apps/front/views.py中傳輸的數據**context           -->
                {% for board in boards %}
                    <!--         注意這裏的current_board_id數據類型是int,才能與board.id相比較           -->
                    {% if current_board_id == board.id %}
                        <!--    url_for('front.index', board_id=board.id)每次點擊跳轉到front_index.html頁面,即當前界面,且傳輸給一個board_id的參數值,由board.id賦值            -->
                        <a href="{{ url_for('front.index', board_id=board.id, st=current_sort ) }}" class="list-group-item active">{{ board.name }}</a>
                    {% else %}
                        <!--  沒被選中,即沒有被傳輸相同的board.id,圖標樣式是class="list-group-item">    -->
                        <a href="{{ url_for('front.index', board_id=board.id, st=current_sort  ) }}" class="list-group-item">{{ board.name }}</a>
                    {% endif %}
                {% endfor %}
            </div>
        </div>
    </div>
    <!--  居中樣式  -->
{% endblock %}

帖子詳情頁面文件:templates/front/front_detail.html,接收傳遞的評論數據和閱讀數據。

<!-- 帖子詳情頁面文件:templates/front/front_detail.html  -->
{% extends 'front/front_base.html' %}

{% block title %}帖子詳情{% endblock %}

{% block head %}
    <!-- 百度的富文本編輯器加載 -->
    <script src="{{ url_for('static', filename='ueditor/ueditor.config.js') }}"></script>
    <script src="{{ url_for('static', filename='ueditor/ueditor.all.min.js') }}"></script>
    <link rel="stylesheet" href="{{ url_for('static', filename='front/css/front_pdetail.css') }}">
    <script src="{{ url_for('static', filename='front/js/front_pdetail.js') }}"></script>
{% endblock %}

{% block main_content %}
<div class="main-container">
    <div class="lg-container">
        <div class="post-container">
            <!--      帖子標題,前臺藍圖文件:apps/front/views.py中的路由定義中傳輸過來      -->
            <h2>{{ post.title }}</h2>
            <p class="post-info-group">
                <span>發表時間:{{ post.create_time }}</span>
                <!--  author和board這兩個字段是PostModel的外鍵,關聯了Front_User和BoardModel模型中的username、name字段       -->
                <span>作者:{{ post.author.username }}</span>
                <span>所屬板塊:{{ post.board.name }}</span>
                <span>閱讀數:{{ post.read_count }}</span>
                <span>評論數:{{ comment_count }}</span>
            </p>
            <!--     data-id="{{ post.id }} 傳輸帖子id到front_pdetail.js文件進行獲取      -->
            <article class="post-content" id="post-content" data-id="{{ post.id }}">
                <!--   safe用於轉義成安全字符串,content_html才能在頁面渲染出標籤的效果,content中包含有標籤內容         -->
                {{ post.content_html|safe }}
            </article>
        </div>
        <div class="comment-group">
            <h3>評論列表</h3>
            <ul class="comment-list-group">
                <!--  comments是反向引用的屬性  -->
                {% for comment in post.comments %}
                    <li>
                        <div class="avatar-group">
                            <img src="{{ url_for('static', filename='common/images/logo.png') }}"
                                 alt="">
                        </div>
                        <div class="comment-content">
                            <p class="author-info">
                                <!--   comment.author外鍵,從CommentModel中調用    -->
                                <span>{{ comment.author.username }}</span>
                                <span>{{ comment.create_time }}</span>
                            </p>
                            <p class="comment-txt">
                                {{ comment.content|safe }}
                            </p>
                        </div>
                    </li>
                {% endfor %}

            </ul>
        </div>
        <div class="add-comment-group">
            <h3>發表評論</h3>
            <!--   這是綁定front_pdetail.js中的百度文本編輯器的id="editor",這裏的標籤是 script       -->
            <script id="editor" type="text/plain" style="height:100px;"></script>

            <div class="comment-btn-group">
                <!--    綁定id="comment-btn"  -->
                <button class="btn btn-primary" id="comment-btn">發表評論</button>
            </div>
        </div>
    </div>

    <div class="sm-container"></div>
</div>
{% endblock %}

3、項目的完善

1、前臺用戶的導航欄功能

前臺模板頁面文件:templates/front/front_base.html,重定向到不同路由界面。

<!-- 前臺模板頁面文件:templates/front/front_base.html  -->

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!--  在頭文件中接收csrf信息  -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{% block title %} {% endblock %}</title>

    <!--  bootstrap支持css,js的樣式  -->
    <script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

    <!--  關聯front_index.html的css樣式  -->
    <link href="{{ url_for('static', filename='front/css/front_index.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='front/css/front_base.css') }}" rel="stylesheet">

    <!--  提示框的靜態資源文件  -->
    <link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
    <!-- 關聯提示框的js樣式  -->
    <script src="{{ url_for('static', filename='common/sweetalert/lgalert.js') }}"></script>
    <script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}"></script>
    <!-- 導入lgajax.js文件  -->
    <script src="{{ url_for('static', filename='common/lgajax.js') }}"></script>

    <!--  模板繼承,補充內容使用  -->
    {% block head %}

    {% endblock %}
</head>
<body>
<!--  bootstrop中複製來的導航條  -->
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">BBS論壇</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="/">首頁 <span class="sr-only">(current)</span></a></li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">搜索</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
                <!--     判斷是否登錄,沒有g對象信息就進行登錄           -->
                {% if g.front_user %}
                <li class="dropdown">
                    <!--   {{ g.front_user.username }}   顯示g對象信息 -->
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">{{ g.front_user.username }}<span class="caret"></span></a>

                    <ul class="dropdown-menu">
                        <li><a href="{{ url_for('cms.profile') }}">個人中心</a></li>
                        <li><a href="{{ url_for('cms.index') }}">設置</a></li>
                        <li><a href="{{ url_for('front.index') }}">退出登錄</a></li>
                    </ul>
                </li>
                {% else %}
                <!--    關聯登錄註冊的url,front.signin是類視圖中路由決定     -->
                <li><a href="{{ url_for('front.signin') }}">登陸</a></li>
                <li><a href="{{ url_for('front.signup') }}">註冊</a></li>
                {% endif %}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<!--   bootstrop中複製的導航條代碼結束 -->

<!-- 模板繼承 -->
{% block main_content %}

{% endblock %}
</body>
</html>

2、評論前驗證登錄權限


3、帖子詳情頁面樣式修改

前臺帖子詳情頁面樣式文件:static/front/css/front_pdetail.css

/**
 * // 前臺帖子詳情頁面樣式文件:static/front/css/front_pdetail.css
 */
.post-container{
    border: 1px solid #e6e6e6;
    padding: 10px;
}

.post-info-group{
    font-size: 12px;
    color: #8c8c8c;
    border-bottom: 1px solid #e6e6e6;
    margin-top: 15px;
    padding-bottom: 10px;
}

.post-info-group span{
    margin-right: 20px;
}

.post-content{
    margin-top: 50px;
}

.post-content img{
    max-width: 100%;
}

.comment-group{
    margin-top: 20px;
    border: 1px solid #e8e8e8;
    padding: 10px;
}

.add-comment-group{
    margin-top: 20px;
    padding: 10px;
    border: 1px solid #e8e8e8;
}

.add-comment-group h3{
    margin-bottom: 10px;
}

.comment-btn-group{
    margin-top: 10px;
    text-align:right;
}

.comment-list-group li{
    overflow: hidden;
    padding: 10px 0;
    border-bottom: 1px solid #e8e8e8;
}

.avatar-group{
    float: left;
}

.avatar-group img{
    width: 50px;
    height: 50px;
    border-radius: 50%;
}

.comment-content{
    float: left;
    margin-left:10px;
}

.comment-content .author-info{
    font-size: 12px;
    color: #8c8c8c;
}

.author-info span{
    margin-right: 10px;
}

.comment-content .comment-txt{
    margin-top: 10px;
}

4、引入Celery異步發送郵件

Celery是Python開發的分佈式任務調度模塊,Celery本身不含消息服務,它使用第三方消息服務來傳遞任務。單個 Celery 進程每分鐘可處理數以百萬計的任務,而保持往返延遲在亞毫秒級。目前,Celery支持的消息服務有RabbitMQ、Redis甚至是數據庫,當然Redis是最佳選擇。
在這裏插入圖片描述
在這裏插入圖片描述
安裝Celery

pip install Celery

clery的任務執行文件:task.py

# -*- encoding: utf-8 -*-
"""
@File    : task.py
@Time    : 2020/6/6 20:37
@Author  : chen
clery的任務執行文件:task.py
"""
from celery import Celery
from flask_mail import Message
from exts import mail
from flask import Flask
import config

app = Flask(__name__)                 # 新創建一個app用於執行clery異步發送,爲了防止互相引用

mail.init_app(app)                    # 初始化


def make_celery(app):                 # 使用clery運行app
    celery = Celery(
        app.import_name,
        backend=config.CELERY_BROKER_URL,
        broker=config.CELERY_RESULT_BACKEND
        # backend=app.config['CELERY_RESULT_BACKEND'],
        # broker=app.config['CELERY_BROKER_URL']
    )
    celery.conf.update(app.config)              # 導入配置文件

    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)

    celery.Task = ContextTask
    return celery


celery = make_celery(app)


@celery.task
def send_mail(subject, recipients, body):
    message = Message(subject=subject, recipients=recipients, body=body)
    mail.send(message)


項目配置文件:config.py

# -*- encoding: utf-8 -*-
"""
@File    : config.py
@Time    : 2020/5/11 10:08
@Author  : chen
項目配置文件:config.py
"""

CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/0"

Redis服務開啓:
在這裏插入圖片描述
視圖文件:apps/cms/views.py文件,修改發送郵件的方法,改爲clery模塊中的send_mail方法異步發送。

# -*- encoding: utf-8 -*-
"""
@File    : views.py
@Time    : 2020/5/11 9:59
@Author  : chen
視圖文件:apps/cms/views.py文件
"""
# 藍圖文件:實現模塊化應用,應用可以分解成一系列的藍圖   後端的類視圖函數寫在這個文件
from flask import (
    request, redirect, url_for,                      # 頁面跳轉redirect   request請求收集
    Blueprint, render_template, views, session,      # 定義類視圖,顯示模板文件
    jsonify, g                                       # jsonify強制轉換成json數據
)
from exts import db, mail                            # 數據庫中更新密碼、郵箱等使用

from apps.cms.forms import (
    LoginForm, ResetPwdForm,                        # ResetPwdForm修改密碼的form信息
    ResetEmailForm,                                 # 導入forms.py文件中的郵箱驗證的表單信息類
    AddBannerForm,                                  # 導入 添加輪播圖 的表單信息
    UpdateBannerForm,                               # 導入 更新輪播圖 的表單信息
    AddBoardsForm,                                  # 導入 增加板塊管理 的表單信息
    UpdateBoardsForm,                               # 導入 編輯板塊管理 的表單信息
)

from apps.cms.models import (
    CMS_User,                                       # 後臺用戶模型
    CMSPersmission,                                 # CMSPersmission驗證用戶不同模塊權限
    CMSRole,                                        # 用戶角色模型
    BannerModel,                                    # 導入 輪播圖模型BannerModel
    BoardModel,                                     # 導入 板塊管理模型
    HighlightPostModel,                             # 帖子加精模型
)
# 導入 帖子 模型文件
from apps.front.models import PostModel

from .decorators import permission_required            # 傳參裝飾器驗證用戶不同模塊權限

# 導入裝飾器:判斷當前界面是否是登錄界面,不是就將url重定向到登錄界面,一般不用,使用的主要是鉤子函數
from .decorators import login_required

# 導入restful.py中的訪問網頁狀態碼的函數          redis_captcha:redis存儲、提取、刪除驗證碼功能
from utils import restful, random_captcha, redis_captcha           # 隨機生成驗證碼函數random_captcha()

# 導入flask-mail中的Message
from flask_mail import Message

# 導入clery的異步發送郵件模塊
from task import send_mail

cms_bp = Blueprint("cms", __name__, url_prefix='/cms/')     # URL前綴url_prefix

# 鉤子函數是在cms_bp創建之後才創建的,順序在cms_bp創建之後
from .hooks import before_request


@cms_bp.route("/")                                          # 後臺界面
# @login_required             # 裝飾器判定當前界面是否是登錄界面,但是需要每個路由函數都要加該裝飾器,比較麻煩,推薦使用鉤子函數
def index():
    # return "cms index:後端類視圖文件"
    return render_template('cms/cms_index.html')  # 登陸之後進入CMS後臺管理界面


# 用戶註銷登錄
@cms_bp.route("/logout/")                              # 需要關聯到cms/cms_index.html中的註銷屬性
def logout():
    # session清除user_id
    del session['user_id']
    # 重定向到登錄界面
    return redirect(url_for('cms.login'))             # 重定向(redirec)爲把url變爲重定向的url


# 定義個人中心的路由
@cms_bp.route("/profile/")
def profile():
    return render_template("cms/cms_profile.html")   # 模板渲染(render_template)則不會改變url,模板渲染是用模板來渲染請求的url


# 定義類視圖,顯示模板文件   用戶登錄功能實現
class LoginView(views.MethodView):
    def get(self, message=None):                                         # message=None時候不傳輸信息到cms_login.html頁面
        return render_template("cms/cms_login.html", message=message)    # 針對post方法中同樣要返回到cms_login.html頁面進行代碼簡化
    
    # 用戶登錄操作驗證
    def post(self):
        # 收集表單信息
        login_form = LoginForm(request.form)
        if login_form.validate():
            # 數據庫驗證
            email = login_form.email.data
            password = login_form.password.data
            remember = login_form.remember.data
            
            # 查詢數據庫中的用戶信息
            user = CMS_User.query.filter_by(email=email).first()    # 郵箱唯一,用於查詢驗證用戶
            if user and user.check_password(password):              # 驗證用戶和密碼是否都正確
                session['user_id'] = user.id                        # 查詢到用戶數據時,保存session的id到瀏覽器
                # session['user_name'] = user.username                # 將數據庫中的user.username保存到session中,在hooks.py中判斷
                # session['user_email'] = user.email                  # 將數據庫中的email保存到session中,方便html調用信息
                # session['user_join_time'] = user.join_time          # 將數據庫中的join_time保存到session中,方便html調用信息
                
                if remember:                                        # 如果用戶點擊了remember選擇,在瀏覽器中進行數據持久化
                    session.permanent = True                        # 數據持久化,默認31天,需要設置session_key在config.py中
            
                # 登錄成功,跳轉到後臺首頁
                return redirect(url_for('cms.index'))               # 在藍圖中必須加cms   跳轉到index方法
            else:
                # return "郵箱或密碼錯誤"                              # 登錄出錯,返回結果
                # return render_template("cms/cms_login.html", message="郵箱或密碼錯誤")  # 登錄出錯,返回結果渲染到cms_login.html頁面
                return self.get(message="郵箱或密碼錯誤")             # 傳參到get方法中,多加一個傳輸錯誤信息的參數到方法中
        else:
            # print(login_form.errors)                                 # forms.py中的錯誤信息  字典類型數據
            # print(login_form.errors.popitem())                       # forms.py中的錯誤信息  元祖類型數據
            # return "表單驗證錯誤"                                     # 錯誤信息需要渲染到cms_login.html頁面
            # return self.get(message=login_form.errors.popitem()[1][0])  # 字典類型數據信息提取
            return self.get(message=login_form.get_error())            # login_form是收集到的表單信息,信息提取放置到forms.py的父類中實現
    
    
# 修改密碼的類視圖驗證
class ResetPwd(views.MethodView):
    def get(self):
        return render_template('cms/cms_resetpwd.html')         # 模板渲染到cms_resetpwd.html
    
    # post提交密碼修改
    def post(self):
        # 先審查舊密碼是否與數據庫中的信息相同
        form = ResetPwdForm(request.form)
        if form.validate():
            oldpwd = form.oldpwd.data
            newpwd = form.newpwd.data
            # 對象
            user = g.cms_user
            # 將用戶輸入的密碼進行加密檢測是否與數據庫中的相同
            if user.check_password(oldpwd):
                # 更新我的密碼  將新密碼賦值,此時的新密碼已經經過驗證二次密碼是否一致
                user.password = newpwd         # user.password已經調用了models.py中的 @property裝飾器進行密碼加密
                # 數據庫更新
                db.session.commit()
                # return jsonify({"code": 400, "message": "密碼修改成功"})        # 代碼改寫爲下面
                return restful.success("密碼修改成功")             # 調用restful.py中定義的訪問網頁成功的函數
            else:
                # 當前用戶輸入的舊密碼與數據庫中的不符
                # return jsonify({"code": 400, "message": "舊密碼輸入錯誤"})
                return restful.params_error(message="舊密碼輸入錯誤")      # 參數錯誤
        else:
            # ajax 需要返回一個json類型的數據
            # message = form.errors.popitem()[1][0]                     # 收集錯誤信息
            # return jsonify({"code": 400, "message": message})         # 將數據轉換成json類型
            return restful.params_error(message=form.get_error())       # 參數錯誤,信息的收集在forms.py的父類函數中實現  form是收集到的信息
        

# 定義修改郵箱的類視圖 驗證
class ResetEmail(views.MethodView):
    def get(self):
        return render_template("cms/cms_resetemail.html")      # 返回到修改郵箱頁面url
    
    def post(self):
        form = ResetEmailForm(request.form)                    # 接收郵箱驗證的form表單信息
        if form.validate():                                    # 驗證表單信息是否通過
            email = form.email.data                            # 獲取form表單中填寫的郵箱地址
            
            # 查詢數據庫
            # CMS_User.query.filter_by(email=email).first()
            # CMS_User.query.filter(CMS_User.email == email).first()
            g.cms_user.email = email                           # 數據庫中的查詢在apps/cms/hooks.py文件中確定了該用戶的數據庫信息,用全局對象g.cms_user修改郵箱
            db.session.commit()
            return restful.success()                           # 郵箱修改成功
        else:
            return restful.params_error(form.get_error())      # form是這個類中的所有表單信息
        
        
# 發送測試郵件進行驗證
@cms_bp.route("/send_email/")
def send_mail_1():                                                  # 防止與clery中異步發送郵件方法名相同
    message = Message('郵件發送', recipients=['[email protected]'], body='測試郵件發送')   # 主題:郵件發送;收件人:recipients;郵件內容:測試郵件發送
    mail.send(message)                   # 發送郵件
    return "郵件已發送"


# 郵件發送
class EmailCaptcha(views.MethodView):
    def get(self):                                  # 根據resetemail.js中的ajax方法來寫函數,不需要post請求
        email = request.args.get('email')           # 查詢email參數是否存在
        if not email:
            return restful.params_error('請傳遞郵箱參數')
        
        # 發送郵件,內容爲一個驗證碼:4、6位數字英文組合
        captcha = random_captcha.get_random_captcha(4)            # 生成4位驗證碼
        # message = Message('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)
        
        # 異常處理
        try:
            send_mail.delay('BBS論壇郵箱驗證碼', recipients=[email], body='您的驗證碼是:%s' % captcha)   # 引用clery異步發送郵件
            # mail.send(message)
        except:
            return restful.server_error(message="服務器錯誤,郵件驗證碼未發送!")   # 發送異常,服務器錯誤
        
        # 驗證碼保存,一般有時效性,且頻繁請求變化,所以保存在Redis中
        redis_captcha.redis_set(key=email, value=captcha)        # redis中都是鍵值對類型,存儲驗證碼
        return restful.success("郵件驗證碼發送成功!")
    

# 輪播圖管理路由
@cms_bp.route("/banners/")
def banners():
    # 通過模型中定義的權重priority的倒敘來排序
    banners = BannerModel.query.order_by(BannerModel.priority.desc()).all()
    return render_template("cms/cms_banners.html", banners=banners)           # 傳輸banners數據到cms_banners.html界面渲染


# 添加輪播圖功能路由,且方法需要與static/cms/js/banners.js中綁定的方法POST相同
@cms_bp.route("/abanner/", methods=['POST'])
def abanner():
    form = AddBannerForm(request.form)                  # 接收添加輪播圖的form表單信息
    if form.validate():
        name = form.name.data
        image_url = form.image_url.data
        link_url = form.link_url.data
        priority = form.priority.data
        
        banner = BannerModel(name=name, image_url=image_url, link_url=link_url, priority=priority)     # 輪播圖模型
        db.session.add(banner)                                                                         # 提交數據庫
        db.session.commit()
        return restful.success()                                                                       # 輪播圖信息提交成功
    else:
        return restful.params_error(message=form.get_error())                                          # 表單信息錯誤


# 修改 輪播圖 路由,方法與static/cms/js/banners.js中綁定的方法POST相同
@cms_bp.route("/ubanner/", methods=['POST'])
def ubanner():
    # 修改根據banner_id查詢再修改
    form = UpdateBannerForm(request.form)             # 表單信息UpdateBannerForm中的request
    if form.validate():                                # 先查詢頁面表單信息是否存在
        banner_id = form.banner_id.data               # 收集用戶輸入的表單信息
        name = form.name.data
        image_url = form.image_url.data
        link_url = form.link_url.data
        priority = form.priority.data
        
        banner = BannerModel.query.get(banner_id)     # 通過輪播圖的模型BannerModel的banner_id查詢數據庫中輪播圖對象
        if banner:                                     # 再查詢數據庫對象數據是否存在
            banner.name = name                        # 將UpdateBannerForm中收集到的form信息命名給數據庫中的banner對象
            banner.image_url = image_url
            banner.link_url = link_url
            banner.priority = priority
            
            db.session.commit()                       # 數據庫信息直接提交修改即可,不用添加新的對象
            return restful.success()
        else:
            return restful.params_error(message=form.get_error())    # 表單信息錯誤
    

# 刪除  輪播圖路由,路由命名與banners.js綁定
@cms_bp.route("/dbanner/", methods=['POST'])
def dbanner():
    '''
    request.form.get("key", type=str, default=None)      獲取表單數據
    request.args.get("key")                              獲取get請求參數
    request.values.get("key")                            獲取所有參數
    '''
    # 修改根據banner_id查詢再修改,獲取post請求參數         get請求方式使用request.args.get()
    banner_id = request.form.get('banner_id')            # 獲取表單數據,這裏沒有單獨創建刪除的Form表單,使用之前創建的
    if not banner_id:
        return restful.params_error(message="輪播圖不存在")
    
    banner = BannerModel.query.get(banner_id)           # 根據banner_id查詢數據庫
    if banner:
        db.session.delete(banner)                       # 刪除該banner
        db.session.commit()
        return restful.success()                        # 返回成功
    else:
        return restful.params_error("輪播圖不存在")      # 根據banner_id查詢數據庫信息不存在
    

# 帖子管理路由 ,需要和cms_base.js中命名的相同纔可以
@cms_bp.route("/posts/")
@permission_required(CMSPersmission.POSTER)                # 傳參裝飾器驗證不同用戶不同模塊權限
def posts():
    posts = PostModel.query.all()                          # 數據庫查詢帖子信息,進行傳輸到後端頁面cms_posts.html渲染
    return render_template("cms/cms_posts.html", posts=posts)
    

# 帖子 加精的 後臺管理,路由名稱在static/cms/js/posts.js文件定義好了
@cms_bp.route("/hpost/", methods=['POST'])                # 方法確定爲post方式,默認支持的是get方法
@permission_required(CMSPersmission.POSTER)                # 傳參裝飾器驗證不同用戶不同模塊權限
def hpost():
    # 接收外鍵,post接收方式使用form
    post_id = request.form.get("post_id")                  # 接收post_id進行查詢
    if not post_id:
        return restful.params_error(message="請輸入帖子ID")
    
    post = PostModel.query.get(post_id)                    # 從帖子的數據表中查找該帖子對象
    if not post:
        return restful.params_error(message="沒有這篇帖子")
    
    highlight = HighlightPostModel()                      # 創建模型
    highlight.post = post                                 # 外鍵關聯,加精帖子補充到新的表中
    db.session.add(highlight)
    db.session.commit()                                   # 提交
    return restful.success()                              # 加精成功,視圖函數必須有返回值


# 帖子 取消加精的 後臺管理,路由名稱在static/cms/js/posts.js文件定義好了
@cms_bp.route("/uhpost/", methods=["POST"])               # 方法確定爲post方式,默認支持的是get方法
@permission_required(CMSPersmission.POSTER)                # 傳參裝飾器驗證不同用戶不同模塊權限
def uhpost():
    # 接收外鍵,post接收方式使用form
    post_id = request.form.get("post_id")                  # 接收post_id進行查詢
    if not post_id:
        return restful.params_error(message="請輸入帖子ID")
    
    post = PostModel.query.get(post_id)                     # 從帖子的數據表中查找該帖子對象
    if not post:
        return restful.params_error(message="沒有這篇帖子")
    
    highlight = HighlightPostModel.query.filter_by(post_id=post_id).first()
    db.session.delete(highlight)
    db.session.commit()                                     # 提交
    return restful.success()                                # 視圖函數必須有返回值


# 評論管理路由
@cms_bp.route("/comments/")
@permission_required(CMSPersmission.COMMENTER)             # 傳參裝飾器驗證不同用戶不同模塊權限
def comments():
    return render_template("cms/cms_comments.html")


# 板塊管理路由
@cms_bp.route("/boards/")
@permission_required(CMSPersmission.BOARDER)               # 傳參裝飾器驗證不同用戶不同模塊權限
def boards():
    boards = BoardModel.query.all()                        # 數據庫查詢所有板塊名稱
    return render_template("cms/cms_boards.html", boards=boards)        # 數據渲染到cms_boards.html


# 增加 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/aboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER)               # 傳參裝飾器驗證不同用戶不同模塊權限
def aboards():
    form = AddBoardsForm(request.form)                     # 表單信息傳輸過來,方便修改調用
    if form.validate():
        name = form.name.data                              # 表單信息收集
        
        board = BoardModel(name=name)                      # 添加信息到板塊模型中
        db.session.add(board)
        db.session.commit()                                # 提交數據庫
        return restful.success()                           # 數據庫添加成功
    else:
        return restful.params_error(message=form.get_error())    # 表單信息錯誤


# 編輯 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/uboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER)             # 傳參裝飾器驗證不同用戶不同模塊權限
def uboards():
    form = UpdateBoardsForm(request.form)                # 表單信息傳輸過來,方便修改調用
    if form.validate():
        board_id = form.board_id.data                    # 表單信息收集
        name = form.name.data
        
        board = BoardModel.query.get(board_id)           # 根據表單中提交的board_id查詢數據庫中對象信息
        if board:
            board.name = name                            # 表單中提交的name命名給數據庫中對象的名字
            db.session.commit()                          # 修改數據後提交數據庫
            return restful.success()                     # 數據庫修改成功
        else:
            return restful.params_error(message="沒有這個分類板塊")  # 數據庫中對象信息不存在
    else:
        return restful.params_error(message=form.get_error())  # 表單信息錯誤


# 刪除 板塊管理名稱 路由,與static/cms/js/banners.js中綁定的方法、路由要相同
@cms_bp.route("/dboard/", methods=['POST'])
@permission_required(CMSPersmission.BOARDER)             # 傳參裝飾器驗證不同用戶不同模塊權限
def dboards():
    board_id = request.form.get('board_id')                  # 查詢表單信息中的board_id,這裏沒有單獨創建刪除的Form表單,使用之前創建的
    if not board_id:
        return restful.params_error(message="分類板塊不存在")  # 表單信息不存在

    board = BoardModel.query.get(board_id)               # 根據表單中提交的board_id查詢數據庫中對象信息,注意.get
    if not board:
        return restful.params_error(message="分類板塊不存在")  # 數據庫中對象信息不存在
    
    db.session.delete(board)                             # 刪除數據庫中的信息
    db.session.commit()                                  # 提交數據庫修改
    return restful.success()                             # 刪除成功


# 前臺用戶管理路由
@cms_bp.route("/fusers/")
@permission_required(CMSPersmission.FRONTUSER)             # 傳參裝飾器驗證不同用戶不同模塊權限
def fuser():
    return render_template("cms/cms_fuser.html")


# 後用戶管理路由
@cms_bp.route("/cusers/")
@permission_required(CMSPersmission.CMSUSER)               # 傳參裝飾器驗證不同用戶不同模塊權限
def cuser():
    return render_template("cms/cms_cuser.html")


# 添加登錄路由
cms_bp.add_url_rule("/login/", view_func=LoginView.as_view('login'))    # view_func 命名操作名字,"/login/"路由地址

# 類視圖函數添加綁定路由  注意類視圖需要修改ResetPwd.as_view('resetpwd')
cms_bp.add_url_rule("/resetpwd/", view_func=ResetPwd.as_view('resetpwd'))  # view_func 命名操作名字,/resetpwd/路由地址

# 添加修改郵箱的類視圖路由綁定,路由的命名和cms_base.js中的命名要相同,否則不關聯,url=/resetemail/必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/resetemail/", view_func=ResetEmail.as_view('resetemail'))

# 綁定路由,路由的命名和cms_base.js中的命名要相同,必須要和resetemail.js中的ajax綁定的路由相同
cms_bp.add_url_rule("/email_captcha/", view_func=EmailCaptcha.as_view('email_captcha'))

命令行執行下面文件:

# 先導入future模塊
pip install future

# Window系統下執行
celery -A task.celery worker --loglevel=info --pool=solo

# linux系統
celery -A task.celery worker --loglevel=info

在這裏插入圖片描述
運行項目後修改郵箱,測試郵箱驗證碼發送是否正常:
在這裏插入圖片描述
再查看Clery監控程序:
在這裏插入圖片描述
目前項目中已經引入了Clery異步發送郵件的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章