【问题标题】:Many to many relationship with SQLAlchemy fails with 'Could not determine join condition between parent/child tables'与 SQLAlchemy 的多对多关系因“无法确定父/子表之间的连接条件”而失败
【发布时间】:2020-12-27 13:29:48
【问题描述】:

我已经使用 SQLAlchemy 成功设置了一个 API 端点。新的目标是让 json 与 tag 对象的嵌套数组一起返回。所以我最终会得到这样的结果:

{
    "favourite": "0",
    "location": {
        "urbanisation": "Montreal",
        "province": "Quebec"
    },
    "tags":[{
            "name":"celebration"
        },
        {
            "name":"national holiday"
        },
    
    ],
    "count_articles": 5,
    "scheduled": "1",
    "title": "TEST TITLE",
    "count_video": 5,
    "id": 1,
    "date": "2020-09-08",
    "count_audio": 5,
    "summary": "TEST DESCRIPTION"
}

尝试 1:

我知道我可以创建一个 many to many relationship in SQLAlchemy 并修改了我的 python 和数据库以适应这种变化。

这是连接表的处理方式

#JOINING TABLE FOR EVENTS AND TAGS
tag_association_table = Table('events_tags', db.metadata,
    db.Column('event_id', db.Integer, db.ForeignKey('events.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)

这是完整的代码:

from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import create_engine, Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from json import dumps
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, fields, auto_field
from flask_cors import CORS
from sqlalchemy.ext.declarative import declarative_base

import mysql.connector

#SQLAlchemy Docs: https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:password@localhost/news'
CORS(app, resources={r"/*": {"origins": "*"}})
db = SQLAlchemy(app)
api = Api(app)

#JOINING TABLE FOR EVENTS AND TAGS
tag_association_table = Table('events_tags', db.metadata,
    db.Column('event_id', db.Integer, db.ForeignKey('events.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)

#MODELS FOR EVENTS RESOURCE
class EventModel(db.Model):
    __tablename__ = "events"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    summary = db.Column(db.String, nullable=False)
    date = db.Column(db.String, nullable=True)
    scheduled = db.Column(db.String, nullable=True)
    favourite = db.Column(db.String, nullable=True)
    count_audio = db.Column(db.Integer, nullable=False)
    count_video = db.Column(db.Integer, nullable=False)
    count_articles = db.Column(db.Integer, nullable=False)
    location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False)
    location = db.relationship("LocationModel")
    tags = db.relationship("TagModel",secondary=tag_association_table)


class LocationModel(db.Model):
    __tablename__ = "location"

    id = db.Column(db.Integer, primary_key=True)
    province = db.Column(db.String, nullable=True)
    urbanisation = db.Column(db.String, nullable=True)


class TagModel(db.Model):
    __tablename__ = "tags"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)


#SERIALISER CLASSES
class EventSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = EventModel
        load_instance = True
        include_fk = False

    location = fields.Nested('LocationSchema', many=False)
    id = auto_field(load_only=False)
    
class LocationSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = LocationModel
        load_instance = True
        include_fk = False

    id = auto_field(load_only=True)

class TagSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = TagModel
        load_instance = True
        include_fk = False

    id = auto_field(load_only=True)


#LOADER
class Events(Resource):
    def get(self):
        schema = EventSchema()
        result = db.session.query(EventModel).all()
        return schema.dump(result, many=True), 200
        

api.add_resource(Events, '/events') # Route_1
#api.add_resource(Entries, '/events/event_id/entries>') # Route_2


if __name__ == '__main__':
     app.run(port='5002')

应用程序运行,但标签根本不返回。 JSON 输出完全缺少标签数组。有什么想法吗?

尝试 2:

我还尝试将我的课程组织为suggested here。这也可以运行,但不提供标签列表。在这种情况下,我们有一个连接模型,而不是连接表:

class EventsTagsLink(db.Model):
   __tablename__ = 'events_tags'

   event_id = db.Column(db.Integer, ForeignKey('events.id'), primary_key = True)
   tag_id = db.Column(db.Integer, ForeignKey('tags.id'), primary_key = True)

每个事件和标签模型都有一个之前定义的关系

class EventModel(db.Model):
    __tablename__ = "events"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    summary = db.Column(db.String, nullable=False)
    date = db.Column(db.String, nullable=True)
    scheduled = db.Column(db.String, nullable=True)
    favourite = db.Column(db.String, nullable=True)
    count_audio = db.Column(db.Integer, nullable=False)
    count_video = db.Column(db.Integer, nullable=False)
    count_articles = db.Column(db.Integer, nullable=False)
    location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False)
    location = db.relationship("LocationModel")
    tags = db.relationship("TagModel",secondary='events_tags')  


class TagModel(db.Model):
    __tablename__ = "tags"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    events = db.relationship("EventModel",secondary='events_tags')

【问题讨论】:

  • 我知道这不是直接回答你的问题,但你可能看过 sqlacodegen 吗? pypi.org/project/sqlacodegen 我用它来进行数据转换,并使用它从模式中生成的代码。如果你有一些多对多的关系,看看它是如何处理它的?
  • 谢谢,我会试一试
  • 顺便说一句:顾名思义,这是一个基于 SQL 的 -vs JSON db.. 但是如果你有类似 Northwind 或 Oracle 示例数据库之类的东西,你可以看到它是如何建模的.

标签: json python-3.x sqlalchemy


【解决方案1】:

通过确保我准确遵循文档,我终于设法让它工作。

我最终进一步探索了选项 1

关联表

我的原创

tag_association_table = Table('events_tags', db.metadata,
    db.Column('event_id', db.Integer, db.ForeignKey('events.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)

删除db.metadata 并将primary_key=True 添加到关联表中定义的每一列的工作版本。

tag_association_table = db.Table('events_tags',
    db.Column('event_id', db.Integer, db.ForeignKey('events.id'),primary_key=True),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'),primary_key=True)
)

事件和标签模型

我的原创

class EventModel(db.Model):
    __tablename__ = "events"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    summary = db.Column(db.String, nullable=False)
    date = db.Column(db.String, nullable=True)
    scheduled = db.Column(db.String, nullable=True)
    favourite = db.Column(db.String, nullable=True)
    count_audio = db.Column(db.Integer, nullable=False)
    count_video = db.Column(db.Integer, nullable=False)
    count_articles = db.Column(db.Integer, nullable=False)
    location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False)
    location = db.relationship("LocationModel")
    tags = db.relationship("TagModel",secondary=tag_association_table)

工作版本包含值backref="event" 与变量tags

class EventModel(db.Model):
    __tablename__ = "events"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String, nullable=False)
    summary = db.Column(db.String, nullable=False)
    date = db.Column(db.String, nullable=True)
    scheduled = db.Column(db.String, nullable=True)
    favourite = db.Column(db.String, nullable=True)
    count_audio = db.Column(db.Integer, nullable=False)
    count_video = db.Column(db.Integer, nullable=False)
    count_articles = db.Column(db.Integer, nullable=False)
    location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False)
    location = db.relationship("LocationModel")
    tags = db.relationship("TagModel", secondary=tag_association_table, backref="event") 

同样,标签模型现在包含值 backref="tag" 与变量 events

class TagModel(db.Model):
    __tablename__ = "tags"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    events = db.relationship("EventModel", secondary=tag_association_table, backref="tag")

我真的不明白为什么backref 是单数而不是复数,或者其他任何东西。但是,它有效。

【讨论】:

    猜你喜欢
    • 2022-01-11
    • 2018-07-17
    • 2016-03-18
    • 2017-11-26
    • 2017-08-18
    • 2023-03-17
    • 1970-01-01
    • 2014-05-26
    • 2017-10-30
    相关资源
    最近更新 更多