Flask-SQLAlchemy 多对多关系:用户与角色权限管理的正确实践

flask-sqlalchemy 多对多关系:用户与角色权限管理的正确实践

本文详细介绍了如何在 Flask 应用中利用 SQLAlchemy 实现用户与角色之间的多对多关系。我们将通过一个博客应用的示例,展示如何正确定义关联表、用户模型和角色模型,并纠正常见的 `InvalidRequestError`,特别是由于模型类命名不规范或关系属性配置错误导致的问题,确保关系配置的清晰与准确。

理解多对多关系及其应用场景

在Web应用开发中,多对多关系是一种常见的数据库设计模式。例如,一个用户可以拥有多个角色(如“管理员”、“编辑”、“普通用户”),同时一个角色也可以分配给多个用户。在 SQLAlchemy 中,实现这种关系通常需要一个中间表(也称为关联表或连接表)来连接两个主表。

定义关联表

关联表是多对多关系的核心。它只包含两个外键,分别指向两个主表的ID。在 Flask-SQLAlchemy 中,我们可以使用 db.Table 来定义它。

import sqlalchemy as saimport sqlalchemy.orm as sofrom datetime import datetime, timezonefrom typing import Optionalfrom flask_login import UserMixinfrom werkzeug.security import generate_password_hash, check_password_hashfrom hashlib import md5from flask_security.models import fsqla_v3 as fsqlafrom app import db, login # 假设 db 和 login 已在 app.py 中初始化# 定义用户与角色之间的关联表roles_users_table = db.Table(    "roles_users_table", # 关联表的名称    db.metadata,    sa.Column("user_id", sa.Integer, sa.ForeignKey("user.id"), primary_key=True),    sa.Column("role_id", sa.Integer, sa.ForeignKey("role.id"), primary_key=True) # 注意这里是 "role.id")

注意事项:

关联表的名称应清晰反映其连接的两个实体,例如 roles_users_table。外键 (ForeignKey) 引用的是目标表的小写名称和其主键,例如 user.id 和 role.id。两个外键共同构成联合主键 (primary_key=True),确保每对用户-角色关系的唯一性。

定义用户模型 (User)

用户模型需要包含其基本信息,并定义与角色模型的多对多关系。

class User(fsqla.FsUserMixin, db.Model):    id: so.Mapped[int] = so.mapped_column(primary_key=True)    email: so.Mapped[str] = so.mapped_column(sa.String(120), index=True, unique=True)    password_hash: so.Mapped[Optional[str]] = so.mapped_column(sa.String(256))    about_me: so.Mapped[Optional[str]] = so.mapped_column(sa.String(140))    last_seen: so.Mapped[Optional[datetime]] = so.mapped_column(default=lambda: datetime.now(timezone.utc))    # 定义与 Post 模型的一对多关系    posts: so.WriteOnlyMapped['Post'] = so.relationship(back_populates='author')    # 定义与 Role 模型的多对多关系    # 'roles' 属性将包含一个 Role 对象的列表    roles: so.Mapped[list['Role']] = so.relationship(        secondary=roles_users_table, # 指定关联表        back_populates="users"       # 指定 Role 模型中对应的反向关系属性    )    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)    def avatar(self, size):        digest = md5(self.email.lower().encode('utf-8')).hexdigest()        return f'https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}'    def __repr__(self):        return ''.format(self.email)# 假设 Post 模型也已定义class Post(db.Model):    id: so.Mapped[int] = so.mapped_column(primary_key=True)    body: so.Mapped[str] = so.mapped_column(sa.String(140))    timestamp: so.Mapped[datetime] = so.mapped_column(index=True, default=lambda: datetime.now(timezone.utc))    user_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey(User.id), index=True)    author: so.Mapped[User] = so.relationship(back_populates='posts')    def __repr__(self):        return ''.format(self.body)

注意事项:

