官网地址:Flask-Admin — flask-admin 1.6.0 documentation

官方仓库:http://github.com/flask-admin/flask-admin

功能简介

已实现的功能:

  • 通过flask-admin对数据库记录进行增删改查
  • flask-admin页面汉化
  • 导出内容
  • 选择不同的分页大小进行分页
  • 添加待管理的模型并将指定列名/字段汉化。
  • 登录、退出、修改管理员密码、新增管理员用户

实现的最终效果

文档最开始有代码包,下载后安装环境后即可启动预览,代码布局如下:

基本使用

安装工具包

pip install flask flask-Login Flask-SQLAlchemy flask-babel flask-admin[export] bootstrap-flask
# 需要提前下载到本地
pip install flask_admin-2.0.0a4-py3-none-any.whl

建议:从官方github仓库安装flask-admin最新版,更好的兼容flask3.x和WTForm2.x。当前使用的版本是flask_admin-2.0.0a4。也可以从附件中下载。

以下的代码实现了基础的flask-admin功能,包括页面汉化,模型字段汉化,并开启了一些常用的功能,如搜索,过滤,行内编辑,导出,详情内容查看。添加了flask命令,用户初始化数据库,添加管理员和普通用户。

app.py

from flask import Flask
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_admin.base import Bootstrap4Theme
from flask_sqlalchemy import SQLAlchemy
from flask_babel import Babel
from flask_login import UserMixin
from faker import Faker
import click

app = Flask(__name__)

# 配置数据库信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskadmin_example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'flask-admin'
# 开启后台页面汉化
app.config['BABEL_DEFAULT_LOCALE'] = 'zh_CN'

db = SQLAlchemy(app)
babel = Babel(app)
faker = Faker(app)

# 定义管理员模型
class AdminUsers(db.Model, UserMixin):
    __tablename__ = 'admin_users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), nullable=False)
    password = db.Column(db.String(200), nullable=False)

    def __repr__(self):
        return f'<Admin {self.username}>'

# 定义普通用户模型
class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), nullable=False)
    password = db.Column(db.String(200), nullable=False)
    phone_number = db.Column(db.String(20), nullable=False)
    city = db.Column(db.String(50), nullable=False)

    def __repr__(self):
        return f'<User {self.name}>'

# 自定义视图的功能
class UsersModelView(ModelView):
    # 开启搜索框,以用户名为条件进行搜索
    column_searchable_list = ['username']
    # 对username和phone_number开启行内编辑
    column_editable_list = ['username', 'phone_number']
    # 表单排除的列
    form_excluded_columns = ['id']
    # 开启过滤功能,以username条件进行过滤
    column_filters = ['username']
    # 定义显示的列
    column_list = ['username', 'phone_number']
    # 开启详情查看功能
    can_view_details = True
    # 列名映射/汉化
    column_labels = {
        'username': '用户名',
        'phone_number': '电话号码',
    }
    # 开启导出功能
    can_export = True
    # 设置导出的文件格式
    export_types = ['csv', 'json']

    # 开启分页功能
    can_set_page_size = True
    
    # 设置分页大小选项
    page_size_options = [5, 10, 20, 50, 100]
    # 默认分页大小
    page_size = 5


# name:flask-admin后台的名称
# theme:设置flask-admin的主题,支持的主题在https://bootswatch.com/4/查看
admin = Admin(app, name='管理后台', theme=Bootstrap4Theme(swatch='Cerulean'))
# 将UsersModelView视图添加到flask-admin管理
admin.add_view(UsersModelView(User, db.session, name='用户管理'))

# 初始化数据库
@app.cli.command('init_db')
def init_db():
    """Create the flaskadmin_example database."""
    db.drop_all()
    db.create_all()
    click.echo('Database created successfully!')

# 创建管理员用户
@app.cli.command('create_admin')
@click.option('--username', default='admin')
@click.option('--password', default='admin')
def create_admin(username, password):
    """Create a new admin user."""
    admin_user = AdminUsers(username=username, password=password)
    db.session.add(admin_user)
    db.session.commit()
    click.echo(f'Admin user {username}/{password} created successfully!')

# 创建普通用户,默认创建10个用户
@app.cli.command('create_user')
@click.option('--count', default=10)
def create_user(count):
    """Number of users to create."""
    for _ in range(count):
        user = User(
            username=faker.user_name(),
            password=faker.password(),
            phone_number=faker.phone_number(),
            city=faker.city()
        )
        db.session.add(user)
        db.session.commit()
        click.echo(f'User {user.username} created successfully!')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

接下来初始化数据库,创建管理员用户,创建普通用户

