読者です 読者をやめる 読者になる 読者になる

intを含むURL

Django Werkzeug

intと言っても、主に日付に使う整数。

Djangoで、

from django.conf.urls.defaults import *

pattern = patterns(
  'myapp.views',
  url(r'^entries/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/', 'blog_archive'),
)

のようなURLマッピングを書くと、下記のようなURLはすべて同一に扱われてしまう。

/entries/2009/1/1/
/entries/2009/01/01/
/entries/2009/000001/000001/

これは気分悪い。ということで、

from django.conf.urls.defaults import *

pattern = patterns(
  'myapp.views',
  url(r'^entries/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/', 'blog_archive'),
)

のように書き直すと、今度は、

from django.core.urlresolvers import reverse
url = reverse("blog_argchive", args=[2009, 1, 1])

というURL解決ができなくなってしまう。

from django.core.urlresolvers import reverse
url = reverse("blog_argchive", args=['%04d' % 2009, '%02d' % 1, '%02d' % 1])

これならば通るのだが、これはキモい。

Werkzeugならば、

>>> from werkzueg.routing import Map, Rule
>>> m = Map([
... Rule('/entries/<int(fixed_digits=4):year>/<int(fixed_digits=2):month>/<int(fixed_digits=2):day>/',
... endpoint='blog_archive')
... ])
>>> a = m.bind("example.com")
>>> a.match("/entries/2009/01/01")
('blog_archive', {'month': 1, 'day': 1, 'year': 2009})

逆側のURL解決も期待した通りに行ってくれる。

>>> a.build('blog_archive', dict(year=2009, month=1, day=1))
'/entries/2009/01/01/'

ただ、Werkzeugでは"/entires/yyyymmdd"のようなURLを構成する際に、

from werkzueg.routing import Map, Rule
m = Map([
  Rule('/entries/<int(fixed_digits=4):year><int(fixed_digits=2):month><int(fixed_digits=2):day>',
       endpoint='blog_archive')
])

というマッピング定義では「パス→マッチするルール」の方向のマッピングが上手くいかなかったのが気になった。

結局のところ、年毎・月毎・日毎に列挙できるリソースならば、

/entries/2009/
/entries/2009/01/
/entries/2009/01/01/

のように構成し、/entires/2009/1/1/のようなURLに対してはHTTP 301を返すのが適切だろうか。「存在しない日付に対してHTTP404を返し、期待するフォーマットではない日付に対しては301を返す」ようなデコレータというものが書けそうだし、実際にありそうだけれども、寡聞にして知らぬ。