roles: so.Mapped[list[‘Role’]] 表示一个 User 对象可以关联多个 Role 对象。secondary=roles_users_table 指明了用于连接 User 和 Role 的关联表。back_populates=”users” 建立了双向关系。这意味着在 Role 模型中,需要有一个名为 users 的属性来指向关联的 User 对象列表。

定义角色模型 (Role)

角色模型需要包含角色名称等信息,并定义与用户模型的多对多关系。

class Role(db.Model, fsqla.FsRoleMixin): # 注意:类名应为单数 'Role'    id: so.Mapped[int] = so.mapped_column(primary_key=True)    name: so.Mapped[str] = so.mapped_column(sa.String(80), unique=True) # 角色名称,应为字符串类型    description: so.Mapped[Optional[str]] = so.mapped_column(sa.String(255)) # 角色描述(可选)    # 定义与 User 模型的多对多关系    # 'users' 属性将包含一个 User 对象的列表    users: so.Mapped[list['User']] = so.relationship(        secondary=roles_users_table, # 指定关联表        back_populates="roles"       # 指定 User 模型中对应的反向关系属性    )    def __repr__(self):        return ''.format(self.name)

关键修正与注意事项:

类名应为单数:Role 而非 Roles。 这是导致原始 sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper[User(user)], expression ‘Role’ failed to locate a name (‘Role’). 错误的主要原因。SQLAlchemy 在解析关系时,通常期望引用的是模型类的精确名称。如果 User 模型中 so.Mapped[list[‘Role’]] 引用的是 Role,但实际类名为 Roles,就会出现找不到映射的错误。name 属性类型:so.Mapped[str]。 角色名称通常是一个字符串(如 “admin”, “editor”),而不是一个 User 对象。原始代码中的 name: so.Mapped[User] 是不正确的。back_populates 属性一致性: User 模型中的 back_populates=”users” 必须与 Role 模型中用于反向关系的属性名 users 保持一致。

完整示例代码

结合上述修正,以下是完整的模型定义代码:

from datetime import datetime, timezonefrom typing import Optionalimport sqlalchemy as saimport sqlalchemy.orm as sofrom app import db, login # 假设 db 和 login 已在 app.py 中初始化from werkzeug.security import generate_password_hash, check_password_hashfrom flask_login import UserMixinfrom flask_security.models import fsqla_v3 as fsqlafrom hashlib import md5# 定义用户与角色之间的关联表roles_users_table = db.Table(    "roles_users_table",    db.metadata,    sa.Column("user_id", sa.Integer, sa.ForeignKey("user.id"), primary_key=True),    sa.Column("role_id", sa.Integer, sa.ForeignKey("role.id"), primary_key=True))class User(fsqla.FsUserMixin, db.Model):    id: so.Mapped[int] = so.mapped_column(primary_key=True)    email: so.Mapped[str] = so.mapped_column(sa.String(120), index=True, unique=True)    password_hash: so.Mapped[Optional[str]] = so.mapped_column(sa.String(256))    posts: so.WriteOnlyMapped['Post'] = so.relationship(back_populates='author')    about_me: so.Mapped[Optional[str]] = so.mapped_column(sa.String(140))    last_seen: so.Mapped[Optional[datetime]] = so.mapped_column(default=lambda: datetime.now(timezone.utc))    # 定义与 Role 模型的多对多关系    roles: so.Mapped[list['Role']] = so.relationship(        secondary=roles_users_table,        back_populates="users"    )    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)    def __repr__(self):        return ''.format(self.email)    def avatar(self, size):        digest = md5(self.email.lower().encode('utf-8')).hexdigest()        return f'https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}'class Post(db.Model):    id: so.Mapped[int] = so.mapped_column(primary_key=True)    body: so.Mapped[str] = so.mapped_column(sa.String(140))    timestamp: so.Mapped[datetime] = so.mapped_column(index=True, default=lambda: datetime.now(timezone.utc))    user_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey(User.id), index=True)    author: so.Mapped[User] = so.relationship(back_populates='posts')    def __repr__(self):        return ''.format(self.body)class Role(db.Model, fsqla.FsRoleMixin): # 正确的类名:Role    id: so.Mapped[int] = so.mapped_column(primary_key=True)    name: so.Mapped[str] = so.mapped_column(sa.String(80), unique=True) # 角色名称,字符串类型    description: so.Mapped[Optional[str]] = so.mapped_column(sa.String(255)) # 角色描述(可选)    # 定义与 User 模型的多对多关系    users: so.Mapped[list['User']] = so.relationship(        secondary=roles_users_table,        back_populates="roles"    )    def __repr__(self):        return ''.format(self.name)

