利用Flask来实现留言板的基本操作

发布于:2025-05-29 ⋅ 阅读:(23) ⋅ 点赞:(0)

留言板开发

一、相关技术介绍

本项目基于现代Web开发技术栈构建,采用Python 3.12作为后端开发语言,搭配轻量级Flask 2.3.2框架实现核心功能。数据库层使用Flask-SQLAlchemy ORM框架操作MySQL 8.0数据库,通过对象关系映射简化数据操作。用户认证模块整合Flask-Login实现安全的会话管理,结合Flask-WTF处理表单验证,确保数据输入的合法性。前端界面采用Bootstrap 5框架构建响应式布局,通过Jinja2模板引擎实现动态内容渲染,整体架构遵循MVT(Model-View-Template)设计模式,实现业务逻辑、数据管理和表现层的清晰分离。详细如下

Python 3.12:作为后端编程语言

Flask 2.3.2:轻量级Web框架

Flask-SQLAlchemy:ORM框架,用于数据库操作

MySQL 8.0:关系型数据库存储数据

Flask-Login:用户认证管理

Flask-WTF:表单处理和验证

Bootstrap 5:前端UI框架

Jinja2:模板引擎

MVT设计模式:Model-View-Template架构

二、项目整体框架

1、代码目录结构

pythonProject/

├── venv/                   # PyCharm自动创建的虚拟环境

├── app/                    # 应用主目录

│   ├── __init__.py         # 应用初始化文件

│   ├── auth/               # 认证蓝图

│   │   ├── __init__.py

│   │   ├── forms.py

│   │   └── views.py

│   ├── main/               # 主蓝图

│   │   ├── __init__.py

│   │   ├── forms.py

│   │   └── views.py

│   ├── models.py           # 数据模型

│   └── templates/          # 模板文件

│       ├── auth/

│       │   ├── login.html

│       │   └── register.html

│        ── main/

│          ├── base.html

│          ├── create_message.html

│          ├── edit_message.html

│          ├── message_detail.html

│          └── messages.html

├── config.py               # 配置文件

├── requirements.txt        # 依赖文件

├── run.py                  # 启动文件

└── migrations/             # 数据库迁移文件夹(后续生成)

