Python Flask搭建个人博客详细回顾—(3.2 博客前台页面)

个人博客Demo: link.
GitHub项目完整链接:link


回顾上一节主要讲了以下3个方面内容:

  • 基模板 base.html
  • 文章列表局部模板 _post.html
  • 主页index和分类页category相关模板和视图函数

3.2.1 文章详情页post

  • 文章详情页除了包括文章标题,内容,分类,时间戳以外,还有一个重要内容就是文章评论板块
  • 代码虽然比较长(主要是评论板块的占比),但是理解起来并不复杂,其中最重要的莫过于用form表单标签包裹开启,关闭评论按钮和删除评论按钮,form包裹能够提高安全性,防范CSRF攻击。具体查看代码注释。
  • 文章详情页还涉及到很多视图函数,包括后台管理部分的视图函数如,开启关闭评论,删除评论等,视图函数代码一一列举到后面
  • post.html 代码如下:
{% extends 'base.html' %}
{% from 'bootstrap/pagination.html' import render_pagination %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}详情{% endblock title %}

{% block content %}
<div class="row">
    <div class="col-sm-10 mx-auto">
        <div class="page-header">
            <h1>{{ post.title }}</h1>
        </div>
        <div class="">
            <span class="body-font">{{ post.body|safe }}</span>
            <p><small>分类: {{ post.category.name }}</small></p>
            <small>日期: {{ moment(post.timestamp).format('LLL') }}</small>
        </div>
        <hr>
        <div class="comments" id="comments">
                <h3>评论数:{{ pagination.total }}
                    <!-- 如果管理员已登录,显示以下按钮(表单包裹按钮旨在防范CSRF攻击) -->
                    {% if current_user.is_authenticated %}
                    <form method="post"
                          action="{{ url_for('admin.set_comment', post_id=post.id, next=request.full_path) }}"
                        class="float-right">
                        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
                        <button type="submit" class="btn btn-danger btn-sm">
                            {% if post.can_comments %}关闭{% else %}开启{% endif %}评论
                        </button>
                    </form>
                    {% endif %}
                </h3>
                <!-- 评论列表组 -->
                {% if comments %}
                    <ul class="list-group">
                        {% for comment in comments %}
                        <!-- flex弹性盒子,flex-column子元素垂直方向显示 -->
                            <li class="list-group-item list-group-item-action flex-column">
                                <!-- w-100:width:100%,justify-content-between:内容排列方式 -->
                                <div class="d-flex w-100 justify-content-between">
                                    <!-- mb-1:margin-button -->
                                    <h5 class="mb-1 text-primary">
                                            <!-- 这里用于判定,如果评论为管理员则admin.name作为评论者名字,否则用comment.author -->
                                            {% if comment.from_admin %}
                                                {{ admin.name }}
                                            {% else %}
                                                {{ comment.author }}
                                            {% endif %}
                                        </a>
                                        <!-- 管理员评论添加Author徽章 -->
                                        {% if comment.from_admin %}
                                            <span class="badge badge-primary">作者</span>{% endif %}
                                        <!-- 当评论是一个回复时,则显示一个Reply提示标签 -->
                                        {% if comment.replied %}<span class="badge badge-light">Reply</span>{% endif %}
                                    </h5>
                                    <small data-toggle="tooltip" data-placement="top" data-delay="500"
                                           data-timestamp="{{ comment.timestamp.strftime('%Y-%m-%dT%H:%M:%SZ') }}">
                                        {{ moment(comment.timestamp).fromNow() }}
                                    </small>
                                </div>
                                <!-- 判断是否回复;comment.replied.id表示被回复评论的作者,br表示换行 -->
                                {% if comment.replied %}
                                    <p class="alert alert-dark reply-body">{{ comment.replied.author }}:
                                        <br>{{ comment.replied.body }}
                                    </p>
                                {%- endif -%}
                                <!-- 评论主体 -->
                                <p class="mb-1">{{ comment.body }}</p>
                                <!-- float-right:靠右悬浮 -->
                                <div class="float-right">
                                    <a class="btn btn-light btn-sm"
                                       href="{{ url_for('.reply_comment', comment_id=comment.id) }}">回复</a>
                                    {% if current_user.is_authenticated %}
                                    <form class="inline" method="post"
                                              action="{{ url_for('admin.delete_comments',
                                               comment_id=comment.id, next=request.full_path) }}">
                                            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
                                            <button type="submit" class="btn btn-danger btn-sm"
                                                    onclick="return confirm('确定删除该评论?');">删除
                                            </button>
                                    </form>
                                    {% endif %}
                                </div>
                            </li>
                        {% endfor %}
                    </ul>
                {% else %}
                    <div class="tip"><h5>这里还没有评论...</h5></div>
                {% endif %}
            </div>
        {% if comments %}
            {{ render_pagination(pagination, fragment='#comments') }}
        {% endif %}
        {% if request.args.get('reply')%}
            <div class="alert alert-dark ">
                回复:<strong>{{ request.args.get('author') }}</strong>
                <a class="float-right " href="{{ url_for('.show_post', post_id=post.id )}}">取消回复</a>
            </div>
        {% endif %}
        {% if post.can_comments %}
            {{ render_form(form, action=request.full_path) }}
        {% else %}
            <div class="tip">
                <p>评论区已关闭</p>
            </div>
        {% endif %}
    </div>