如何创建和关联数据

在上述模型定义正确后,你可以像这样创建角色和用户,并建立它们之间的关系:

# 假设 db 已经初始化并且应用上下文已激活# from app import app, db, User, Role# with app.app_context():# 创建角色admin_role = Role(name="admin", description="Administrator role")editor_role = Role(name="editor", description="Editor role")user_role = Role(name="user", description="Standard user role")db.session.add_all([admin_role, editor_role, user_role])db.session.commit()# 创建用户user1 = User(email="test1@example.com", password_hash="hashed_password_1")user2 = User(email="test2@example.com", password_hash="hashed_password_2")db.session.add_all([user1, user2])db.session.commit()# 将角色分配给用户user1.roles.append(admin_role)user1.roles.append(editor_role) # user1 拥有 admin 和 editor 两个角色user2.roles.append(user_role) # user2 拥有 user 角色db.session.commit()# 查询用户及其角色print(f"User 1 roles: {[r.name for r in user1.roles]}")# 输出: User 1 roles: ['admin', 'editor']# 查询角色下的用户print(f"Admin role users: {[u.email for u in admin_role.users]}")# 输出: Admin role users: ['test1@example.com']

总结

正确配置 Flask-SQLAlchemy 中的多对多关系是构建复杂应用的关键一步。主要需要注意以下几点:

关联表定义: 使用 db.Table 定义中间表,包含两个主键外键。模型类命名: 确保模型类名与 so.Mapped 或 so.relationship 中引用的名称一致,通常使用单数形式(如 Role 而非 Roles)。这是避免 InvalidRequestError 的常见且重要的一点。关系属性类型: so.Mapped[list[‘ModelName’]] 用于表示多对多关系中包含的是另一个模型对象的列表。secondary 参数: 在 so.relationship 中正确指定关联表。back_populates 参数: 确保双向关系的属性名在两个模型中保持一致,以便于双向导航。遵循这些最佳实践,可以有效避免常见的配置错误,并构建出健壮的 Flask 应用数据模型。

以上就是Flask-SQLAlchemy 多对多关系:用户与角色权限管理的正确实践的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1382156.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 23:39:03
下一篇 2025年12月14日 23:39:21

