如何在Flask-SQLAlchemy中为db.session加入查询进行分页?

时间:2013-03-31 06:55:30

标签: python sqlalchemy flask flask-sqlalchemy

说,我们有以下关系:

  • 一个人可以拥有多个电子邮件地址
  • 电子邮件服务提供商可以(显然)提供多个电子邮件地址

所以,这是一对多关系。我有三个表:电子邮件,提供商和用户。电子邮件有两个外国ID供提供者和用户使用。

现在,给定一个特定的人,我想打印所有电子邮件提供商及其为此人托管的电子邮件地址(如果存在)。 (如果此人没有Gmail上的电子邮件,我仍然希望Gmail出现在结果中。我相信否则我只需要一个左内连接来解决这个问题。)

我想出了如何使用以下子查询执行此操作(遵循sqlalchemy教程):

email_subq = db.session.query(Emails).\
                filter(Emails.user_id==current_user.id).\
                subquery()

provider_and_email = db.session.query(Provider, email_subq).\
                outerjoin(email_subq, Provider.emails).\
                all()

这样可行(它返回(Provider, user_id, provider_id, email_address)的4元组,我想要的所有信息),但我后来发现这是使用Flask {{1} } class,因此Flask-SQLAlchemy提供的BaseQuery不起作用。显然pagination不是Flask-SQLAlchemy Query实例。

我尝试db.session.query(),但只返回电子邮件表格中的列,但我想要提供商信息和电子邮件。

我的问题:我如何使用Flask-SQLAlchemy做同样的事情,这样我就不必重新实现已存在的分页?


我认为此时最简单的选择是实现我自己的分页功能,但我很想知道是否还有其他正确的方法。

7 个答案:

答案 0 :(得分:8)

我不确定这是否会成为长期解决方案,并不能直接解决我对不使用Flask-SQLAlchemy的BaseQuery的担忧,而是最简单的方法来实现我想要的是重新实现分页功能。

事实上,使用原始的Flask-SQLAlchemy例程很容易做到这一点:

def paginate(query, page, per_page=20, error_out=True):
    if error_out and page < 1:
        abort(404)
    items = query.limit(per_page).offset((page - 1) * per_page).all()
    if not items and page != 1 and error_out:
        abort(404)

    # No need to count if we're on the first page and there are fewer
    # items than we expected.
    if page == 1 and len(items) < per_page:
        total = len(items)
    else:
        total = query.order_by(None).count()

    return Pagination(query, page, per_page, total, items)

从第376行附近的paginate函数修改:https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy.py

答案 1 :(得分:3)

您的问题是如何在常规SQLAlchemy查询中使用Flask-SQLAlchemy的分页。

由于Flask-SQLAlchemy的BaseQuery对象没有自己的状态,并且是从SQLAlchemy的Query派生的,并且实际上只是方法的容器,所以你可以使用这个hack:

from flask.ext.sqlalchemy import BaseQuery
def paginate(sa_query, page, per_page=20, error_out=True):
  sa_query.__class__ = BaseQuery
  # We can now use BaseQuery methods like .paginate on our SA query
  return sa_query.paginate(page, per_page, error_out)

使用:

@route(...)
def provider_and_email_view(page):
  provider_and_email = db.session.query(...) # any SQLAlchemy query
  paginated_results = paginate(provider_and_email, page)
  return render_template('...', paginated_results=paginated_results)

*编辑:

请小心这样做。它实际上只是一种避免复制/粘贴paginate函数的方法,如另一个答案所示。请注意,BaseQuery没有__init__方法。请参阅How dangerous is setting self.__class__ to something else?

* EDIT2:

如果BaseQuery有__init__,您可以使用SA查询对象构建一个,而不是攻击.__class__

答案 2 :(得分:3)

我目前正在使用这种方法:

query = BaseQuery([Provider, email_subq], db.session())

创建我自己的BaseQuerydbSqlAlchemy个实例。

更新:作为@afilbert suggests,你也可以这样做:

query = BaseQuery(provider_and_email.subquery(), db.session())

答案 3 :(得分:2)

嘿,我在这里找到了一个快速解决方法:

provider_and_email = Provider.query.with_entities(email_subq).\
            outerjoin(email_subq, Provider.emails).paginate(page, POST_PER_PAGE_LONG, False)

答案 4 :(得分:0)

我可能错了,但我认为你的问题可能是.all()。通过使用它,您将获得一个列表,而不是一个查询对象。

尝试将其关闭,然后将查询传递给分页方法(为清楚起见,我将所有子查询详细信息保留下来):

email_query = db.session.query(Emails).filter(**filters)
email_query.paginate(page, per_page)

答案 5 :(得分:0)

如何使用SQLAlchemy初始化您的应用程序?

可能你当前的SQLAlchemy连接与flask.ext.sqalchemy无关,你使用原始sqlalchemy

查看本教程并检查您的导入,它们确实来自于flask.ext.sqlalchemy

http://pythonhosted.org/Flask-SQLAlchemy/quickstart.html#a-minimal-application

答案 6 :(得分:0)

您可以尝试用结果对列表进行分页。

my_list = [my_list[i:i + per_page] for i in range(0, len(my_list), per_page)][page]