[TurboGears] @paginate()

注意

この記事は、1.0/PaginateDecorator - TurboGears Documentationの日本語訳です。

状態 公式

@paginateデコレータは、クエリの結果を透過的に分割し、より簡単にあるいはより使い易く表示するようにします。paginateデコレータが定義されているコントローラのメソッドのテンプレートは、クエリの結果と、ページングに関する変数を受け取ります。このとき、コントローラは、SQLObjectのクエリを定義すること以外、なにもする必要はありません。paginateパッケージには、DataGridを基にしたウィジェット (PaginateDataGrid) があり、これは元のDataGridウィジェットをページングするように拡張したものです。

基本的な使いかた

以下の例(と、このページのすべての例)は、Personという名前で、nameとageというカラムを持つデータベースのテーブルを仮定しています。これらの例は、このページに添付されたファイルの中にあるデモから抜粋したものです。

DataGridを基にしないページングするコントローラの実装は、以下のようになります:

@expose(template=".templates.paginate1")
@paginate('persons')
def paginate1(self):
    persons = Person.select()
    return dict(persons=persons)

SQLObjectは実際には、結果が必要になるまで、クエリの実行を延期します。上のコントローラのコードでは結果 (persons) が定義されていますが、実際にはこの結果は使用されていません。これにより、paginateデコレータは、どの列を取り出すか制御することができます。ここで、@paginate()デコレータは、コントローラが返した辞書を調べて、personsという結果を発見します。デコレータは、personsオブジェクトに手を加え、ある限られた結果しか返らないようにし、いくつかの変数を設定し、それらすべてをテンプレートに渡します。

以下は、このデコレータのために書かれたテンプレートです。

<span py:for="page in tg.paginate.pages">
    <a py:if="page != tg.paginate.current_page"
        href="${tg.paginate.get_href(page)}">${page}</a>
    <b py:if="page == tg.paginate.current_page">${page}</b>
</span>
<table border="1">
    <tr>
        <td>
            Name
        </td>
        <td>
            Age
        </td>
    </tr>
    <tr py:for="person in persons">
        <td>
            ${person.name}
        </td>
        <td>
            ${person.age}
        </td>
    </tr>
</table>

このコードではtg.paginateというのが使われていますが、これこそがまさにあなたがクエリの結果といっしょに使いたいと思うようなものです。tg.paginateオブジェクトはpaginateデコレータによって自動的に生成され、このページに適したデータが収められています。このテンプレートで作られている数字のリンクをクリックすると、クエリの結果の適切なページに遷移します。ページングするために、あなたのページのURLに加えられた変更点を見てみましょう。以下は、paginate1からの抜粋です。

http://localhost:8080/paginate1?tg_paginate_limit=10&tg_paginate_no=2&tg_paginate_order=

  • tg_paginate_limitは、1ページにいくつの結果を表示するかをデコレータに指示します。デフォルトは10個ですが、もしあなたが上書きを許可していた場合、変更することができます(デフォルトでは、上書きできません)。
  • tg_paginate_noは、現在のページ番号です。私たちは現在、全結果の2ページ目を見ています。
  • tg_paginate_orderは、クエリの並び順をデコレータに指定するのに使用されます。指定されていれば、他のカラムで並び替えることができます。

tg.paginateについて詳しく

@paginate()デコレータについてもっと話す前に、tg.paginateオブジェクトが何をするかを見ておいた方がよいでしょう。これは、テンプレートにページの情報を渡すのに使用されます。これは役に立ついくつかの値と、gre_hrefメソッドを持っています。get_hrefメソッドは、ページングのためのリンクを作成します。以下が、メンバ変数です:

  • tg.paginate.pages - 各ページ番号のリスト。
  • tg.paginate.current_page - 現在のページ番号。
  • tg.paginate.href_next - 次のページに遷移するためのURL。
  • tg.paginate.href_prev - 前のページに遷移するためのURL。
  • tg.paginate.href_last - 最後のページに遷移するためのURL。
  • tg.paginate.href_first - 最初のページに遷移するためのURL。
  • tg.paginate.page_count - 結果の総ページ数。
  • tg.paginate.limit - 1ページ当たりの表示件数。
  • tg.paginate.order - 指定されていたら、クエリの結果の並べ替えに使用したデータベースのカラム。
  • tg.paginate.reversed - ブール値で、tg.paginate.orderで指定されたまま表示するか、逆順にするか。
  • tg.paginate.input_values - ページングシステムが、主にリンクを生成するために内部で使用します。おそらくこれを変更する必要はないでしょう。

tg.paginate.get_href()

tg.paginate.get_hrefメソッドは、ページ番号が指定されているURLを生成します。このメソッドはtg.paginate.input_valuesを使用し、あなたが新しい値を指定しない限り、自動的に結果を(逆順にも)並び替えるようにします。

  • page(必須) - リンク先のページ番号。
  • order(任意。デフォルトはNone) - 結果を並び替えるのに使用されるカラム。
  • reverse_order(任意。デフォルトはFalse) - もしこれがtrueだったら、結果は逆順に並び替えられます。

@paginate()を詳しく