# 初始化数据库
flask init_db
# 创建管理员用户,用户名和密码都是admin,支持通过--username和--password自定义用户名和密码
flask create_admin --username admin --password admin
# 创建普通用户,支持通过--count自定义创建的用户数量
flask create_user --count 10

查看user表

查看admin_users表

运行程序,访问http://127.0.0.1:5000/admin访问管理后台

为首页增加内容,如展示当前的普通用户数量。在templates目录下,创建admin目录,并创建index.html文件。index.html文件内容如下:

{% extends 'admin/master.html' %}

{% block body %}
<div class="container text-center">
    <h1>欢迎来到 Flask-Admin 管理后台</h1>
    <p>这里是您的管理面板,您可以在此处管理用户和其他资源。</p>
    <p>当前用户数量: {{ user_count }}</p>
</div>
{% endblock %}

然后导入AdminIndexView类和expose,创建一个CustomAdminIndexView类,并继承AdminIndexView,编写逻辑,然后使用self.render渲染模板并传参。

app.py

# 导入AdminIndexView, expose
from flask_admin import Admin, AdminIndexView, expose

# 其他未修改的代码这里省略...


# 新增自定义类
class CustomAdminIndexView(AdminIndexView):
    @expose('/')
    def index(self):
        user = User.query.count()
        print(f'当前用户数量: {user}')
        return self.render('admin/index.html', user_count=user)

# 实例化时,传递自定义的index_view
admin = Admin(app, name='管理后台', theme=Bootstrap4Theme(swatch='Cerulean'), index_view=CustomAdminIndexView())

提示:AdminIndexView类是Admin的首页类,可以覆盖相关的方法重写。

最终实现的效果如下,实际工作中要根据需求定义首页,这里只做示例。

接下来点击用户管理页面,就可以体验配置好的基础功能了。

登录功能

flask-admin默认没有登录鉴权功能,需要手动为flask-admin编写后台登录,登出,修改密码功能。可以使用flask-login扩展来实现。实现的功能要求如下:

  1. 登录
    1. 未登录无法进入后台管理模型
    2. 未登录不显示管理的模型,只显示首页
    3. 登录后显示用户名,点击用户名可以选择修改密码、退出登录、新增管理员
  1. 退出:登录后,可以退出登录
  2. 修改密码:登录后,可以修改密码,修改密码后需要重新登录

app.py

# 省略其他包
from flask_bootstrap import Bootstrap4
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired

# 实例化
login_manager = LoginManager(app)
bootstrap = Bootstrap4(app)

# 设置登录加载函数
@login_manager.user_loader
def load_user(user_id):
    return AdminUsers.query.get(int(user_id))

# 创建登录表单
class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submit = SubmitField('登录')

# 创建修改密码表单
class ChangePasswordForm(FlaskForm):
    old_password = PasswordField('旧密码', validators=[DataRequired()])
    new_password = PasswordField('新密码', validators=[DataRequired()])
    confirm_password = PasswordField('确认新密码', validators=[DataRequired()])
    submit = SubmitField('修改密码')

# 创建BaseModelView类并继承ModelView,重写is_accessible和inaccessible_callback函数
# 如果认证通过返回true,如果认证不通过重定向到admin.login登录视图
class BaseModelView(ModelView):
    def is_accessible(self):
        return current_user.is_authenticated
    
    def inaccessible_callback(self, name, **kwargs):
        # redirect to login page if user doesn't have access
        return redirect(url_for('admin.login', next=request.url))

# 将UserModelView继承类ModelView改为BaseModelView,以便使用重写的认证函数
# 其他新增的模型类也要继承该类。
class UsersModelView(BaseModelView):
    # 代码未修改

# 修改CustomAdminIndexView,添加login,logout,change_password函数,重写index函数判断用户是否登录
class CustomAdminIndexView(AdminIndexView):
    @expose('/')
    def index(self):
        if current_user.is_authenticated:
            user = User.query.count()
            print(f'当前用户数量: {user}')
            return self.render('admin/index.html', user_count=user)
        return redirect(url_for('admin.login'))
    
    @expose('/login', methods=['GET', 'POST'])
    def login(self):
        if current_user.is_authenticated:
            return redirect(url_for('admin.index'))
        form = LoginForm()
        if form.validate_on_submit():
            username = form.username.data
            password = form.password.data
            user = AdminUsers.query.filter_by(username=username).first()
            if user and user.password == password:
                login_user(user)
                flash('您已登录成功!', 'success')
                return redirect(url_for('admin.index'))
            else:
                flash('用户名或密码错误!', 'danger')
        return self.render('admin/login.html', form=form)
    
    @expose('/logout/')
    def logout(self):
        logout_user()
        flash('您已成功登出!', 'success')
        return redirect(url_for('admin.login'))

    @expose('/change_password/', methods=['GET', 'POST'])
    def change_password(self):
        form = ChangePasswordForm()
        if form.validate_on_submit():
            current_user.password = form.new_password.data
            db.session.commit()
            flash('密码修改成功,请重新登录。', 'success')
            logout_user()  # 修改密码后登出用户
            return redirect(url_for('admin.login'))
        return self.render('admin/change_password.html', form=form)

    @expose('/add_adminuser/', methods=['GET', 'POST'])
    def add_adminuser(self):
        form = AdminUserForm()
        if form.validate_on_submit():
            admin_user = AdminUsers(
                username=form.username.data,
                password=form.password.data
            )
            db.session.add(admin_user)
            db.session.commit()
            flash(f'管理员用户{admin_user.username}添加成功!', 'success')
            return redirect(url_for('admin.index'))
        return self.render('admin/add_adminuser.html', form=form)

