PythonAlchemy:“无法确定嵌套关系的关系方向

PythonAlchemy:“无法确定嵌套关系的关系方向,python,sqlite,flask-sqlalchemy,Python,Sqlite,Flask Sqlalchemy,我正在尝试构建一个双动态表单页面,其中下拉菜单中的表单项和选择选项都是根据所选组织和用户的语言偏好从数据库中读取的 在代码中,用户\数据\类型表示表单项,响应\选项表示下拉菜单选项 我收到的错误消息是: 无法确定关系“UserDataType.response\u option\u names”的关系方向-外键列不在父级中 也不是孩子的映射表 这个错误消息意味着什么,我遗漏了什么/如何修复它?问题似乎在UserDataType类定义中 Python代码部分: org = Org.query.fi

我正在尝试构建一个双动态表单页面,其中下拉菜单中的表单项和选择选项都是根据所选组织和用户的语言偏好从数据库中读取的

在代码中,用户\数据\类型表示表单项,响应\选项表示下拉菜单选项

我收到的错误消息是: 无法确定关系“UserDataType.response\u option\u names”的关系方向-外键列不在父级中 也不是孩子的映射表

这个错误消息意味着什么,我遗漏了什么/如何修复它?问题似乎在UserDataType类定义中

Python代码部分:

org = Org.query.filter_by(slug=slug).first() #works fine

language_pref = logged_in_user.language_pref_id #works fine

user_data_types = OrgDataType.query.filter_by(org_id=org.id).with_entities(OrgDataType.user_data_type_id) #works fine

user_data_type_details = UserDataType.query.filter(UserDataType.id.in_(user_data_types)).\
filter_by(language_id=language_pref).\
join(UserDataTypeResponseOption, UserDataType.id == UserDataTypeResponseOption.user_data_type_id).\
join(ResponseOption, UserDataTypeResponseOption.response_option_id == ResponseOption.id).\
filter_by(language_id=language_pref)\
#I think this works, see equivalent(?) SQL query further below

if request.method == "GET":
    return render_template("myform/pref_edit.html", user_data_type_details=user_data_type_details)
{% for user_data_type_detail in user_data_type_details %}
  <div>
    <select name="user_data_type_detail.user_data_type_name">
    {% for response_option in user_data_type_detail.reponse_option_names %}
      <option value="{{ response_option.response_option_name }}">{{ response_option.response_option_name }}</option>
    {% endfor %}
    </select>
  </div>
{% endfor %}
HTML代码部分:

org = Org.query.filter_by(slug=slug).first() #works fine

language_pref = logged_in_user.language_pref_id #works fine

user_data_types = OrgDataType.query.filter_by(org_id=org.id).with_entities(OrgDataType.user_data_type_id) #works fine

user_data_type_details = UserDataType.query.filter(UserDataType.id.in_(user_data_types)).\
filter_by(language_id=language_pref).\
join(UserDataTypeResponseOption, UserDataType.id == UserDataTypeResponseOption.user_data_type_id).\
join(ResponseOption, UserDataTypeResponseOption.response_option_id == ResponseOption.id).\
filter_by(language_id=language_pref)\
#I think this works, see equivalent(?) SQL query further below

if request.method == "GET":
    return render_template("myform/pref_edit.html", user_data_type_details=user_data_type_details)
{% for user_data_type_detail in user_data_type_details %}
  <div>
    <select name="user_data_type_detail.user_data_type_name">
    {% for response_option in user_data_type_detail.reponse_option_names %}
      <option value="{{ response_option.response_option_name }}">{{ response_option.response_option_name }}</option>
    {% endfor %}
    </select>
  </div>
{% endfor %}
在SQLite DB Browser中创建的表如下所示:

CREATE TABLE "user_data_type" (
    "id"    INTEGER NOT NULL,
    "language_id"   INTEGER NOT NULL,
    "user_data_type_name"   TEXT,
    "user_data_type_description"    TEXT,
    PRIMARY KEY("id","language_id")
);

CREATE TABLE "user_data_type_response_option" (
    "id"    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    "user_data_type_id" INTEGER,
    "response_option_id"    INTEGER,
    FOREIGN KEY("response_option_id") REFERENCES "response_option"("id"),
    FOREIGN KEY("user_data_type_id") REFERENCES "user_data_type"("id")
);

CREATE TABLE "response_option" (
    "id"    INTEGER NOT NULL,
    "language_id"   INTEGER NOT NULL,
    "response_option_name"  TEXT,
    PRIMARY KEY("id","language_id")
);
在SQLite DB浏览器中,我可以运行此SQL查询


