如何使用 SQLAlchemy 使用多个值初始化关联对象的字段?

How to initialize an association object's field with multiple values using SQLAlchemy?

上下文

我有三个 table、UsersSubredditsKeywords。思路是users可以监控多个subredditssubreddits可以监控多个keywords.

由于一个用户可以监控多个subreddit,一个subreddit可以有多个用户监控,我想在UsersSubreddits之间建立多对多关系。

同理,由于一个subreddit可以监控多个关键词,一个关键词可以被多个subreddit监控,我希望Subreddits和[=18之间是多对多的关系=].

我使用的是 association object 而不是 2 association tables,因为我有 3 个 table 想要相互关联,而关联 table 仅适用于 2 tables.

之间的关系

实施

以下是我如何实现每个table和关联对象:

用户

class User(db.Model, JsonSerializer):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(128), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    phone_num = db.Column(db.String(64), index=True, unique=True)
    received_posts = db.Column(db.String(128), index=True, unique=True)

    monitor = db.relationship('Monitor', back_populates='user')

    //...

子版块

class Subreddit(db.Model, JsonSerializer):
    __tablename__ = 'subreddits'

    id = db.Column(db.Integer, primary_key=True)
    subreddit_name = db.Column(db.String(128), index=True)

    // ...

关键字

class Keyword(db.Model, JsonSerializer):
    __tablename__ = 'keywords'

    id = db.Column(db.Integer, primary_key=True)
    keyword = db.Column(db.String(128), index=True)

    monitor = db.relationship('Monitor', back_populates='keyword')

    // ...

关联对象:

class Monitor(db.Model):
    __table_name__ = 'monitors'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    subreddit_id = db.Column(db.Integer, db.ForeignKey("subreddits.id"), nullable=True)
    keyword_id = db.Column(db.Integer, db.ForeignKey("keywords.id"), nullable=True)

    user = db.relationship('User', back_populates='monitor')
    subreddit = db.relationship('Subreddit')
    keyword = db.relationship('Keyword', back_populates='monitor')

我想做什么

理想情况下,每次 user 跟踪一个 subreddit 和相应的多个 keywords,所有这些信息都存储到一个 monitor 对象中。

例如,

这应该导致:

我尝试过的

方法一

正在用 keywords 列表初始化一个 monitor 实例。

# Parse the incoming data.
incoming = request.get_json()
subreddit_name = incoming["subredditName"]
subreddit_keywords = incoming["subredditKeywords"]
logged_in_user_id = incoming["id"]

# Query the User.
user = User.query.get(logged_in_user_id)

# Query the Subreddit.
subreddit = Subreddit.query.filter_by(subreddit_name=subreddit_name).first()

# Create a list of Keywords.
monitor_keywords = []
for kw in subreddit_keywords:
    # check if Keyword objects are in the database already
    if Keyword.query.filter_by(keyword=kw).first() is not None:
        keyword = Keyword.query.filter_by(keyword=kw).first()
    else: # create new Keyword objects
        keyword = Keyword(kw)
monitor_keywords.append(keyword)

# Create the monitoring1 association object.
monitor1 = Monitor(user=user, subreddit=subreddit, keyword=monitor_keywords) <----- Problem

db.session.add(monitor1)
db.session.commit()

我无法用 keyword=monitor_keywords 初始化 monitor1

我收到以下错误: AttributeError: 'list' object has no attribute '_sa_instance_state'。 Stack Overflow 上的其他提交与我的特定错误案例无关。

方法#2 到#5

我试过先初始化一个 monitor 对象,然后向其附加多个 keywords 但这也没有用。

# Query the User
user = User.query.get(logged_in_user_id)

# Create the association object
monitor = Monitor(user=user, subreddit=None, keyword=None)
db.session.add(monitor)

// ...

for kw in subreddit_keywords:
    # check if Keyword objects are in the database already
    if Keyword.query.filter_by(keyword=kw).first() is not None:
        keyword = Keyword.query.filter_by(keyword=kw).first()
    else: # create new Keyword objects
        keyword = Keyword(kw)

    # Initialize monitor.keyword so we can append to it.
    if (monitor.keyword is None):
        monitor.keyword = keyword
    else: #All failed attempts to append to monitor.keyword.
        # monitor.keyword.append(keyword) <- Method #2    
        # keyword.append(monitor)         <- Method #3 
        # monitor.append(keyword)         <- Method #4  
        # user.monitor.append(keyword)    <- Method #5   

问题

当我初始化一个关联对象时,我需要用多个keywords来初始化它。

无论是用keywords初始化关联对象,还是稍后用keywords更新关联对象,都不起作用。理想情况下,我想坚持使用方法 #1 的方法,但我愿意接受其他建议。

我对你的要求感到困惑,但我认为在这种情况下,你的意思似乎是让用户在特定的 subreddit 中收听特定的关键字?这意味着关联对象将跟踪每一个。为了简化复数形式,也许是 Monitor。对于列外键,用单数名称命名它们是有意义的。与复数或单数的关系与复数的 backref 形成对比。

class Monitor(db.Model):
    __table_name__ = 'monitors'

    id = db.Column(db.Integer, primary_key=True)
    # Singular
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    # Singular
    subreddit_id = db.Column(db.Integer, db.ForeignKey("subreddits.id"), nullable=True)
    # Singular
    keyword_id = db.Column(db.Integer, db.ForeignKey("keywords.id"), nullable=True)
    # Backref is all monitors for user across ALL subreddits and keywords.
    user = db.relationship('User', backref='monitors')
    # Backref is all monitors for subreddit across ALL users and keywords.
    subreddit = db.relationship('Subreddit', backref='monitors')
    # Backref is all monitors for keyword across ALL users and subreddits.
    keyword = db.relationship('Keyword', backref='monitors')
# Parse the incoming data.
incoming = request.get_json()
subreddit_name = incoming["subredditName"]
subreddit_keywords = incoming["subredditKeywords"]
logged_in_user_id = incoming["id"]

# Query the User.
user = User.query.get(logged_in_user_id)

# Query the Subreddit.
subreddit = Subreddit.query.filter_by(subreddit_name=subreddit_name).first()

# Create a list of Keywords.
for kw in subreddit_keywords:
    # Create Keyword in database (this is another problem if you want the names to be unique)
    keyword = Keyword(keyword=kw)
    # The subreddit and keyword this user is watching.
    db.session.add(Monitor(user=user, subreddit=subreddit, keyword=keyword))

db.session.commit()

# All subreddit/keyword pairs being watched by this user.
monitors = db.query(Monitor).filter(Monitor.user == user).all()

# All keywords being watched for this subreddit for this user
monitored_kws = db.query(Keyword).join(Keyword.monitors).filter(Monitor.user == user, Monitor.subreddit == subreddit).all()