在templates/admin目录下添加login.html模板,内容如下

{% extends 'admin/master.html' %}
{% from "bootstrap4/form.html" import render_form %}

{% block title %}管理员登录{% endblock %}


{% block body %}
    <div class="container">
        <h3>管理员登录</h3>
        {{ render_form(form) }}
    </div>
{% endblock %}

在templates/admin目录下添加change_password.html模板,内容如下

{% extends 'admin/master.html' %}
{% from "bootstrap4/form.html" import render_form %}

{% block title %}修改密码{% endblock %}


{% block body %}
    <div class="container">
        <h3>修改密码</h3>
        {{ render_form(form) }}
    </div>
{% endblock %}

在templates/admin目录下添加master.html模板,用于在导航栏添加登录,退出,修改密码链接。默认的master.html模板是flask-admin提供的,该模板继承admin_base_template,手动在admin目录下创建master.html后,flask-admin会自行使用修改后的master.html,便于自定义。

{% extends admin_base_template %}

{% block menu_links %}
    {{ super() }}
    <ul class="nav navbar-nav navbar-right">
    {% if current_user.is_authenticated %}
        <li class="dropdown">
            <a class="dropdown-toggle nav-link" data-toggle="dropdown"
                href="javascript:void(0)" aria-expanded="false">{{
                current_user.username }}<i
                    class="glyphicon glyphicon-chevron-down small"></i></a>
            <ul class="dropdown-menu">
                <li>
                    <a class="dropdown-item"
                        href="{{ url_for('admin.add_adminuser') }}">
                        新增管理员</a>
                </li>
                <li>
                    <a class="dropdown-item"
                        href="{{ url_for('admin.change_password') }}">
                        修改密码</a>
                </li>
                <li>
                    <a class="dropdown-item" href="{{ url_for('admin.logout') }}">
                        退出</a>
                </li>
            </ul>
        </li>
    {% else %}
        <li>
            <a class="nav-link" href="/admin/login">登录</a>
        </li>
    {% endif %}
    </ul>
{% endblock %}

最终实现的效果在文档最前面。

完整示例

app.py

from flask import Flask, url_for, redirect, request, flash
from flask_admin import Admin, AdminIndexView, expose
from flask_admin.contrib.sqla import ModelView
from flask_admin.base import Bootstrap4Theme
from flask_sqlalchemy import SQLAlchemy
from flask_babel import Babel
from flask_login import UserMixin, LoginManager, current_user, login_user, logout_user
from faker import Faker
from flask_bootstrap import Bootstrap4
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
import click


app = Flask(__name__)

# 配置数据库信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskadmin_example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'flask-admin'
# 开启后台页面汉化
app.config['BABEL_DEFAULT_LOCALE'] = 'zh_CN'

db = SQLAlchemy(app)
babel = Babel(app)
faker = Faker(app)
login_manager = LoginManager(app)
bootstrap = Bootstrap4(app)

# 设置登录加载函数
@login_manager.user_loader
def load_user(user_id):
    return AdminUsers.query.get(int(user_id))

# 定义管理员模型
class AdminUsers(db.Model, UserMixin):
    __tablename__ = 'admin_users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), nullable=False)
    password = db.Column(db.String(200), nullable=False)

    def __repr__(self):
        return f'<Admin {self.username}>'

# 定义普通用户模型
class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), nullable=False)
    password = db.Column(db.String(200), nullable=False)
    phone_number = db.Column(db.String(20), nullable=False)
    city = db.Column(db.String(50), nullable=False)

    def __repr__(self):
        return f'<User {self.name}>'
    
class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submit = SubmitField('登录')

class ChangePasswordForm(FlaskForm):
    old_password = PasswordField('旧密码', validators=[DataRequired()])
    new_password = PasswordField('新密码', validators=[DataRequired()])
    confirm_password = PasswordField('确认新密码', validators=[DataRequired()])
    submit = SubmitField('修改密码')