select user_data_type.id, user_data_type_name, response_option_id, response_option_name
   from user_data_type
   join user_data_type_response_option
      on user_data_type.id = user_data_type_response_option.user_data_type_id
   join response_option
      on user_data_type_response_option.response_option_id = response_option.id
   where user_data_type.id = 1 and user_data_type.language_id = 2 and response_option.language_id = 2
…这给了我以下结果,这就是我认为呈现表单页面所需的结果

id |用户|数据|类型|响应|选项| id |响应|选项|名称 1封电子邮件1请不要发电子邮件 1封电子邮件2封有趣的电子邮件 1电子邮件3所有电子邮件请检查错误

无法确定关系“UserDataType.response\u option\u names”的关系方向-父级或子级映射表中都不存在外键列

正在告诉您从ResponseOption到UserDataType的外键关系是预期的,但未找到

当您在UserDataType表上定义列response_option_names=db.relationshipResponseOption,…时,应使用此外键。在这种多对多方案中,不使用外键是正确的,因为您将使用关联表UserDataTypeResponseOption关联UserDataType和ResponseOption

根据文档,您可以通过更改

response\u option\u names=db.relationshipResponseOption,primaryjoin=UserDataType.id==UserDataTypeResponseOption.user\u data\u type\u id,viewonly=True 到

associations=db.relationshipUserDataTypeResponseOption,primaryjoin=UserDataType.id==UserDataTypeResponseOption.user\U data\U type\U id,viewonly=True 您还需要将UserDataTypeResponseOption中的新关系添加到ResponseOption,如下所示:

response_option=db.relationshipResponseOption 现在回答你的问题

user\u data\u type\u details=UserDataType.query.filterUserDataType.id.in\u user\u data\u types\ 筛选器_bylanguage_id=language_pref\ joinUserDataTypeResponseOption,UserDataType.id==UserDataTypeResponseOption.user\u数据类型\u id\ joinResponseOption,UserDataTypeResponseOption.response\u选项\u id==ResponseOption.id\ 筛选器_bylanguage_id=language_pref\ 可以正常工作,但请注意,您不能从每个UserDataType元素直接访问ResponseOptions。您需要通过UserDataTypeResponseOptions:

对于用户\数据\类型\详细信息中的udt: 打印“用户数据类型:”,自定义项 对于udt.associations中的assoc: 打印“响应选项:”,assoc.response\u option.response\u option\u name 输出:

user data type:  <UserDataType 1, 1>
response option:  No e-mails please
response option:  Only fun e-mails
response option:  All e-mails please
user data type:  <UserDataType 1, 1>
response option:  No e-mails please
response option:  Only fun e-mails
response option:  All e-mails please
现在你可以有多种语言

从UserDataType对象直接访问ResponseOptions。 SQLAlchemy提供了一种使用关联代理扩展直接访问响应选项的方法:

此扩展允许配置属性,这些属性将通过单个访问访问两个“跃点”,一个“跃点”访问关联对象,另一个“跃点”访问目标属性

但这有一个警告:

警告:关联对象模式不使用将关联表映射为“辅助”的单独关系来协调更改

由于不修改任何实体,因此可以安全地使用扩展,如下所示:

类UserDataTypedb.Model: __tablename\uuuu='user\u data\u type' id=db.Columndb.Integer,主键=True,null=False 语言\u id=db.Columndb.Integer,主键=True,可空=False 用户\数据\类型\名称=db.Columndb.Text response\u options=db.relationshipsresponseoption,secondary=user\u data\u type\u response\u option,viewonly=True添加的secondary 类ResponseOptiondb.Model: __tablename\uuuu='response\u option' id=db.Columndb.Integer,主键=True,null=False 语言\u id=db.Columndb.Integer,主键=True,可空=False 响应\选项\名称=db.Columndb.Text 类UserDataTypeResponseOptiondb.Model: __tablename\uuuu='user\u data\u type\u response\u option' 用户\u数据\u类型\u id=db.Columndb.Integer,主键\u=True 用户\u数据\u类型\u语言\u id=db.Columndb.Integer,主键\u=True response\u option\u id=db.Columndb.Integer,primary\u key=True response\u option\u language\u id=db.Columndb.Integer,primary\u key=True response\u option=db.relationshipResponseOption,backref=parent\u关联添加了backref __表_args_uu=ForeignKeyConstraint t[用户数据类型id,用户数据类型语言id], [UserDataType.id,UserDataType.language\u id], ForeignKeyConstraint[response\u option\u id,response\u option\u language\u id], [ResponseOption.id,ResponseOption.language\u id], {} 对于用户\数据\类型\详细信息中的udt: 打印“用户数据类型:”,自定义项 对于udt.response_选项中的ro: 打印“响应选项:”,ro.response\u选项\u名称 输出:

