通过辅助表的字段过滤关系的backref

时间:2015-03-06 04:12:51

标签: python sqlalchemy relationship

我有一套相当复杂的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_submissionsTaskQuestion.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保存到数据库中。

如果我取消对secondarysecondaryjoinprimaryjoinorder_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).

(我觉得更令人困惑)。所以,仍然没有更接近解决这个问题。

0 个答案:

没有答案