class BaseModelView(ModelView):
    def is_accessible(self):
        """检查用户是否有权限访问该视图"""
        return current_user.is_authenticated
    

    def inaccessible_callback(self, name, **kwargs):
        # redirect to login page if user doesn't have access
        return redirect(url_for('admin.login', next=request.url))

# 自定义视图的功能
class UsersModelView(BaseModelView):
    # 开启搜索框,以用户名为条件进行搜索
    column_searchable_list = ['username']
    # 对username和phone_number开启行内编辑
    column_editable_list = ['username', 'phone_number']
    # 表单排除的列
    form_excluded_columns = ['id']
    # 开启过滤功能,以username条件进行过滤
    column_filters = ['username']
    # 定义显示的列
    column_list = ['username', 'phone_number']
    # 开启详情查看功能
    can_view_details = True
    # 列名映射/汉化
    column_labels = {
        'username': '用户名',
        'phone_number': '电话号码',
    }
    # 开启导出功能
    can_export = True
    # 设置导出的文件格式
    export_types = ['csv', 'json']

    # 开启分页功能
    can_set_page_size = True
    
    # 设置分页大小选项
    page_size_options = [5, 10, 20, 50, 100]
    # 默认分页大小
    page_size = 5


class CustomAdminIndexView(AdminIndexView):
    @expose('/')
    def index(self):
        if current_user.is_authenticated:
            user = User.query.count()
            print(f'当前用户数量: {user}')
            return self.render('admin/index.html', user_count=user)
        return redirect(url_for('admin.login'))

    # 登录函数
    @expose('/login', methods=['GET', 'POST'])
    def login(self):
        if current_user.is_authenticated:
            return redirect(url_for('admin.index'))
        form = LoginForm()
        if form.validate_on_submit():
            username = form.username.data
            password = form.password.data
            user = AdminUsers.query.filter_by(username=username).first()
            if user and user.password == password:
                login_user(user)
                flash('您已登录成功!', 'success')
                return redirect(url_for('admin.index'))
            else:
                flash('用户名或密码错误!', 'danger')
        return self.render('admin/login.html', form=form)

    # 登出函数
    @expose('/logout/')
    def logout(self):
        logout_user()
        flash('您已成功登出!', 'success')
        return redirect(url_for('admin.login'))

    # 修改密码函数
    @expose('/change_password/', methods=['GET', 'POST'])
    def change_password(self):
        form = ChangePasswordForm()
        if form.validate_on_submit():
            current_user.password = form.new_password.data
            db.session.commit()
            flash('密码修改成功,请重新登录。', 'success')
            logout_user()  # 修改密码后登出用户
            return redirect(url_for('admin.login'))
        return self.render('admin/change_password.html', form=form)

    # 新增管理员用户
    @expose('/add_adminuser/', methods=['GET', 'POST'])
    def add_adminuser(self):
        form = AdminUserForm()
        if form.validate_on_submit():
            admin_user = AdminUsers(
                username=form.username.data,
                password=form.password.data
            )
            db.session.add(admin_user)
            db.session.commit()
            flash(f'管理员用户{admin_user.username}添加成功!', 'success')
            return redirect(url_for('admin.index'))
        return self.render('admin/add_adminuser.html', form=form)

# name:flask-admin后台的名称
# theme:设置flask-admin的主题,支持的主题在https://bootswatch.com/4/查看
admin = Admin(app, name='管理后台', theme=Bootstrap4Theme(swatch='Cerulean'), index_view=CustomAdminIndexView())
# 将UsersModelView视图添加到flask-admin管理
admin.add_view(UsersModelView(User, db.session, name='用户管理'))

# 初始化数据库
@app.cli.command('init_db')
def init_db():
    """Create the flaskadmin_example database."""
    db.drop_all()
    db.create_all()
    click.echo('Database created successfully!')

# 创建管理员用户
@app.cli.command('create_admin')
@click.option('--username', default='admin')
@click.option('--password', default='admin')
def create_admin(username, password):
    """Create a new admin user."""
    admin_user = AdminUsers(username=username, password=password)
    db.session.add(admin_user)
    db.session.commit()
    click.echo(f'Admin user {username}/{password} created successfully!')

# 创建普通用户
@app.cli.command('create_user')
@click.option('--count', default=10)
def create_user(count):
    """Number of users to create."""
    for _ in range(count):
        user = User(
            username=faker.user_name(),
            password=faker.password(),
            phone_number=faker.phone_number(),
            city=faker.city()
        )
        db.session.add(user)
        db.session.commit()
        click.echo(f'User {user.username} created successfully!')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

html模板的内容参考登录示例即可。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