基于flask_sqlachemy的分页

基于flask_sqlachemy的分页

flask 项目中使用分页paginate
报错 AttributeError:Query object has no attribute 'paignate'
创建model的时候使用的是sqlachemy,而不是flask_sqlachemy,而分页paginate方法只在flask_sqlchemy中提供,同样get_or_404()和first_or_404()也只在flask_sqlachemy中才有,所以要想使用,必须修改model的创建方式,基于flask_sqlalchemy才可以。

https://stackoverflow.com/questions/18409645/sqlalchemy-does-not-work-with-pagination
属性:
items : 表示获得的查询结果
pages : 表示一共有多少页
page :获得当前页码数
total :数据总条数
has_prev: 是否有上一页
has_next: 是否有下一页
prev_num:上一页页码
next_num:下一页页码
iter_page():当前页的页码列表

关联查询


data = db.session.query(Alipay_B_Order.spec_name, Alipay_B_Bus.app_id).\
    join(Alipay_B_Bus,Alipay_B_Order.bus_id == Alipay_B_Bus.bus_id).\
    filter(Alipay_B_Order.order_id == order_id).first()

query 中使用了 Alipay_B_Order和 Alipay_B_Bus,后面再 join 一次 Alipay_B_Bus.那么 SQLAlchemy 如何选择 JOIN 的时候谁先谁后呢?看看这个错误就知道了:

data = db.session.query(Alipay_B_Order.spec_name, Alipay_B_Bus.app_id).\
    join(Alipay_B_Order,Alipay_B_Order.bus_id == Alipay_B_Bus.bus_id).\
    filter(Alipay_B_Order.order_id == order_id).first()

08-17 14:09 flask.app ERROR Exception on /internal_alipay/data_background_order_information [GET]
Traceback (most recent call last):
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/mnt/home/tangsq/3/AppletServers/app/internal_alipay_applet/data_background_api.py", line 72, in data_background_order_information
    join(Alipay_B_Order,Alipay_B_Order.bus_id == Alipay_B_Bus.bus_id).\
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 1964, in join
    from_joinpoint=from_joinpoint)
  File "<string>", line 2, in _join
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/sqlalchemy/orm/base.py", line 201, in generate
    fn(self, *args[1:], **kw)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2108, in _join
    outerjoin, full, create_aliases, prop)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/sqlalchemy/orm/query.py", line 2164, in _join_left_to_right
    l_info.selectable)
sqlalchemy.exc.InvalidRequestError: Can't join table/selectable 'alipay_b_order' to itself

select * from alipay_b_order a INNER JOIN alipay_b_bus b on a.bus_id = b.bus_id where order_id ="2021043009571900008c"

因此,query 中参数的顺序很重要,query 的第一个参数就代表着第一个table是谁。故,此处 JOIN 的目标应该是 Alipay_B_Bus, 而不应该是 Alipay_B_Order。

联表分页


data = db.session.query(Alipay_B_Order).\
    join(Alipay_B_Bus,Alipay_B_Order.bus_id == Alipay_B_Bus.bus_id).\
    filter(Alipay_B_Order.order_id==order_id, Alipay_B_Bus.app_id == app_id).paginate(1, per_page=20, error_out=False)

08-17 15:17 flask.app ERROR Exception on /internal_alipay/data_background_order_information [GET]
Traceback (most recent call last):
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/root/.virtualenvs/apicall_env/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/mnt/home/tangsq/3/AppletServers/app/internal_alipay_applet/data_background_api.py", line 77, in data_background_order_information
    filter(Alipay_B_Order.order_id==order_id, Alipay_B_Bus.app_id == app_id).paginate(1, per_page=20, error_out=False)
AttributeError: 'Query' object has no attribute 'paginate'

报错的原因是db.session.query 默认返回的是 orm.Query 对象,这个对象并不包含paginate方法。
要解决这个问题:

方法一

修改 Flask-SQLAlchemy 的源码

找到 SQLAlchemy 对象的 init 定义,在其中加入 session_options['query_cls'] = BaseQuery 即可:

