我有一套相当复杂的sqlalchemy模型(所有模型都使用DeclarativeBase声明,通过flask-sqlalchemy的db.Model类声明)。业务逻辑如下:
我已经准备了一个示例应用来演示核心功能:
#!/usr/bin/env python
from __future__ import print_function
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
import sqlalchemy_defaults
from flask.ext.script import Manager, Server
app = Flask(__name__)
app.config.update(
DEBUG=True,
SQLALCHEMY_DATABASE_URI=(
'postgresql://sqlareltest:sqlareltest' +
'@localhost:5432/sqlareltest'),
)
db = SQLAlchemy(app)
db.Column = sqlalchemy_defaults.Column
sqlalchemy_defaults.make_lazy_configured(db.mapper)
class Task(db.Model):
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255), default='')
def __repr__(self):
return self.title
class Submission(db.Model):
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(255), default='')
def __repr__(self):
return self.title
class TaskQuestion(db.Model):
id = db.Column(db.Integer(), primary_key=True)
task_id = db.Column(db.Integer, db.ForeignKey('task.id'))
title = db.Column(db.String(255), default='')
weight = db.Column(db.Integer(), default=0)
task = db.relationship(Task, backref=db.backref(
"task_questions",
order_by=weight,
lazy='dynamic'))
def __repr__(self):
return self.title
class QuestionOption(db.Model):
id = db.Column(db.Integer(), primary_key=True)
task_question_id = db.Column(db.Integer,
db.ForeignKey('task_question.id'))
title = db.Column(db.String(255), default='')
weight = db.Column(db.Integer(), default=0)
task_question = db.relationship(TaskQuestion, backref=db.backref(
"question_options",
order_by=weight,
lazy='dynamic'))
def __repr__(self):
return self.title
class TaskSubmission(db.Model):
id = db.Column(db.Integer(), primary_key=True)
task_id = db.Column(db.Integer, db.ForeignKey('task.id'))
submission_id = db.Column(db.Integer,
db.ForeignKey('submission.id'))
task = db.relationship(Task, backref=db.backref(
"task_submissions",
lazy='dynamic'))
submission = db.relationship(Submission, backref=db.backref(
"task_submissions",
lazy='dynamic'))
def __repr__(self):
return '%s: %s' % (self.task, self.submission)
class TaskQuestionSubmission(db.Model):
id = db.Column(db.Integer(), primary_key=True)
task_submission_id = db.Column(db.Integer,
db.ForeignKey('task_submission.id'))
task_question_id = db.Column(db.Integer,
db.ForeignKey('task_question.id'))
question_option_id = db.Column(db.Integer,
db.ForeignKey('question_option.id'))
task_submission = db.relationship(
TaskSubmission,
secondary=TaskQuestion.__table__,
secondaryjoin=(
"TaskQuestionSubmission.task_question_id==TaskQuestion.id"),
primaryjoin=(
"TaskQuestionSubmission.task_submission_id==" +
"TaskSubmission.id"),
backref=db.backref(
"task_question_submissions",
order_by=TaskQuestion.weight,
lazy='dynamic'))
task_question = db.relationship(TaskQuestion, backref=db.backref(
"task_question_submissions",
lazy='dynamic'))
question_option = db.relationship(
QuestionOption,
backref=db.backref(
"task_question_submissions",
lazy='dynamic'))
def __repr__(self):
return '%s: %s: %s' % (self.task_submission,
self.task_question,
self.question_option)
manager = Manager(app)
@manager.command
def createdb():
db.create_all()
@manager.command
def createtask():
t = Task(title='Tell us about yourself')
db.session.add(t)
tq = TaskQuestion(title='What is your favourite colour?', weight=1)
t.task_questions.append(tq)
db.session.add(tq)
qo = QuestionOption(title='Blue', weight=1)
tq.question_options.append(qo)
db.session.add(qo)
qo = QuestionOption(title='Red', weight=0)
tq.question_options.append(qo)
db.session.add(qo)
qo = QuestionOption(title='Green', weight=2)
tq.question_options.append(qo)
db.session.add(qo)
tq = TaskQuestion(title='What is your favourite fruit?', weight=0)
t.task_questions.append(tq)
db.session.add(tq)
qo = QuestionOption(title='Apple', weight=2)
tq.question_options.append(qo)
db.session.add(qo)
qo = QuestionOption(title='Peach', weight=1)
tq.question_options.append(qo)
db.session.add(qo)
qo = QuestionOption(title='Grape', weight=0)
tq.question_options.append(qo)
db.session.add(qo)
db.session.commit()
@manager.command
def createsubmission():
t = Task.query.first()
s = Submission(title='Info about Johnny')
db.session.add(s)
ts = TaskSubmission(task=t, submission=s)
db.session.add(ts)
tq = t.task_questions[1]
qo = QuestionOption.query.filter_by(task_question=tq).first()
tqs = TaskQuestionSubmission(task_submission=ts,
task_question=tq,
question_option=qo)
db.session.add(tqs)
tq = t.task_questions[0]
qo = QuestionOption.query.filter_by(task_question=tq).first()
tqs = TaskQuestionSubmission(task_submission=ts,
task_question=tq,
question_option=qo)
db.session.add(tqs)
db.session.commit()
@manager.command
def showoldsubmission():
ts = TaskSubmission.query.first()
for tqs in ts.task_question_submissions:
print(tqs)
@manager.command
def shownewsubmission():
t = Task.query.first()
s = Submission(title='Info about Sarah')
ts = TaskSubmission(task=t, submission=s)
tq = t.task_questions[1]
qo = QuestionOption.query.filter_by(task_question=tq).all()[1]
tqs = TaskQuestionSubmission(task_submission=ts,
task_question=tq,
question_option=qo)
tq = t.task_questions[0]
qo = QuestionOption.query.filter_by(task_question=tq).all()[1]
tqs = TaskQuestionSubmission(task_submission=ts,
task_question=tq,
question_option=qo)
for tqs in ts.task_question_submissions:
print(tqs)
if __name__ == "__main__":
manager.run()
我希望TaskSubmission.task_question_submissions
按TaskQuestion.weight
订购,但这不起作用,所以我在TaskQuestionSubmission.task_submission
中注释了相关的行:
task_submission = db.relationship(
TaskSubmission,
# secondary=TaskQuestion.__table__,
# secondaryjoin=(
# "TaskQuestionSubmission.task_question_id==TaskQuestion.id"),
# primaryjoin=(
# "TaskQuestionSubmission.task_submission_id==" +
# "TaskSubmission.id"),
backref=db.backref(
"task_question_submissions",
# order_by=TaskQuestion.weight,
lazy='dynamic'))
我使用以下命令设置了此示例应用程序:
./app.py createdb
./app.py createtask
./app.py createsubmission
然后我运行调试命令来查看提交的内容。
./app.py showoldsubmission
输出:
Tell us about yourself: Info about Johnny: What is your favourite colour?: Blue
Tell us about yourself: Info about Johnny: What is your favourite fruit?: Apple
和
./app.py shownewsubmission
输出:
Tell us about yourself: Info about Sarah: What is your favourite colour?: Red
Tell us about yourself: Info about Sarah: What is your favourite fruit?: Peach
这些都是错误的,因为这个问题'你最喜欢的水果是什么?'重量低于'你最喜欢的颜色是什么?'。它显示了你最喜欢的颜色是什么?'首先,因为我先将TaskQuestion
保存到数据库中。
如果我取消对secondary
,secondaryjoin
,primaryjoin
和order_by
行的评论,请再次运行调试命令,然后showoldsubmission
完美运行:
./app.py showoldsubmission
输出:
[Tell us about yourself: Info about Johnny]: What is your favourite fruit?: Apple
[Tell us about yourself: Info about Johnny]: What is your favourite colour?: Blue
可是:
./app.py shownewsubmission
输出:
Traceback (most recent call last):
File "./app.py", line 231, in <module>
manager.run()
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/__init__.py", line 412, in run
result = self.handle(sys.argv[0], sys.argv[1:])
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/__init__.py", line 383, in handle
res = handle(*args, **config)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/commands.py", line 216, in __call__
return self.run(*args, **kwargs)
File "./app.py", line 217, in shownewsubmission
question_option=qo)
File "<string>", line 4, in __init__
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/state.py", line 260, in _initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 526, in _declarative_constructor
setattr(self, k, kwargs[k])
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 226, in __set__
instance_dict(instance), value, None)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 1009, in set
lambda adapter, i: adapter.adapt_like_to_iterable(i))
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 1025, in _set_iterable
new_values = list(adapter(new_collection, iterable))
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 1009, in <lambda>
lambda adapter, i: adapter.adapt_like_to_iterable(i))
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/collections.py", line 646, in adapt_like_to_iterable
given, wanted))
TypeError: <flask_script.commands.Command object at 0x7f4c52967190>: Incompatible collection type: TaskSubmission is not list-like
我一直在绞尽脑汁试图解决这个问题 - 已经找到了大量的sqlalchemy文档,论坛帖子等,但无法弄清楚。
我不是sqlalchemy的专家,关系&#39; /&#39; backref&#39; /&#39; primaryjoin&#39; /&#39; secondaryjoin&#39;东西有点过头了。任何人都能指出我做错了什么?
更新:我还注意到,在取消评论问题行后,TaskQuestionSubmission.task_submission
会输出一个列表而不是一个项目。所以,我尝试更改定义新TaskQuestionSubmission
(在shownewsubmission
函数中)的行,如下所示:
tqs = TaskQuestionSubmission(task_submission=[ts],
task_question=tq,
question_option=qo)
(请注意更改:之前为task_submission=ts
)。
但是,在这样做之后,运行:
./app.py shownewsubmission
给出了不同的错误:
Traceback (most recent call last):
File "./app.py", line 235, in <module>
manager.run()
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/__init__.py", line 412, in run
result = self.handle(sys.argv[0], sys.argv[1:])
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/__init__.py", line 383, in handle
res = handle(*args, **config)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/flask_script/commands.py", line 216, in __call__
return self.run(*args, **kwargs)
File "./app.py", line 223, in shownewsubmission
tq = t.task_questions[0]
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/dynamic.py", line 254, in __getitem__
sess = self.session
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/dynamic.py", line 237, in session
sess.flush()
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1919, in flush
self._flush(objects)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 2037, in _flush
transaction.rollback(_capture_exception=True)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 2001, in _flush
flush_context.execute()
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 372, in execute
rec.execute(self)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 481, in execute
self.dependency_processor.process_saves(uow, states)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 1051, in process_saves
False, uowcommit, "add"):
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/dependency.py", line 1156, in _synchronize
self.prop.synchronize_pairs)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/sync.py", line 84, in populate_dict
_raise_col_to_prop(False, source_mapper, l, None, r)
File "/wsgi/sqla-rel-test/env/local/lib/python2.7/site-packages/sqlalchemy/orm/sync.py", line 123, in _raise_col_to_prop
(source_column, source_mapper, dest_column))
sqlalchemy.orm.exc.UnmappedColumnError: Can't execute sync rule for source column 'task_question.'; mapper 'Mapper|TaskSubmission|task_submission' does not map this column. Try using an explicit `foreign_keys` collection which does not include destination column 'task_question_submission.' (or use a viewonly=True relation).
(我觉得更令人困惑)。所以,仍然没有更接近解决这个问题。