python - Flask-SQLAlchemy如何在MySQL(InnoDB)中使用级联来约束外键?

标签 python sqlalchemy flask-migrate

我一直在寻找在 SQLAlchemy 中使用 PyMySQL 驱动程序和数据库中使用 InnoDB 的 MariaDB 10.0 中的 UsersAccessMapping 模型中实现 CONSTRAINT FOREIGN KEY ON DELETE CASCADE 的方法。

Python = 3.5.2  
SQLAlchemy = 1.1.13  
Flask-SQLAlchemy = 2.2  

SQL:

CREATE TABLE Users (
    UserID int AUTO_INCREMENT,
    Name varchar(200) NOT NULL,
    Email varchar(200),
    Username varchar(200) NOT NULL,
    Password text NOT NULL,
    Created datetime,
    Updated datetime,
    PRIMARY KEY (UserID)
);

CREATE TABLE UsersAccessLevels (
    UsersAccessLevelID int AUTO_INCREMENT,
    LevelName varchar(100) NOT NULL,
    AccessDescription text,
    PRIMARY KEY (UsersAccessLevelID)
);

CREATE TABLE UsersAccessMapping (
    UsersAccessMappingID int AUTO_INCREMENT,
    UserID int NOT NULL,
    UsersAccessLevelID int NOT NULL,
    PRIMARY KEY (UsersAccessMappingID),
    CONSTRAINT fk_useraccess FOREIGN KEY (UserID)
        REFERENCES Users(UserID) ON DELETE CASCADE,
    CONSTRAINT fk_useraccess_level FOREIGN KEY (UsersAccessLevelID)
        REFERENCES UsersAccessLevels(UsersAccessLevelID) ON DELETE CASCADE
);

我的 models.py 现在有什么:

from app import db


class Users(db.Model):
    """All users' information is stored here"""
    __tablename__ = "Users"
    UserID = db.Column(db.Integer(), primary_key=True)
    Name = db.Column(db.String(200), nullable=False)
    Email = db.Column(db.String(200))
    Username = db.Column(db.String(200), nullable=False)
    Password = db.Column(db.Text, nullable=False)
    Created = db.Column(db.DateTime)
    Updated = db.Column(db.DateTime)


class UsersAccessLevels(db.Model):
    """This defines the various access levels users can have"""
    __tablename__ = "UsersAccessLevels"
    UsersAccessLevelID = db.Column(db.Integer, primary_key=True)
    LevelName = db.Column(db.String(100), nullable=False)
    AccessDescription = db.Column(db.Text)


class UsersAccessMapping(db.Model):
    """Each users' access level is defined here"""
    __tablename__ = "UsersAccessMapping"
    UsersAccessMappingID = db.Column(db.Integer, primary_key=True)
    UserID = db.Column(db.Integer, nullable=False)
    UsersAccessLevelID = db.Column(db.Integer, nullable=False)
    __table_args__ = (
        db.ForeignKeyConstraint(
            ["fk_useraccess", "fk_useraccess_level"],
            ["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"],
            ondelete="CASCADE"
        )
    )

table_args 语法有问题,但我找不到任何关于它应该如何的示例。我发现一个非常相似,但第三个参数是一个空字典。但是,我想使用 ondelete="CASCADE"。如何添加?

运行python3 manage.py db init时,它会抛出以下错误:

  File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/ext/declarative/base.py", line 196, in _scan_attributes
    "__table_args__ value must be a tuple, "
sqlalchemy.exc.ArgumentError: __table_args__ value must be a tuple, dict, or None

我尝试将 ondelete="cascade" 更改为字典 {"ondelete": "cascade"},但这也不起作用。它给出了与上面相同的错误。

更新: 问题是 ondelete 应该在元组之外,如下所示:

__table_args__ = (
    db.ForeignKeyConstraint(
        ["fk_useraccess", "fk_useraccess_level"],
        ["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"]
    ),
    ondelete="CASCADE"
)

但是,经过此更改,仍然存在语法错误,因为未定义 ondelete="CASCADE"。将其更改为字典 {"ondelete": "cascade"} 会抛出以下错误:

  File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/sql/base.py", line 282, in _validate_dialect_kwargs
    "named <dialectname>_<argument>, got '%s'" % k)
TypeError: Additional arguments should be named <dialectname>_<argument>, got 'ondelete'

最佳答案

好吧,经过一些测试和阅读,答案是 SQLAlchemy 做了一些内部魔法来实现它。因此,这将实现与 SQL 相同的结果:

from app import db  # The value is from: db = SQLAlchemy(app)


class Users(db.Model):
    """All users' information is stored here"""
    __tablename__ = "Users"
    UserID = db.Column(db.Integer(), primary_key=True)
    Name = db.Column(db.String(200), nullable=False)
    Email = db.Column(db.String(200))
    Username = db.Column(db.String(200), nullable=False)
    Password = db.Column(db.Text, nullable=False)
    Created = db.Column(db.DateTime)
    Updated = db.Column(db.DateTime)


class UsersAccessLevels(db.Model):
    """This defines the various access levels users can have"""
    __tablename__ = "UsersAccessLevels"
    UsersAccessLevelID = db.Column(db.Integer, primary_key=True)
    LevelName = db.Column(db.String(100), nullable=False)
    AccessDescription = db.Column(db.Text)


class UsersAccessMapping(db.Model):
    """Each users' access level is defined here"""
    __tablename__ = "UsersAccessMapping"
    UsersAccessMappingID = db.Column(db.Integer, primary_key=True)
    UserID = db.Column(
        db.Integer, db.ForeignKey("Users.UserID", ondelete="CASCADE"), nullable=False
    )
    UsersAccessLevelID = db.Column(
        db.Integer,
        db.ForeignKey("UsersAccessLevels.UsersAccessLevelID", ondelete="CASCADE"),
        nullable=False
    )

约束等是通过列定义中的db.ForeignKey() 参数自动处理的。它不需要像 SQL 那样直接在表上完成。

外键的名称似乎也是由 SQLAlchemy 自动生成的。这是它在数据库中的样子:

enter image description here

关于python - Flask-SQLAlchemy如何在MySQL(InnoDB)中使用级联来约束外键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46012785/

相关文章:

python - 如何使用Docker Compose管理Flask中的迁移?

python - 无效路径,不允许使用反斜杠

Python-向描述符添加属性

python - 从装饰器内部获取路由值

python - 无法使用 Flask-Migrate (Alembic) 迁移或升级数据库

flask 迁移 "ValueError: Constraint must have a name"

python - 使用 Python 从 Scapy 获取变量

Flask-WTForms 和 SQLAalchemy SelectField 的关系

python - 使用 sqlalchemy 语句和表达式 api 生成多个连接

python - sqlalchemy 获取同义词和关系的字段类型