</div>
{% endblock content %}
  • 文章详情页视图函数部分:
  1. blog.py/ show_post视图
    blog.py导入模块及实例部分代码变化成这样:
from flask import Blueprint, request, current_app, render_template, redirect, flash, url_for
from Blog.models import Post, Category, Comment
from Blog.forms import CommentForm
from Blog.extensions import db
@blog_bm.route('/post/<int:post_id>', methods=['GET', 'POST'])
def show_post(post_id):
    post = Post.query.get_or_404(post_id)
    page = request.args.get('page', 1, type=int)
    per_page = current_app.config['BLOG_COMMENT_PER_PAGE']
    pagination = Comment.query.with_parent(post).order_by(Comment.timestamp.desc()).paginate(page, per_page=per_page)
    comments = pagination.items

    form = CommentForm()

    if form.validate_on_submit():
        author = form.name.data
        body = form.comment.data

        # 必须加入post=post参数,否则无法显示出对应评论,因为comment不知道自己属于哪一篇文章
        comment = Comment(
            author=author,
            body=body,
            post=post
        )
        replied_id = request.args.get('reply')
        if replied_id:
            replied_comment = Comment.query.get_or_404(replied_id)
            comment.replied = replied_comment

        db.session.add(comment)
        db.session.commit()
        flash('评论成功!', 'success')
        return redirect(url_for('.show_post', post_id=post_id))
    return render_template('blog/post.html', post=post, pagination=pagination, comments=comments, form=form)
  • 视图函数中涉及到分页对象;实例化评论表单类: form = CommentForm();渲染文章详情页模板,传入相关参数
  • form.validate_on_submit()验证表单并提交,表单name,comment字段数据传递到author,body变量中,再实例化数据模型Comment类,传入参数author,body变量,以及post对象(告知评论属于哪一篇文章)
  • 在表单提交验证中,通过查询字符串reply获取replied_id,存在replied回复则将对应replied_id评论转换成replied对象,即被回复的评论对象
  • 最后db.session.add()提生成临时会话,session.commit()提交会话保存到数据库,页面重定向到文章详情页,刷新评论

  1. blog.py /reply_comment视图函数
  • 评论区回复按钮的视图函数代码:
@blog_bm.route('/reply/comment/<int:comment_id>', methods=['GET', 'POST'])
def reply_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)

    if not comment.post.can_comments:
        flash('暂时不能回复,文章评论已关闭')
        return redirect(url_for('.show_post', post_id=comment.post.id))
    return redirect(url_for
                    ('.show_post', post_id=comment.post.id, reply=comment_id, author=comment.author) + '#comment-form')
  • 重定向url的时候,传入的参数post_id为回复对应的评论对象comment的外键post.id,确定文章页面不出错;reply=comment_id参数为被回复评论的id;author=comment.author则是被回复评论的作者。

  1. admin.py / set_comment视图函数
  • 由于设置评论功能只能登录后才能看到,因此使用flask_login提供的login_required装饰器进行视图保护,代码较为简单,不赘述
  • 开启/关闭按钮视图函数:
