Djangoのジェネリックビューで検索結果を表示してみる

最近社内で使うアプリをDjangoで作ってみてるわけなんだけど、Djangoすごすぎ。


C#とかJavaで作ったら結構工数かかるよなー、っていう管理系画面がコマンド一発で作れる。
しかもWebアプリでありがちな画面のための仕組みが用意されてて、ページングまでしてくれる。


だからやってることといえば、これくらいしかない。

  • モデルを考える。
  • urlを考える。
  • HTMLテンプレート(CSSとかも)を書く。
  • データ表示用のクエリを考える。
  • ジェネリックビューで実現できない場合はビューを書く。

PythonのレベルをあげるためにDjangoでやってみてるのに、Pythonコードをほとんど書いていない・・・


そんな素敵なDjangoなんだけど、ちょっとした検索結果を表示したい時にジェネリックビューが使えない。
(もしかしたらあるのかもしれないけど、見つけられなかった。)
それを何とかしてみようと、いろいろやってたら動いたのでメモしておく。


メインのコードはこんな感じ。
list_detail.object_list の代わりにこれを指すようにして、patternsの第3引数にfiltersとqueryをセットした辞書オブジェクトを渡してやればいい。複数条件の場合はANDで結合する。

filters
{キー=検索キー(URLからキャプチャする値の名前)、値=検索条件}の辞書オブジェクト
query
クエリセットを取得する関数
def lookup(*args, **kwargs):
    filters = kwargs.pop('filters')
    query = kwargs.pop('query')

    qs = []
    for key in filters:
        id = kwargs.pop(key, None)
        if not id:
            continue
        qs.append(Q(**{ filters[key] : id }))
    q = reduce(lambda x, y:x & y, qs)

    kwargs['queryset'] = query(q)
    return object_list(*args, **kwargs)


たとえばこんなモデルがあったとして、Book一覧を表示するだけなら list_detail.object_list で構わない。

# -*- encoding: utf-8 -*-
from django.db import models

class Author(models.Model):
    name = models.CharField(maxlength=200)
    
class Book(models.Model):
    name = models.CharField(maxlength=200)
    authors = models.ManyToManyField(Author)


でもAuthorで絞り込みたい、ってのはよくあるはず。
で、こういう風にURLを書いてみても、

urlpatterns = patterns('',
    (r'^book/author/(?P<author_id>\d+)/$',
        'django.views.generic.list_detail.object_list',
        {'queryset':Book.objects.filter(**{'authors__id' : 'author_id'})}),
)
object_list() got an unexpected keyword argument 'author_id'

というエラーが発生して意図した結果は得られない。
コンテキストに余計なキーがあるとエラーが発生するようだ。


そこで上記のラッパーを指すようにすると、意図した結果を得られる。

urlpatterns = patterns('',
    (r'^book/$', 'django.views.generic.list_detail.object_list', {'queryset':Book.objects.all()}),
    (r'^book/author/(?P<author_id>\d+)/$', 'djangotest.book.urls.lookup',
        { 'filters' : { 'author_id' : 'authors__id' }, 'query' : lambda x: Book.objects.filter(x) })
)