def __init__(self, app=None, use_native_unicode=True, session_options=None, metadata=None):

    if session_options is None:
        session_options = {}

    session_options.setdefault('scopefunc', connection_stack.__ident_func__)
    self.use_native_unicode = use_native_unicode
    self.app = app

    # 使用 BaseQuery,这样可以让使用 db.session.query 等方法创建的 Query 对象支持 BaseQuery 的方法
    session_options['query_cls'] = BaseQuery

方法二

另一种关联查询语法

在 Flask-SQLAlchemy 提供的 Model 对象中,可以使用 Model.query 这样的语法来直接得到一个查询对象,这是由于 Flask-SQLAlchemy 中存在一个 _QueryProperty 类,每次调用 Model.get时,会自动生成一个基于当前 session 的 query 对象:

data = Alipay_B_Order.query.join(Alipay_B_Bus,Alipay_B_Order.bus_id == Alipay_B_Bus.bus_id).\
    filter(Alipay_B_Order.order_id==order_id, Alipay_B_Bus.app_id == app_id).\
    paginate(1, per_page=20, error_out=False)
print({"total": data.total,
                     "pages": data.pages,
                     "next_num": data.next_num,
                     "prev_num": data.prev_num,
                     "has_next": data.has_next,
                     "has_prev": data.has_prev})
print(data.items)
for item in data.items:
    oneData = item.__dict__
    del oneData["_sa_instance_state"]
    print(item.spec_name)

参考--在 Flask-SQLAlchemy 中联表查询

class BaseQuery(orm.Query):
    def paginate(self, page=None, per_page=None, error_out=True, max_per_page=None, count=True):
        """
        当“error_out”为"True"时,符合下面判断时将引起404响应,我一般都关了这个
        error_out,把接口返回什么的控制权拿回来。
        """
        //如果"page"或"per-page"是"None",则将从请求查询
        //flask-sqlalchemy和flask app绑定的还真是挺深,连request都用上了
        if request:
            if page is None:
                try:
                    //这么写更好page = request.args.get('page', 1, type=int)
                    page = int(request.args.get('page', 1))
                except (TypeError, ValueError):
                    if error_out:
                        abort(404)

                    page = 1

            if per_page is None:
                try:
                    per_page = int(request.args.get('per_page', 20))
                except (TypeError, ValueError):
                    if error_out:
                        abort(404)

                    per_page = 20
        else: // 如果没有请求分别默认为1和20
            if page is None:
                page = 1
            if per_page is None:
                per_page = 20
        //如果指定了“max_per_page”,则“per_page”将受这个值钳制。
        if max_per_page is not None:
            per_page = min(per_page, max_per_page)

        if page < 1:
            ......
                page = 1

        if per_page < 0:
            ......
                per_page = 20
        //原来paginate就是用的limit和offset,我猜如果有重复数据,先limit后all,all的时候再去个重,数据就少了,分页也没了
        items = self.limit(per_page).offset((page - 1) * per_page).all()
        ......
        //如果"count"是"False",total就不能用了,不用的时候可以关掉,省个查询
        if not count:
            total = None
        else:
            //为什么order_by(None)?
            total = self.order_by(None).count()

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

class Pagination(object):
    def __init__(self, query, page, per_page, total, items):
        #: the unlimited query object that was used to create this
        #: pagination object.
        self.query = query
        #: the current page number (1 indexed)
        self.page = page
        #: the number of items to be displayed on a page.
        self.per_page = per_page
        #: the total number of items matching the query
        self.total = total
        #: the items for the current page
        self.items = items

flask-sqlalchemy代码没有去重,再看看sqlalchemy的代码

class Query(Generative):
    ......
    def all(self):
        """
        不翻译了,留着原话,这里给去重了
        Return the results represented by this :class:`_query.Query`
        as a list.
        This results in an execution of the underlying SQL statement.
        .. warning::  The :class:`_query.Query` object,
           when asked to return either
           a sequence or iterator that consists of full ORM-mapped entities,
           will **deduplicate entries based on primary key**.  See the FAQ for
           more details.
            .. seealso::
                :ref:`faq_query_deduplicating`
        """
        return self._iter().all()
    ......
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容