クラスベースビューとデコレータとkwargs
クラスベースビューとデコレータを駆使するとDRYに書けていいですねーという話。
たとえば
こんなURL設計のページを実装するとする。
/shop/<shop_id>/ /shop/<shop_id>/goodsA/<goodsA_id>/ /shop/<shop_id>/goodsA/ /shop/<shop_id>/goodsA/<goodsA_id>/buy /shop/<shop_id>/goodsA/<goodsA_id>/confirm /shop/<shop_id>/goodsA/<goodsA_id>/buy_finish /shop/<shop_id>/goodsB/ /shop/<shop_id>/goodsB/<goodsB_id>/ /shop/<shop_id>/goodsB/<goodsB_id>/buy /shop/<shop_id>/goodsB/<goodsB_id>/confirm /shop/<shop_id>/goodsB/<goodsB_id>/buy_finish . . .
べた書きするとひじょうにめんどそうな感じですが、クラスベースビューを使ってshop, goodsA, goodsBの3つのアプリに分け・・・
class ShopViews(object): @property def urls(self): from django.conf.urls.defaults import url, patterns return patterns("", url("^shop/(?P<shop_id>\d+)/$", self.index, name=u"index"), url("^shop/(?P<shop_id>\d+)/goodsA/", include(goodsAView().urls), url("^shop/(?P<shop_id>\d+)/goodsB", include(goodsBView().urls), )
などして、適宜goodsAView, goodsBViewでbuy, confirmなどのビューを実装してやればビューの分割が綺麗にできてうれしさ満点です。*1
しかし
この場合goodsAのビューメソッドがどうなるかというと
class goodsA(object): @property def urls(self): from django.conf.urls.defaults import url, patterns return patterns("", url("^$", self.index, name=u"index"), url("^(?P<goodsA_id>\d+)/", self.detail, name=u"detail"), url("^(?P<goodsA_id>\d+)/buy", self.buy, name=u"buy"), 以下多数 ) def index(self, shop_id): return get_object_or_404(Shop, pk=shop_id) def detail(self, shop_id, goodsA_id): shop = get_objects_or_404(Shop, pk=shop_id) goods = get_objects_or_404(GoodsA, pk=goodsA_id) return (shop, goods) 以下延々と
こんな感じですね。
なにがめんどいかと言えば
- shop_idからShopオブジェクトを取得するところが分離できてないので同じ処理をたくさん書かなきゃいけない
- goods_idも同様
- ビューメソッドにshop_idとかいっぱい出てくるのでダサい
- でもURLパターンでshop_id取っちゃってるからビューメソッドの引数からなくすとエラー
やだー
どうする
このままではダサいうえに変更に弱くなるのでなんとかしましょう。
まずこんなデコレータを書きます。
(shop/views.pyとか) def shop_view(view_func): def _view(request, *args, **kwargs): _kwargs = copy(kwargs) shop_id = _kwargs.pop("shop_id") shop = get_object_or_404(Shop, shop_id) request._shop = shop return view_func(request, *args, **kwargs) return _view (goodsA/views.pyとか) def goods_view(view_func) def _view(request, *args, **kwargs): _kwargs = copy(kwargs) goods_id = _kwargs.pop("goodsA_id") goods = get_object_or_404(GoodsA, goods_id) request._goods = goods return view_func(request, *args, **kwargs) return _view
あとはgoodsAのビュークラスに仕込むだけ。
class goodsAView(object): ... @shop_view def index(self, request): return request._shop @goods_view @shop_view def buy(self, request): return (request._shop, request._goods)
ちょうすっきりしました。デコレータはよいものですね。
要点1
shop = get_object_or_404(Shop, shop_id) request._shop = shop
Shopオブジェクトを取得し、なければ404を返すというのをデコレータ上で処理させます。
ビュー周りではデコレータをかけるだけなので見通しがよく、つまらんコードをコピペする必要もなくなります。
要点2
goods_id = _kwargs.pop("goodsA_id")
kwargsからpopしています。
urlから取られた引数のdict(kwargs)がそのままviewに渡されるので受け取る側と引数がマッチしていないとエラーが出ますが*2、こいつをビューに渡る前にとっぱらってやるわけです。
要点3
_kwargs = copy(kwargs)
kwargsままでなく、コピーしています。
元のkwargsを弄ってしまうと、shop_viewより外側で適用されてかつview実行の後処理を行なうデコレータがあった場合にkwargs変更の影響が及んでしまい、危険です。
おしまい。