@paginateデコレータは、修飾しているコントローラの関数の戻り値を調べ、変更するように指示されているSelectResultかリストを変更し、ページングに関する変数 (tg.paginate) を追加し、変更されて新しくなった関数の結果をテンプレートに渡します。@paginateを使うのであれば、このデコレータはリストかSQLObjectのSelectResultsしか受け付けないことを、必ず覚えておいて下さい。以下は、paginateデコレータに与える引数です:

  • var_name(必須) - @paginateが変更する、コントローラの戻り値の辞書に含まれる変数の名前。
  • default_order(任意。デフォルトは空文字列) - 結果を並び替えるのに使用されるカラムの名前。ページング機能の実装の都合により、default_orderを指定するのは、コントローラ内で指定された並び順を上書きします。
  • limit(任意。デフォルトは10) - 1ページ当たりの件数。
  • allow_limit_override(任意。デフォルトはFalse) - もしこれがTrueであれば、URLでデフォルト値以外のlimitの値を指定できます。
  • max_pages(任意。デフォルトは5で、最小値は3) - これは、tg.paginate.pagesを生成するのに使用されます。もしページ数がmax_pagesよりも多かったら、tg.paginate.pagesには、現在のページから1/2 max_pagesの範囲内にあるページ番号しか格納されません。例えば、あなたが15ページ分の結果を持っていて、7ページ目を参照しており、max_pagesが5に設定されていたら、tg.paginate.pagesは[5, 6, 7, 8, 9]となります。

tg.paginateにある変数のいくつかと、URL中のいくつかのパラメータは、@paginateデコレータの動作に影響を与えます。以下に、URLのパラメータとその影響を記します:

  • tg_paginate_reversed - もしこの値がTrueに設定されていたら、デコレータは、default_order引数で与えられているカラムか、tg.paginate.orderで指定されているカラムで並び替えた後、結果の順序を逆にします。これは、default_orderが指定されていなければ、なんの効果も持ちません。
  • tg_paginate_order - もしdefault_orderが指定されていたら、このパラメータで指定されているカラムで、default_orderの設定が上書きされます。
  • tg_paginate_limit - もしallow_limit_overrideがTrueであったら、この値が@paginateに与えられたlimit引数の代わりに使用されます。

PaginateDataGrid

paginateデコレータは便利です。しかし、これがもっともよく使用されるのは、テーブルに表示するデータの件数を制限するときでしょう。我々には既に、これをこなしてくれるウィジェットがあります: DataGridです。ページングシステムは、このウィジェットをページ化する拡張を提供しています: PagingDataGridです。PagingDataGridは、DataGridの機能を変更せず、単にページングの機能をテンプレートに付け加えただけです。PaginateDataGridを普通のDataGridの代わりに使う場合、あなたのテンプレートやウィジェットに渡す引数に、手を加える必要はありません。単に、ページングの機能をあなたのコントローラに追加すればよいのです。

SQL以外のデータを扱う

paginateは、辞書のリスト(やタプル)といったSQLObject以外のデータも扱えます:

@turbogears.expose(template='.templates.paginate5')
@turbogears.paginate('persons')
def paginate5(self):
    return dict(persons=self.persons(), list=data_grid)

def persons(self):
    data = []
    for i in range(100):
        data.append(dict(id=i, name='name%d'%i, age=100-i))
    return data

(Kidを使う場合、)person.nameとperson['name']は違うので、テンプレートを変更するのを忘れないで下さい。

以下は、paginate5.kidテンプレートの一部です。

<tr py:for="person in persons">
    <td py:content="person['name']">
        person.name
    </td>
    <td py:content="person['age']">
        person.age
    </td>
</tr>

このコードは、添付ファイル2にあります。

データグリッドの内部にリンクを加える

データグリッドの内部にリンクを加えるには、TreeElementで文字列のデータを置き換えて下さい。

以下は、SQL以外のデータでの例です。

from elementtree import ElementTree as ET

# generate the link inside the datagrid
class MakeLink:
    def __init__(self, baseurl, id, title, action):
        self.baseurl=baseurl
        self.id=id
        self.title=title
        self.action=action

    def __call__(self, obj):
        url=controllers.url(self.baseurl, dict(id=obj[self.id],action=self.action))
        link = ET.Element('a', href=url, style='text-decoration: underline' )
        link.text = obj[self.title]
        return link

# to get this kind of link :  http://centos41-srv:8080/person?action=edit&id=6
mylink=MakeLink('person', 'id', 'name', 'edit')

# just to get a element from a dictionary
def get(fieldname):
    return lambda x: x.get(fieldname)

data_grid = PaginateDataGrid(
    fields = [
        PaginateDataGrid.Column(name='name', getter=mylink, title='Name', options=dict(sortable=True)),
        PaginateDataGrid.Column(name='age', getter=get('age'), title='Age', options=dict(sortable=True, reverse_order=True)),
    ])

the controller is very simple and use some the code from above:

# this is like paginate2 except it has sorting support
@turbogears.expose(template='.templates.paginate2')
@turbogears.paginate('persons', default_order='name')
def paginate4(self):
    return dict(persons=self.persons(), list=data_grid)

このコードは、添付ファイル3にあります。

FAQ

私のコントローラが文字列を返すとき、なぜページングが機能しないのですか?

実装の都合により、ページングは辞書を返すコントローラのメソッドのみに対応しています。

SQLObjectの結果以外もページ化できますか?

現在(2006年9月14日)、paginateをSQLAlchemyでも動くようにしようという議論が行われています。この議論は、http://trac.turbogears.org/turbogears/ticket/1115のページで見ることができます。デフォルトではページングは、明らかにカラムで並べ変えを行うことができないリストで動作します。

URLから渡されるパラメータの名前を、もっと格好いいものに変更できますか?

現在の実装では、できません。