GAEでPagination その2

GAEでPaginationでPaginationの方法はわかったものの、似たようなコードをあちこちに書くのは嫌なのでまとめてみた。Paginateされたアイテムを表示する場所が1箇所増えるごとにviews.pyが2文、テンプレートが1行増える。

現状の実装ではソートに使うキー(order_by引数)がdb.DateTimeProperty1つで1秒に1回以上の投稿がないことを前提にしているが、より一般的にはこれがリストを受け取るようにすることと、bookmarkの文字列へのシリアライズとデシリアライズの方法を渡せるようにする必要がある。まあ必要になった時に作る。

引数のデフォルト値は、こういうプロパティを付けている時に便利なように設定してある。これは僕がそうしてるから。

class Item(polymodel.PolyModel):
    (中略)
    created_on = db.DateTimeProperty(auto_now_add=True)

使い方はビューの中でこうやって、

def user_page(request, key):
  (中略)
  bookmark = request.args.get("bookmark")
  items, next = utils.pagination(
    Item.all().filter("user =", user), bookmark)

  return render_to_response(
    'core/user_page.html', 
    dict(
      items=items,
      next=next))

テンプレートの側では下のマクロを呼び出すだけ。(Jinja2の機能)

{{ macro.paginated_items(items, next) }}

マクロの実装はこうなっている。一般的にはこのマクロの中で呼び出している「itemを表示するマクロ」quoteは引数で渡せるようにするべきだね。まあ必要になったらそうする。

{% macro paginated_items(items, next) %}
{% for item in items %}
{{ quote(item) }}
{% endfor %}

{% if next %}
<a href="?bookmark={{ next }}">next>></a>
{% endif %}
{% endmacro %}

以下ソースコード

def pagination(query, bookmark=None, order_by="created_on", desc=True, 
               page_size=10):
    if desc:
        param_for_order_by = "-" + order_by
        param_for_filter = order_by + " <="
    else:
        param_for_order_by = order_by
        param_for_filter = order_by + " >="
    
    if bookmark:
        import datetime
        bookmark = datetime.datetime.strptime(bookmark, "%Y%m%d%H%M%S")
        items = (query.order(param_for_order_by)
                 .filter(param_for_filter, bookmark)
                 .fetch(page_size + 1))
    else:
        items = (query.order(param_for_order_by)
                 .fetch(page_size + 1))
        
    next = None
    if len(items) == page_size + 1:
        next = getattr(items[-1], order_by).strftime("%Y%m%d%H%M%S")
    
    items = items[:page_size]
    return items, next