DjangoでA<-B<-Cという方向で関係性を持ったモデルがあったとする。
データをいくつか作って、テンプレートで表示させる際にManyToManyフィールドがあると、途中の参照でManagerが入ってしまう。
その際にModelMapperを使って記述を少し簡単にしよう、という話。
まあ実際に使える場面は多くない。
myapp/models.py
C.b_listがManyToManyになるため、インスタンス化したc.b_listはManagerになる。
from django.db import models class A(models.Model): name = models.CharField(max_length=10) def __unicode__(self): return self.name class B(models.Model): name = models.CharField(max_length=10) a = models.ForeignKey(A, null=True, blank=True) def __unicode__(self): return self.name class C(models.Model): name = models.CharField(max_length=10) b_list = models.ManyToManyField(B, blank=True) def __unicode__(self): return self.name
myapp/fixtures/initial_data.json
表示する際に使うデータはこんな感じ。
[ { "pk": 1, "model": "myapp.a", "fields": { "name": "A1" } }, { "pk": 2, "model": "myapp.a", "fields": { "name": "A2" } }, { "pk": 3, "model": "myapp.a", "fields": { "name": "A3" } }, { "pk": 1, "model": "myapp.b", "fields": { "a": 1, "name": "B1" } }, { "pk": 2, "model": "myapp.b", "fields": { "a": 2, "name": "B2" } }, { "pk": 3, "model": "myapp.b", "fields": { "a": 3, "name": "B3" } }, { "pk": 4, "model": "myapp.b", "fields": { "a": null, "name": "B4" } }, { "pk": 5, "model": "myapp.b", "fields": { "a": null, "name": "B5" } }, { "pk": 6, "model": "myapp.b", "fields": { "a": null, "name": "B6" } }, { "pk": 1, "model": "myapp.c", "fields": { "name": "C1", "b_list": [ 1, 2 ] } }, { "pk": 2, "model": "myapp.c", "fields": { "name": "C2", "b_list": [] } }, { "pk": 3, "model": "myapp.c", "fields": { "name": "C3", "b_list": [ 3, 4, 5 ] } }, { "pk": 4, "model": "myapp.c", "fields": { "name": "C4", "b_list": [ 6 ] } } ]
myapp/mappers.py
ここで、bpmappers.djangomodelのModelMapperを使う。
CモデルからMapperクラスを作成。
from bpmappers.djangomodel import ModelMapper from myapp.models import C class MyMapper(ModelMapper): class Meta: model = C
myapp/views.py
Cモデルからいくつかのオブジェクトを取り出して、MyMapperでマッピングしてindex.htmlテンプレートに渡す。
from django.views.generic import TemplateView from myapp.mappers import MyMapper from myapp.models import C class MyView(TemplateView): template_name = 'index.html' def get_context_data(self, **kwargs): context = super(MyView, self).get_context_data(**kwargs) context['data'] = [MyMapper(obj).as_dict() for obj in C.objects.all()[:5]] return context
ここで、Cモデルのマッピングした結果は {'b_list': [...], ...} のような辞書になり、b_listはリストになる。
また、マッピングの時点でallメソッドを呼んでいるので、ここでデータベースへのアクセスが発生する。
myapp/templates/index.html
テンプレート内では、Managerではなく、マッピング後の辞書を使ってデータを表示する。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8"/> </head> <body> <h1>bpmappers.djangomodel.ModelMapper</h1> <ul> {% for c in data %} <li>{{ c.name }} {% if c.b_list %} <ul> {% for b in c.b_list %} <li>{{ b.name }}</li> {% if b.a %} <ul> <li>{{ b.a.name }}</li> </ul> {% endif %} {% endfor %} </ul> {% endif %} </li> {% endfor %} </ul> </body> </html>
Mapperを使わない場合は、c.b_listのところがc.b_list.allのようになる。
テンプレート側にデータベースへの問い合わせの記述が入ってしまうと複雑になってくるので、こう書けるほうが少し簡単で嬉しい。
データの取得方法を変更する場合
c.b_list.allではなく、filterメソッドで絞込みたい場合、この例だとMapperクラスの記述だけで対応できる。
規模が大きい場合は、データの取得をそれだけでまとめてしまって、マッピングコードも分離したほうが良さそう。