2、项目配置文件(config.py

import os

from dotenv import load_dotenv

basedir = os.path.abspath(os.path.dirname(__file__))

load_dotenv(os.path.join(basedir, '.env'))

class Config:

    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-123'

    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \

        'mysql+pymysql://username:password@localhost/flask_data'

    SQLALCHEMY_TRACK_MODIFICATIONS = False

    MESSAGES_PER_PAGE = 5

3、项目扩展包文件(requirements.txt)

Flask==2.3.2

Flask-SQLAlchemy==3.0.3

Flask-Login==0.6.2

Flask-WTF==1.1.1

Flask-Migrate==4.0.4

PyMySQL==1.0.3

python-dotenv==1.0.0

email-validator==2.0.0

三、数据库设计

1、对于数据库上的操作

检查MySQL服务状态

sudo systemctl status mysql

如果未运行,启动它

sudo systemctl start mysql

2、在mysql中创建数据库flask_data

CREATE DATABASE flask_data CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

3、数据库连接

CREATE DATABASE flask_data CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

GRANT ALL PRIVILEGES ON flask_data.* TO 'root'@'%' IDENTIFIED BY '12345678';

FLUSH PRIVILEGES;

EXIT;

  在pycharm终端执行

flask db init

flask db migrate -m "initial migration"

flask db upgrade

4、数据库表设计

App/models.py 核心代码

from datetime import datetime

from flask_login import UserMixin

from werkzeug.security import generate_password_hash, check_password_hash

from app import db, login

class User(UserMixin, db.Model):

    """用户表"""

    __tablename__ = 'users'

    

    id = db.Column(db.Integer, primary_key=True)

    username = db.Column(db.String(64), unique=True, nullable=False)

    email = db.Column(db.String(120), unique=True, nullable=False)

    password_hash = db.Column(db.String(255))  # 加密后的密码

    messages = db.relationship('Message', backref='author', lazy='dynamic')

    

    def set_password(self, password):

        self.password_hash = generate_password_hash(password)

    

    def check_password(self, password):

        return check_password_hash(self.password_hash, password)

class Message(db.Model):

    __tablename__ = 'messages'

    

    id = db.Column(db.Integer, primary_key=True)

    title = db.Column(db.String(100), nullable=False)

    content = db.Column(db.Text, nullable=False)

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

三线表描述
  1. users表结构

字段名

类型

说明

id

INT

主键,自增

username

VARCHAR(64)

用户名,唯一

email

VARCHAR(120)

邮箱,唯一

password_hash

VARCHAR(255)

加密后的密码

created_at

DATETIME

创建时间

  1. messages表结构

字段名

类型

说明

id

INT

主键,自增

title

VARCHAR(100)

留言标题

content

TEXT

留言内容

user_id

INT

外键,关联users.id

created_at

DATETIME

创建时间

updated_at

DATETIME

最后更新时间

四、留言板模块设计

1、登陆模块

功能组成:登录模块提供完整的用户认证流程,包含四大核心功能:首先,用户注册功能实现账户创建,采用密码加密存储确保安全性;其次,登录功能支持凭证验证和会话管理;第三,退出功能可安全终止用户会话;最后,"记住我"选项通过持久化Cookie提升用户体验。所有密码均通过加密算法处理后再存储,即使数据库泄露也不会暴露原始密码。
核心代码 (auth/views.py):

from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user
from app import db
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm
from app.models import User

@bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is None or not user.check_password(form.password.data):
            flash('无效的用户名或密码')
            return redirect(url_for('auth.login'))
        login_user(user, remember=form.remember_me.data)
        next_page = request.args.get('next')
        return redirect(next_page) if next_page else redirect(url_for('main.index'))
    return render_template('auth/login.html', title='登录', form=form)

@bp.route('/logout')
def logout():
    print(f"退出前用户: {current_user}")  # 调试信息
    logout_user()
    print(f"退出后用户: {current_user}")  # 调试信息
    return redirect(url_for('main.index'))

@bp.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('恭喜,注册成功!')
        return redirect(url_for('auth.login'))
    return render_template('auth/register.html', title='注册', form=form)

2、留言板模块

功能组成:留言板模块构成应用的核心功能体系:分页显示的留言列表提供内容概览;留言详情页展示完整内容和元信息;新增留言功能支持富文本内容创建;编辑功能允许作者修改已有内容;删除功能实现内容移除。每个功能点都进行了严格的权限控制,确保用户只能管理自己的内容,同时所有数据库操作都通过ORM进行,避免SQL注入风险

核心代码 (main/views.py):

@bp.route('/')

def index():

    page = request.args.get('page', 1, type=int)

    messages = Message.query.order_by(Message.created_at.desc()).paginate(

        page=page, per_page=current_app.config['MESSAGES_PER_PAGE'])

    return render_template('main/messages.html', messages=messages)

@bp.route('/create_message', methods=['GET', 'POST'])

@login_required

def create_message():

    form = MessageForm()

    if form.validate_on_submit():

        message = Message(

            title=form.title.data,

            content=form.content.data,

            author=current_user

        )

        db.session.add(message)

        db.session.commit()

        flash('留言已发布!')

        return redirect(url_for('main.index'))

    return render_template('main/create_message.html', form=form)

@bp.route('/message/<int:id>')

def message_detail(id):

    message = Message.query.get_or_404(id)

    return render_template('main/message_detail.html', message=message)

@bp.route('/edit_message/<int:id>', methods=['GET', 'POST'])

@login_required

def edit_message(id):

    message = Message.query.get_or_404(id)

    if message.author != current_user:

        abort(403)

    

    form = MessageForm()

    if form.validate_on_submit():

        message.title = form.title.data

        message.content = form.content.data

        db.session.commit()

        flash('留言已更新!')

        return redirect(url_for('main.message_detail', id=id))

    

    elif request.method == 'GET':

        form.title.data = message.title

        form.content.data = message.content

    

    return render_template('main/edit_message.html', form=form)

@bp.route('/delete_message/<int:id>', methods=['POST'])

@login_required

def delete_message(id):

    message = Message.query.get_or_404(id)

    if message.author != current_user:

        abort(403)

    

    db.session.delete(message)

    db.session.commit()

    flash('留言已删除!')

    return redirect(url_for('main.index'))

五、页面设计

1、登陆模块

以下我分别用dbb、hahaha、dbbb三个号来讲述

登录页面 (login.html) 特点:

登录页面采用Bootstrap 5框架实现响应式布局,能够完美适配从手机到桌面电脑的各种设备屏幕尺寸。表单设计实现了双重验证机制,既包含前端JavaScript实时验证,又通过Flask-WTF进行服务器端校验,确保数据安全性。错误提示采用醒目的红色边框和文字说明,直观展示验证失败的具体原因,帮助用户快速定位问题。"记住我"功能通过设置持久化Cookie实现,为用户提供便捷的自动登录体验。页面底部设有明显的注册入口链接,引导未注册用户快速跳转到注册页面,形成完整的用户引导流程。

代码如下

{% extends "main/base.html" %}
{% block title %}登录{% endblock %}
{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card">
            <div class="card-body">
                <h2 class="card-title text-center mb-4">登录</h2>
                <form method="POST" action="{{ url_for('auth.login') }}">
                    {{ form.hidden_tag() }}
                    <div class="mb-3">
                        {{ form.username.label(class="form-label") }}
                        {{ form.username(class="form-control") }}
                        {% if form.username.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.username.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    
                    <div class="mb-3">
                        {{ form.password.label(class="form-label") }}
                        {{ form.password(class="form-control") }}
                        {% if form.password.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.password.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    <div class="mb-3 form-check">
                        {{ form.remember_me(class="form-check-input") }}
                        {{ form.remember_me.label(class="form-check-label") }}
                    </div>
                    <div class="d-grid">
                        {{ form.submit(class="btn btn-primary") }}
                    </div>
                </form>
                <div class="mt-3 text-center">
                    <p>新用户?<a href="{{ url_for('auth.register') }}">点击注册</a></p>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

注册页面 (register.html) 特点:

注册页面实现了全面的表单验证体系,包括用户名唯一性检查(实时查询数据库)、标准邮箱格式验证(使用email-validator库)以及密码强度要求(最少8位字符)。密码确认字段通过WTForms的EqualTo验证器确保两次输入一致,防止输入错误。所有验证结果都会通过动态错误提示实时反馈,在输入框下方即时显示具体错误信息,大幅提升填写效率。页面保留了良好的用户引导设计,底部设有登录入口链接,方便已有账号的用户快速切换到登录界面,形成注册-登录的闭环体验。

代码如下:{% extends "main/base.html" %}

{% block title %}注册{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card">
            <div class="card-body">
                <h2 class="card-title text-center mb-4">注册</h2>
                
                <form method="POST" action="{{ url_for('auth.register') }}">
                    {{ form.hidden_tag() }}
                    
                    <div class="mb-3">
                        {{ form.username.label(class="form-label") }}
                        {{ form.username(class="form-control") }}
                        {% if form.username.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.username.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    
                    <div class="mb-3">
                        {{ form.email.label(class="form-label") }}
                        {{ form.email(class="form-control") }}
                        {% if form.email.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.email.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    
                    <div class="mb-3">
                        {{ form.password.label(class="form-label") }}
                        {{ form.password(class="form-control") }}
                        {% if form.password.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.password.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    
                    <div class="mb-3">
                        {{ form.password2.label(class="form-label") }}
                        {{ form.password2(class="form-control") }}
                        {% if form.password2.errors %}
                            <div class="invalid-feedback d-block">
                                {% for error in form.password2.errors %}
                                    {{ error }}
                                {% endfor %}
                            </div>
                        {% endif %}
                    </div>
                    
                    <div class="d-grid">
                        {{ form.submit(class="btn btn-primary") }}
                    </div>
                </form>
                
                <div class="mt-3 text-center">
                    <p>已有账号?<a href="{{ url_for('auth.login') }}">点击登录</a></p>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

2、留言板模块

基础模板 (base.html) 特点:

基础模板采用Bootstrap 5框架构建统一的UI风格,确保整个应用保持一致的视觉效果。响应式导航栏能自动适应不同设备屏幕尺寸,并根据用户登录状态动态显示对应的功能入口(如登录用户显示"发布留言"和"退出"按钮,未登录用户显示"登录"和"注册"按钮)。通过Flask的消息闪现系统,所有操作结果都会以醒目的提示框形式展示在页面顶部,增强用户交互体验。导航栏还实时显示当前登录用户名,让用户随时感知自己的登录状态。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}留言板{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            padding-top: 20px;
            background-color: #f8f9fa;
        }
        .message-card {
            margin-bottom: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .pagination {
            justify-content: center;
            margin-top: 20px;
        }
        /* 确保导航栏项目正确对齐 */
        .navbar-nav {
            align-items: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
            <div class="container-fluid">
                <a class="navbar-brand" href="{{ url_for('main.index') }}">留言板</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarCollapse">
                    <ul class="navbar-nav me-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('main.index') }}">首页</a>
                        </li>
                        {% if current_user.is_authenticated %}
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('main.create_message') }}">发布留言</a>
                        </li>
                        {% endif %}
                    </ul>
                    <ul class="navbar-nav">
                        {% if current_user.is_authenticated %}
                        <li class="nav-item">
                            <span class="navbar-text me-2">欢迎, {{ current_user.username }}</span>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('auth.logout') }}" id="logoutLink">退出</a>
                        </li>
                        {% else %}
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('auth.login') }}">登录</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('auth.register') }}">注册</a>
                        </li>
                        {% endif %}
                    </ul>
                </div>
            </div>
        </nav>

        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }} alert-dismissible fade show">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        // 确保退出链接正常工作
        document.getElementById('logoutLink')?.addEventListener('click', function() {
            // 可以在这里添加确认对话框
            return confirm('确定要退出吗?');
        });
    </script>
</body>
</html>

留言列表 (messages.html) 特点:

留言列表页面采用卡片式布局展示内容摘要,每张卡片包含留言标题、部分内容和关键元信息。分页控件位于页面底部,支持大量数据的分批加载和浏览。每条留言卡片都清晰标注作者用户名和发布时间,并采用条件渲染技术,仅对留言作者显示"编辑"和"删除"操作按钮。卡片标题链接到详情页,方便用户查看完整内容。整体布局简洁明了,信息层级分明。

代码如下:

{% extends "main/base.html" %}

{% block title %}留言板{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto">
        <h1 class="mb-4">所有留言</h1>

        {% for message in messages.items %}
        <div class="card message-card mb-3">
            <div class="card-body">
                <h2 class="card-title">
                    <a href="{{ url_for('main.message_detail', id=message.id) }}" class="text-decoration-none">
                        {{ message.title }}
                    </a>
                </h2>
                <p class="card-text text-muted">
                    {{ message.content[:100] }}...  <!-- 显示前100个字符 -->
                </p>
                <div class="d-flex justify-content-between align-items-center">
                    <small class="text-muted">
                        作者: {{ message.author.username }} |
                        发布于: {{ message.created_at.strftime('%Y-%m-%d %H:%M') }}
                    </small>
                    {% if current_user == message.author %}
                    <div>
                        <a href="{{ url_for('main.edit_message', id=message.id) }}" class="btn btn-sm btn-outline-primary">编辑</a>
                    </div>
                    {% endif %}
                </div>
            </div>
        </div>
        {% else %}
        <div class="alert alert-info">暂无留言</div>
        {% endfor %}

        <!-- 分页导航 -->
        <nav aria-label="Message navigation">
            <ul class="pagination justify-content-center">
                {% if messages.has_prev %}
                <li class="page-item">
                    <a class="page-link" href="{{ url_for('main.index', page=messages.prev_num) }}">上一页</a>
                </li>
                {% endif %}

                <li class="page-item disabled">
                    <span class="page-link">第 {{ messages.page }} 页 / 共 {{ messages.pages }} 页</span>
                </li>

                {% if messages.has_next %}
                <li class="page-item">
                    <a class="page-link" href="{{ url_for('main.index', page=messages.next_num) }}">下一页</a>
                </li>
                {% endif %}
            </ul>
        </nav>

        <div class="text-center mt-3">
            <a href="{{ url_for('main.create_message') }}" class="btn btn-primary">发布新留言</a>
        </div>
    </div>
</div>
{% endblock %}

留言详情 (message_detail.html) 特点:

详情页完整展示留言的标题和内容,在显著位置标注作者信息和创建时间。如果留言被编辑过,还会额外显示最后更新时间,让读者了解内容的新鲜度。页面底部为留言作者提供了专属操作区,包含"编辑"和"删除"按钮,非作者用户则看不到这些功能入口。所有内容采用舒适的排版和合适的字体大小,确保最佳阅读体验。

代码如下:

{% extends "main/base.html" %}

{% block title %}{{ message.title }}{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto">
        <div class="card message-card">
            <div class="card-body">
                <h2 class="card-title">{{ message.title }}</h2>
                <div class="card-text mb-4">
                    {{ message.content|safe }}
                </div>
                <div class="d-flex justify-content-between align-items-center">
                    <small class="text-muted">
                        作者: {{ message.author.username }} |
                        发布于: {{ message.created_at.strftime('%Y-%m-%d %H:%M') }} |
                        最后更新: {{ message.updated_at.strftime('%Y-%m-%d %H:%M') }}
                    </small>
                    {% if current_user == message.author %}
                    <div>
                        <a href="{{ url_for('main.edit_message', id=message.id) }}" class="btn btn-outline-primary">编辑</a>
                        <form action="{{ url_for('main.delete_message', id=message.id) }}" method="post" class="d-inline">
                            <button type="submit" class="btn btn-outline-danger" οnclick="return confirm('确定删除吗?')">删除</button>
                        </form>
                    </div>
                    {% endif %}
                </div>
            </div>
        </div>

        <div class="mt-3">
            <a href="{{ url_for('main.index') }}" class="btn btn-secondary">返回列表</a>
        </div>
    </div>
</div>
{% endblock %}

创建/编辑页面 (create_message.html/edit_message.html) 特点:

这两个页面共享相似的表单结构,都实现了严格的表单验证机制,包括必填项检查和内容长度限制。富文本编辑区域支持基本的格式设置,提升内容输入体验。在编辑模式下,表单会自动载入原有数据,方便用户修改。所有破坏性操作(如删除)都要求二次确认,通过JavaScript弹窗提示用户确认操作,有效防止误操作导致的数据丢失。页面布局保持简洁,突出核心的表单区域。

create_message.html:
{% extends "main/base.html" %}

{% block title %}发布留言{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto">
        <h1 class="mb-4">发布留言</h1>

        <form method="POST" action="{{ url_for('main.create_message') }}">
            {{ form.hidden_tag() }}

            <div class="mb-3">
                {{ form.title.label(class="form-label") }}
                {{ form.title(class="form-control") }}
                {% if form.title.errors %}
                    <div class="invalid-feedback d-block">
                        {% for error in form.title.errors %}
                            {{ error }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>

            <div class="mb-3">
                {{ form.content.label(class="form-label") }}
                {{ form.content(class="form-control", rows=8) }}
                {% if form.content.errors %}
                    <div class="invalid-feedback d-block">
                        {% for error in form.content.errors %}
                            {{ error }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>

            <div class="d-grid">
                {{ form.submit(class="btn btn-primary") }}
            </div>
        </form>
    </div>
</div>
{% endblock %}
edit_message.html:

{% extends "main/base.html" %}

{% block title %}编辑留言{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto">
        <h1 class="mb-4">编辑留言</h1>
        
        <form method="POST" action="{{ url_for('main.edit_message', id=message.id) }}">
            {{ form.hidden_tag() }}
            
            <div class="mb-3">
                {{ form.title.label(class="form-label") }}
                {{ form.title(class="form-control") }}
                {% if form.title.errors %}
                    <div class="invalid-feedback d-block">
                        {% for error in form.title.errors %}
                            {{ error }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>
            
            <div class="mb-3">
                {{ form.content.label(class="form-label") }}
                {{ form.content(class="form-control", rows=8) }}
                {% if form.content.errors %}
                    <div class="invalid-feedback d-block">
                        {% for error in form.content.errors %}
                            {{ error }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>
            
            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                <a href="{{ url_for('main.message_detail', id=message.id) }}" class="btn btn-secondary me-md-2">取消</a>
                {{ form.submit(class="btn btn-primary") }}
            </div>
        </form>
    </div>
</div>
{% endblock %}

第一次进入页面

使用dbbb这个号第三次进入页面就有之前两个的留言


用第三个dbbb号进入并发布新留言

编辑留言

成功删除留言


网站公告

今日签到

点亮在社区的每一天
去签到