user data type:  <UserDataType 1, 1>
response option:  No e-mails please
response option:  Only fun e-mails
response option:  All e-mails please
user data type:  <UserDataType 1, 1>
response option:  No e-mails please
response option:  Only fun e-mails
response option:  All e-mails please
pref_edit.py

预编辑 {用户\数据\类型\详细信息%中的用户\数据\类型\详细信息%} {{user\u data\u type\u detail.user\u data\u type\u name} {用户\数据\类型\详细信息中ro的百分比。响应\选项%} {{ro.response_option_name}} {%endfor%} {%endfor%} 其他可能的解决办法
如果您愿意更改数据库设计,将language_id从UserDataType和ResponseOption表移到新表中,还有其他解决方案。查看有趣的文章,了解不同的方法

在尝试文章中建议的多语言支持方法4时,我在筛选出登录用户设置为首选语言的语言(也存储在数据库中)时遇到问题。我仍然得到所有语言中的所有UserDataType和ResponseOptions,所以我缺少一些基本的东西

数据库类定义现在是:

class UserDataType(db.Model):
    __tablename__ = 'user_data_type'
    id = db.Column(db.Integer, primary_key=True, nullable=False)
    user_data_type_name = db.Column(db.Text) #but not intending to use
    user_data_type_description = db.Column(db.Text) #but not intending to use
    udt_phrases = db.relationship('UdtTranslation', viewonly=True)
    response_options = db.relationship('ResponseOption', secondary="user_data_type_response_option", viewonly=True)

class UdtTranslation(db.Model):
    __tablename__ = 'udt_translation'
    udt_trans_id = db.Column(db.Integer, db.ForeignKey('user_data_type.id'), primary_key=True, nullable=False)
    udt_language_id = db.Column(db.Integer, db.ForeignKey('language.id'), primary_key=True, nullable=False)
    udt_name = db.Column(db.Text) #the actual name in all languages
    udt_description = db.Column(db.Text) #the actual description in all languages

class ResponseOption(db.Model):
    __tablename__ = 'response_option'
    id = db.Column(db.Integer, primary_key=True, nullable=False)
    response_option_name = db.Column(db.Text) #but not intending to use
    response_option_description = db.Column(db.Text) #but not intending to use
    ro_phrases = db.relationship('RoTranslation', viewonly=True)
    ro_languages = db.relationship('Language', secondary="ro_translation", viewonly=True)

class RoTranslation(db.Model):
    __tablename__ = 'ro_translation'
    ro_trans_id = db.Column(db.Integer, db.ForeignKey('response_option.id'), primary_key=True, nullable=False)
    ro_language_id = db.Column(db.Integer, db.ForeignKey('language.id'), primary_key=True, nullable=False)
    ro_name = db.Column(db.Text)  #the actual name in all languages
    ro_description = db.Column(db.Text) #the actual description in all languages

class Language(db.Model):
    __tablename__ = 'language'
    id = db.Column(db.Integer, primary_key=True, nullable=False, unique=True)
    language_name = db.Column(db.Text)

class UserDataTypeResponseOption(db.Model):
    __tablename__ = 'user_data_type_response_option'
    id = db.Column(db.Integer, primary_key=True, nullable=False, unique=True)
    user_data_type_id = db.Column(db.Integer, db.ForeignKey('user_data_type.id'))
    response_option_id = db.Column(db.Integer, db.ForeignKey('response_option.id'))
    response_option = db.relationship("ResponseOption", backref="parent_associations")
我尝试的问题是:

user_data_type_details = UserDataType.query.filter(UserDataType.id.in_(user_data_types)). \
    join(UserDataTypeResponseOption, UserDataType.id == UserDataTypeResponseOption.user_data_type_id). \
    join(UdtTranslation, UdtTranslation.udt_trans_id == UserDataType.id).filter_by(udt_language_id = language_pref). \
    join(ResponseOption, ResponseOption.id == UserDataTypeResponseOption.response_option_id). \
    join(RoTranslation, RoTranslation.ro_trans_id == ResponseOption.id).filter_by(ro_language_id = language_pref).all()
通过下面的打印例程,我得到了所有语言的UserDataType,对于每个UserDataType实例,我得到了所有语言的ResponseOptions。很明显,我过滤语言的方式不起作用。想法

for user_data_type_detail in user_data_type_details:
   for udt_phrase in user_data_type_detail.udt_phrases:
       print(udt_phrase.udt_name)
        for ro in user_data_type_detail.response_options:
            for ro_phrase in ro.ro_phrases:
                print(ro_phrase.ro_name)

谢谢,非常有用和有见地的回答。是的,在UserDataType和ResponseOption表之外,需要以不同的方式处理语言。我正在学习你建议的教程中的方法4,但我遇到了一些困难。将添加一个后续问题。