from flask import Blueprint, flash, redirect, url_for
from flask_login import login_required
from Blog.models import Post
from Blog.extensions import db

admin_bm = Blueprint('admin', __name__)


@admin_bm.route('/set-comment/<int:post_id>', methods=['POST'])
@login_required
def set_comment(post_id):
    post = Post.query.get_or_404(post_id)
    if post.can_comments:
        post.can_comments = False
        flash('评论已关闭!', 'info')
    else:
        post.can_comments = True
        flash('评论已开启!', 'info')
    db.session.commit()
    return redirect(url_for('blog.show_post', post_id=post.id))

  1. admin.py / delete_comment视图函数
  • 删除评论功能代码如下:
@admin_bm.route('/comment/<int:comment_id>/delete', methods=['POST'])
@login_required
def delete_comments(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    db.session.delete(comment)
    db.session.commit()
    flash('评论已删除.', 'success')
    return redirect_back()

3.2.2 登录页面login和登出logout

  • 登录/登出的入口在页脚右下角(登录页面将页脚设置为空),可直接通过快速渲染表单样式render_form渲染,代码比较简单
  • 用户在登录时,提交表单通过验证之后,获取表单密码,用户名进行验证,最后再调用login_user(admin, remember)方法登录用户
  • 登出不需要特殊的页面,只需要调用flask_login模块提供的logout_user()函数即可
  • 登录页面模板代码 login.html 如下
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title%}登录{% endblock title %}

{% block content %}
    <div class="container ">
        <div class="row justify-content-center page-header">
            <h1>登录</h1>
        </div>
        <!--
        <div class="row justify-content-center">
            {{ render_form(form, extra_classes='col-6') }}
        </div>
        -->
        <div class="row">
            <div class="col-sm-6 mx-auto">
                <form method="post">
                    {{ form.csrf_token }}
                    <div class="form-group ">
                        {{ form.username.label }}
                        {{ form.username(class='form-control rounded-0') }}
                    </div>
                    <div class="form-group ">
                        {{ form.password.label }}
                        {{ form.password(class='form-control rounded-0') }}
                    </div>
                    <div class="form-check">
                        {{ form.remember(class='form-check-input') }}
                        {{ form.remember.label }}
                    </div>
                        {{ form.submit(class='btn btn-dark rounded-0') }}
                </form>
            </div>
        </div>
    </div>
{% endblock content %}

{% block footer %}
{% endblock footer %}
from flask import Blueprint, render_template, flash, redirect, url_for
from Blog.forms import LoginForm
from flask_login import logout_user, login_required, login_user
from Blog.helpers import redirect_back
from Blog.models import Admin
from flask_login import current_user


login_bm = Blueprint('login', __name__)


@login_bm.route('/login', methods=['GET', 'POST'])
def login():
    # 如果已登录,重定向回到主页
    if current_user.is_authenticated:
        return redirect(url_for('blog.index'))

    form = LoginForm()

    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        remember = form.remember.data
        admin = Admin.query.first()  # 返回查询的第一条记录
        if admin:
            if username == admin.username and admin.validate_password(password):
                login_user(admin, remember)  # 登入用户
                flash('欢迎回来!', 'info')
                return redirect_back()
            flash("用户名或密码错误", 'warning')
        else:
            flash("没有管理员账户")
    return render_template('login/login.html', form=form)


@login_bm.route('/logout')
@login_required
def logout():
    logout_user()
    flash('注销成功!', 'info')
    return redirect_back()
    # return redirect(url_for('blog.index'))

3.2.3 小结

  • 文章详情页中的评论板块需要好好理解,细节较多
  • 后面会介绍管理后台页面的实现
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值