尝试使用 SQLAclhemy+Flask 实现多对多 ORM 数据模型时,Mapper“NoReferencedTableError”异常

问题描述 投票:0回答:1

我正在为当地一个小型社区的项目做志愿者,同时学习 Python。这是我第一次使用 Flask 和 SQLAlchemy(MySQL 用于存储数据)。我选择的 ORM 一开始工作得很好,但是当我尝试按照这个官方指南在两个表之间切换到多对多模型时,这个错误“NoReferencedTableError”在主 Flask 应用程序的初始化期间开始出现。

这是完整的错误行:

sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'assoc_tbl_1_user_X_challenge.challengeId' could not find table 'challengesTbl' with which to generate a foreign key to target column 'id'

无论我多么努力,我都无法发现我的定义中的错误。我试图用谷歌搜索这个错误,但其他情况似乎是关于人们在表或类名中犯了一些拼写错误 - 我仔细检查过,这里没有拼写错误。我开始怀疑这可能与以下事实有关:

association_table1
变量是在定义其他表的类之前定义的,并且在实例化它时找不到它们。但即使我将它移动到文件末尾,它仍然不起作用,因为它是从其他表类的定义中引用的。首先声明它并在文件末尾初始化也没有帮助。

我还注意到,当我尝试运行使用相同 ORM 文件预初始化数据库的外部脚本时,似乎只创建了三个表:

mysql> show tables;
+------------------------------+
| Tables_in_nekoko$ABsitedb    |
+------------------------------+
| challengesTbl                |
| rideRecordingsTbl            |
| usersTbl                     |
+------------------------------+
3 rows in set (0.00 sec)

我没有看到关联表

assoc_tbl_1_user_X_challenge
,不是应该同时创建吗?

我当前的数据模型是这样的:

from . import db
from flask_login import UserMixin
from sqlalchemy import text

class Base(db.DeclarativeBase):
    pass

association_table1 = db.Table(
    "assoc_tbl_1_user_X_challenge",
    Base.metadata,
    db.Column("userId", db.ForeignKey("usersTbl.id"), primary_key=True),
    db.Column("challengeId", db.ForeignKey("challengesTbl.id"), primary_key=True),
)

class User(UserMixin, db.Model):
    __tablename__ = "usersTbl"
    id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(1000))
    givenName = db.Column(db.String(100))
    sn = db.Column(db.String(100))
    totalDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    groups = db.Column(db.JSON, nullable=False)
    isActive = db.Column(db.Boolean, default=True, nullable=False, index=True)
    challenges = db.relationship('Challenge', secondary=association_table1, back_populates="participants")
    rides = db.relationship('Ride', backref='usersTbl', lazy=True)

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

class Ride(db.Model):
    __tablename__ = "rideRecordingsTbl"
    id = db.Column(db.Integer, primary_key=True)
    rideRecording = db.Column(db.LargeBinary)
    #rideRecording = db.Column(db.LONGBLOB, nullable=False)
    rideName = db.Column(db.String(150))
    distanceKm = db.Column(db.Float, server_default=text("0.0"))
    duration = db.Column(db.Time)
    uploadDateTime = db.Column(db.DateTime)
    stream_hash = db.Column(db.String(1000))
    ownerId = db.Column(db.Integer, db.ForeignKey('usersTbl.id'), nullable=False)

    def __repr__(self):
        return f'<Ride "{self.rideName[:20]}...">'

class Challenge(db.Model):
    __tablename__ = "challengesTbl"
    id = db.Column(db.Integer, primary_key=True)
    rideRecording = db.Column(db.LargeBinary)
    challengeName = db.Column(db.String(150))
    challengeTargetDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    challengeCurrnetTotalDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    challengeEnds = db.Column(db.DateTime)
    challengeStarts = db.Column(db.DateTime)
    createdByUserId = db.Column(db.Integer)
    isActive = db.Column(db.Boolean, default=True, nullable=False, index=True)
    participants = db.relationship('User', secondary=association_table1, back_populates="challenges")

    def __repr__(self):
        return f'<Challenge "{self.challengeName[:20]}...">'

python mysql flask sqlalchemy orm
1个回答
0
投票

您提供的错误通常是由于定义表和关系的顺序而发生的。

如果您在其他表之前定义关联表,SQLAlchemy 可能不具备正确解析外键所需的所有信息。您需要确保在定义关联表本身之前定义关联表中的外键引用的表。

因此,您可以使用带有

Base
的声明性基本方法来确保正确的排序,然后在已定义
User
Challenge
类的类定义中定义关联表。

因此,首先定义

association
after 定义
User
Challenge
类。然后,对于关系也是如此:在定义
User.challenges
后添加
Challenge.participants
association_table1
。 所以,你的最终代码应该看起来或多或少像这样:

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from sqlalchemy import text

db = SQLAlchemy()

class Base(db.Model):
    #this will make the base class abstract, so it won't be mapped to a table.
    __abstract__ = True

#define User and Challenge classes before the association table
class User(UserMixin, Base):
    __tablename__ = "usersTbl"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(1000))
    givenName = db.Column(db.String(100))
    sn = db.Column(db.String(100))
    totalDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    groups = db.Column(db.JSON, nullable=False)
    isActive = db.Column(db.Boolean, default=True, nullable=False, index=True)
    rides = db.relationship('Ride', backref='user', lazy=True)

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

class Ride(Base):
    __tablename__ = "rideRecordingsTbl"
    id = db.Column(db.Integer, primary_key=True)
    rideRecording = db.Column(db.LargeBinary)
    rideName = db.Column(db.String(150))
    distanceKm = db.Column(db.Float, server_default=text("0.0"))
    duration = db.Column(db.Time)
    uploadDateTime = db.Column(db.DateTime)
    stream_hash = db.Column(db.String(1000))
    ownerId = db.Column(db.Integer, db.ForeignKey('usersTbl.id'), nullable=False)

    def __repr__(self):
        return f'<Ride "{self.rideName[:20]}...">'

class Challenge(Base):
    __tablename__ = "challengesTbl"
    id = db.Column(db.Integer, primary_key=True)
    rideRecording = db.Column(db.LargeBinary)
    challengeName = db.Column(db.String(150))
    challengeTargetDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    challengeCurrentTotalDistanceKm = db.Column(db.Float, server_default=text("0.0"))
    challengeEnds = db.Column(db.DateTime)
    challengeStarts = db.Column(db.DateTime)
    createdByUserId = db.Column(db.Integer)
    isActive = db.Column(db.Boolean, default=True, nullable=False, index=True)
    
    def __repr__(self):
        return f'<Challenge "{self.challengeName[:20]}...">'

#define association table after User and Challenge classes
association_table1 = db.Table(
    "assoc_tbl_1_user_X_challenge",
    Base.metadata,
    db.Column("userId", db.Integer, db.ForeignKey("usersTbl.id"), primary_key=True),
    db.Column("challengeId", db.Integer, db.ForeignKey("challengesTbl.id"), primary_key=True),
)

#add relationships after defining the association table
User.challenges = db.relationship('Challenge', secondary=association_table1, back_populates="participants")
Challenge.participants = db.relationship('User', secondary=association_table1, back_populates="challenges")


让我更新,希望它有效。

© www.soinside.com 2019 - 2024. All rights reserved.