URL のクエリ要素にあえて情報をもたせる必要はない?
ページ番号などの情報を URL に持たせる時の話。 Django 的には /something/?page=7 のようにクエリ要素を使うのと /something/7/ のような URL はどちらが便利なのか考えてみた。
今の時点での結論は「URL ディスパッチャが強力なので /something/7/ でよい」かな。 Django 使いはじめたばかりなのですぐに意見が変わる可能性は大。
/somethig/7/ だけでなく /something7/, /something/page7/, /something/page/7/ など、正規表現しだいでいろいろやりようがあるのもメリット。
以下比較。
URL ディスパッチャの記述
ここだけはクエリ要素使用のほうが楽。
クエリ要素に情報を持たせる場合
URL ディスパッチャはクエリを無視するのでやることがない。以下の記述で /something/, /something/?page=7 いずれのアクセスも index ビューに結び付けられる。
from django.conf.urls.defaults import * urlpatterns = patterns('myapp.views', url(r'^something/$', 'index'), )
パスに情報を持たせる場合
正規表現で URL から値を取り出す。正規表現しだいでいろいろできるのがよい感じ。以下の記述で /something/ へのアクセスは index(request) という呼び出しに, /something/7/ は index(request, page=7) になる。
from django.conf.urls.defaults import * urlpatterns = patterns('myapp.views', url(r'^something/(?<page>\d+)/$', 'index', name='index-pagenated'), url(r'^something/$', 'index', name='index'), )
view 関数の記述
手間は大差なし。可読性は view に引数があるパス版のほうが少しだけよい気はする。でも docstring 書かないと将来わけわからなくなるのはどちらも同じ。
クエリ要素に情報を持たせる場合
値を取り出すのはビューの役目となる。request.GET から取り出す。
from django.http import Http404 def index(request): page = request.GET.get('page', 1) try: page = int(page) except ValueError: raise Http404 # 以下略
パスに情報を持たせる場合
値を取り出すのは URL ディスパッチャの役目なので view には値を受け取るための引数を用意しておく。
from django.http import Http404 def index(request, page=1): try: page = int(page) except ValueError: raise Http404 # 以下略
URL の逆引き(テンプレート版)
実際にはページ番号などの情報はコンテキストから持ってくるんだろうけれど、とりあえず "7" リテラルでハードコーディング。
パス版のほうが分かりやすいと思う。 url タグが全部面倒を見てくれるので一安心。
一方、クエリ版はやっかい。逆引きは URL ディスパッチャの設定を元に行われる。このため、ディスパッチャが関与していないクエリ部分は自動生成できない。そして自前で埋め込もうとすると非 ASCII 文字列が含まれる場合、問題が発生する。 HTML 内に UTF-8 でそのまま現れるため URL の体をなさなくなる。これを解決するためには urlencode フィルタを通す。
クエリ要素に情報を持たせる場合
<p> <a href="{% url myapp.views.index %}?page=7"> ページ7 </a> </p>
非 ASCII 文字に備えるにはこう。今回は単なる ASCII 文字の "7" なので余計なのだけれども。
<p> <a href="{% url myapp.views.index %}?page={{ "7"|urlencode }}"> ページ7 </a> </p>
パスに情報を持たせる場合
シンプルでよい感じ。
<p><a href="{% url index-pagenated page='7' %}">ページ7</a></p>
URL の逆引き(Python コード版)
やはりパス版のほうが勝っていると思う。
クエリ要素に情報を持たせる場合
from django.core.urlresolvers import reverse url = reverse('myapp.view.index') + '?page=%s' % '7'
非 ASCII 文字に備えるにはこうか?
from django.core.urlresolvers import reverse from django.utils.encoding import iri_to_uri url = iri_to_uri(reverse('myapp.view.index') + u'?page=%s' % u'7')
urllib.urlencode の Django 版である django.utils.http.urlencode を使うと、非 ASCII 文字に備えつつ、コードをスマートにできる。辞書でクエリを表現して urlencode に渡せばよい。値が非 ASCII 文字を含むユニコード文字列でも問題なし。
from django.core.urlresolvers import reverse from django.utils.http import urlencode query = urlencode(dict(page=u'7')) url = ''.join([ reverse('myapp.view.index'), '?', query])
パスに情報を持たせる場合
単純明快。
from django.core.urlresolvers import reverse url = reverse('index-pagenated', kwargs=dict(page=u'7'))
メモ、ページ分割をしたいだけならば
ページ分割を実現するためにはビューの以下略の部分を実装する必要がありそうだが、 Django には paginator が用意されている。これをつかえばよい。
さらには 汎用ビュー django.views.generic.list_detail.object_list だけでも十分だったりする。内部で paginator を用意してくれるので1ページに何件表示するか引数で指示を与えるだけでビューの作成は終わってしまう。あとはテンプレートの作成をがんばるだけ。