相关推荐

  • Python教程:在多行文本文件中根据关键词查找并打印指定行

    本教程详细介绍了如何使用python在多行文本文件中查找包含特定关键词的行。通过文件逐行读取和字符串包含性检查的组合,我们将展示一种简洁高效的方法来定位并输出所需内容。文章涵盖了文件路径处理、核心代码实现,并提供了进一步优化和扩展的建议,帮助开发者灵活处理文本数据。 在日常编程任务中,我们经常需要处…

    2025年12月14日
    000
  • Python Tkinter iconphoto() 方法详解:设置应用程序图标

    本文旨在详细解释 Python Tkinter 中 `iconphoto()` 方法的使用,重点区分 `default` 参数为 `True` 和 `False` 时的行为差异,帮助开发者正确设置应用程序图标。 在 Tkinter 中,iconphoto() 方法用于设置应用程序的窗口图标。该方法接…

    2025年12月14日
    000
  • Python多目标优化:解决复杂座位分配问题的策略与实践

    本文探讨如何利用Python解决复杂的活动座位分配问题,特别是涉及多方偏好和动态变化的场景。我们将深入了解优化、多目标优化及启发式算法的核心概念,并讨论如何构建一个能够平衡宾客偏好与场地优先级,并有效应对突发情况的自动化解决方案。 在活动组织和资源分配场景中,如何高效地为参与者分配座位,同时满足多方…

    2025年12月14日
    000
  • Python FileNotFoundError:文件路径疑难解析与解决方案

    本教程旨在解决python中常见的`filenotfounderror`,特别是当文件路径看似正确却仍然报错时。文章将深入探讨文件相对路径与绝对路径的原理,指导用户如何准确识别当前工作目录和目标文件路径,并提供可靠的代码示例与最佳实践,确保文件能够被成功访问。 理解 FileNotFoundErro…

    2025年12月14日
    000
  • Hatch 虚拟环境存储位置管理与自定义

    hatch 默认将虚拟环境统一存储在其管理的特定数据目录中,而非当前项目根目录,旨在提供更集中的环境管理。本文将深入探讨 hatch 这一设计理念,并详细指导用户如何利用 `–data-dir` 选项自定义虚拟环境的存储路径,实现将虚拟环境创建在项目目录内部,并提供清晰的操作示例。 Ha…

    2025年12月14日
    000
  • 本地加载TensorFlow MNIST .npz数据集教程

    本教程旨在解决tensorflow中因网络连接问题导致mnist数据集无法通过`tf.keras.datasets.mnist.load_data()`在线加载的困境。我们将详细指导用户如何手动下载`mnist.npz`文件,并利用numpy库将其高效、准确地加载到本地环境中,从而确保机器学习项目的…

    2025年12月14日
    000
  • Python nonlocal关键字使用指南:何时以及为何需要它

    nonlocal关键字在python中用于指示一个变量赋值操作应作用于最近的非全局(enclosing)作用域中的变量,而非在当前函数内创建新的局部变量。理解其核心在于区分对变量的“重新赋值”与对可变对象“内容修改”:只有当你想在内层函数中改变外层函数变量所引用的对象时,才需要使用nonlocal。…

    2025年12月14日
    000
  • C++ OpenSSL AES CBC解密乱码问题解析与EVP API最佳实践

    本文深入探讨了使用C++ OpenSSL低级API(如`AES_cbc_encrypt`)进行AES CBC模式加密时,解密数据开头出现乱码的问题。核心原因在于低级API会原地修改初始化向量(IV),导致解密时无法获取正确的IV。文章强调应避免使用这些低级函数,并详细介绍了OpenSSL推荐的高级E…

    2025年12月14日
    000
  • KerasTuner超参数优化中自定义指标(如F1、AUC)的正确配置方法

    kerastuner在超参数优化中使用f1、auc等自定义指标作为优化目标时,常因keyerror导致失败。本文提供详细教程,指导如何正确配置kerastuner的objective。核心在于理解kerastuner对指标名称的约定(val_metric_name_string),以及确保模型在编译…

    2025年12月14日
    000
  • PyCharm项目文件夹在macOS中消失的解决方案:文件权限配置指南

    本文旨在解决macos用户在使用pycharm时遇到的项目文件夹从项目面板消失的问题。核心原因通常是macos的文件权限限制,而非pycharm本身的bug。教程将详细指导如何在系统设置中为pycharm配置正确的访问权限,确保项目文件正常显示和操作,从而解决此困扰。 问题描述 许多PyCharm用…

    2025年12月14日
    000
  • Selenium自动化测试中iframe元素定位与交互指南

    本文旨在解决selenium自动化测试中因iframe导致元素无法定位的问题。当目标元素嵌套在iframe中时,selenium需要先切换到对应的iframe上下文,才能成功识别并操作其中的元素。文章将详细介绍如何识别iframe、切换到iframe以及在iframe内进行元素操作,并提供实用的代码…

    2025年12月14日
    000
  • 解决C++ OpenSSL低级AES解密乱码:推荐使用EVP API

    本文旨在解决C++ OpenSSL低级AES函数(如`AES_cbc_encrypt`)在与其他语言(如Python)进行数据加解密时出现的乱码问题。文章将深入分析低级API的潜在陷阱,并强烈推荐使用OpenSSL的高级EVP API,提供详细的C++ EVP加密示例及关键注意事项,以确保跨平台加解…

    2025年12月14日
    000
  • 深入理解 Python nonlocal 关键字:作用域与变量操作

    本教程深入探讨 python 中 `nonlocal` 关键字的用法。它主要用于允许嵌套函数修改其直接外层(非全局)作用域中的变量,而非创建新的局部变量。文章通过对比变量的重新赋值与可变对象内容的修改,详细阐述 `nonlocal` 的适用场景,并提供代码示例以加深理解,帮助开发者避免常见误区。 在…

    2025年12月14日
    000
  • KerasTuner超参数调优中集成自定义指标(F1、AUC等)的实践指南

    本文旨在解决kerastuner在使用f1分数、auc等非默认指标作为超参数调优目标时遇到的`keyerror`问题。核心在于理解kerastuner如何识别并记录指标,并提供一套实用的方法,指导用户正确地在keras模型中编译这些指标,并以kerastuner期望的命名格式(如`val_f1_sc…

    2025年12月14日
    000
  • Flask应用中安全初始化SQLAlchemy数据:避免循环导入的最佳实践

    在flask应用中集成flask-sqlalchemy并添加初始数据时,常遇到模型与应用实例间因数据库对象引用导致的循环导入问题。本文将详细阐述这一问题的成因,并提供一种优雅的解决方案:通过引入独立的扩展文件来集中管理sqlalchemy实例,从而有效解耦模块依赖,确保应用初始化与数据填充过程的顺畅…

    2025年12月14日
    000
  • Pandas数据清洗:解决基于部分字符串删除行不生效的问题

    本教程详细阐述了在pandas中如何高效、准确地根据列中包含的特定部分字符串来删除数据行。针对常见的问题,如大小写敏感性导致筛选失败,文章重点介绍了使用`df.column.str.contains()`方法时,结合`case=false`参数进行不区分大小写的匹配,并利用`na=false`处理缺…

    2025年12月14日
    000
  • 如何为Wagtail站点实现高效的URL路径限流

    本文旨在探讨Wagtail CMS中URL路径限流的最佳实践。虽然Wagtail的页面对象提供类似Django视图的`serve`方法,理论上可应用限流装饰器,但此方法效率低下,因数据库查询已发生。因此,推荐在Web服务器层面(如Nginx)或通过外部服务(如Cloudflare)实施限流,以确保更…

    2025年12月14日
    000
  • 利用importlib实现Python大型数组内存驻留及代码热更新

    在python开发中,处理大型数组并频繁迭代更新处理逻辑时,重复加载数据会显著拖慢开发效率。本文将介绍一种基于python原生`importlib`模块的解决方案,通过将大型数组加载到内存中一次,并动态重新加载包含处理逻辑的模块,实现代码的热更新和快速测试,从而避免不必要的磁盘i/o开销,大幅提升开…

    2025年12月14日
    000
  • 深入理解 Python 的 nonlocal 关键字

    nonlocal 关键字用于在嵌套函数中修改其外层(非全局)作用域中的变量。它解决了在内层函数中对外部变量进行赋值操作时,Python 默认创建局部变量的问题,确保了对预期变量的直接修改。本文将详细阐述 nonlocal 的作用机制、适用场景,并与 global 关键字进行对比,辅以代码示例,帮助开…

    2025年12月14日
    000
  • 如何诊断Python multiprocessing.Pool 中无响应的进程

    当Python的`multiprocessing.Pool`在执行任务时出现`TimeoutError`或长时间无响应,即使任务队列看似已空,这通常表明池中的一个或多个工作进程卡住。本文将详细介绍如何利用`Process`对象的`exitcode`属性来识别这些停滞的进程,从而帮助开发者定位问题根源…

    2025年12月14日
    100

发表回复

登录后才能评论
关注微信