バックエンドがDjangoの環境にて、jQuery DataTable を使う機会がありました。
DataTables | Table plug-in for jQuery
データ量がそれなりにあったので、DataTableのServer Side Processingを使いました。
DataTables example - Server-side processing
DjangoでServer Side Processingする場合は、ライブラリ django-datatables-view
を使えば良さそうでした*1。
- pigletto / django-datatables-view — Bitbucket
- [Django] 常時Ajaxで動く検索画面を1時間で作ろう! jQuery Datatables サーバ連携機能の紹介 【サンプルアプリ有り】 - Qiita
ただ、 django-datatables-view
にて、モデルの外部キーの項目を表示する時に少々詰まったので、メモを残します。
目次
環境
なお、サンプルの見栄えを良くするため、Bootstrap 4.5.2 も使っています。
やりたいこと
以下の通り、3つのモデルがあるとします。
class Family(models.Model): """ 科 """ name = models.CharField('名前', max_length=50) class Species(models.Model): """ 種 """ family = models.ForeignKey('fk_app.Family', on_delete=models.CASCADE) name = models.CharField('名前', max_length=50) class Cultivars(models.Model): """ 品種 """ species = models.ForeignKey('fk_app.Species', on_delete=models.CASCADE) name = models.CharField('名前', max_length=50)
これらのモデルのうち、 Cultivars
を django-datatables-view
の model
に指定し、こんな感じで表示したいとします。
注意点としては
科
は、品種から見て外部キーの外部キー種
は、品種から見て外部キー
です。
対応
通常の項目である id
や name
は
class CultivarsDataTableView(BaseDatatableView): model = Cultivars columns = [ 'id', 'name', ]
と、 column
に列名をそのまま指定できます。
一方、外部キーの項目については、そのまま species
を指定しても、うまく表示できません。
そのため、Django側・JavaScript側の両方で対応が必要になります。
Django側
render_column() のオーバーライド
表示ができていないので、render_column()をオーバーライドし、外部キー列が指定された場合は、その name
を表示するようにします。
class CultivarsDataTableView(BaseDatatableView): model = Cultivars columns = [ 'id', 'family', 'species', 'name', ] def render_column(self, row, col): # 列 familyが来たら、外部キーの外部キーのnameを返す if col == 'family': return row.species.family.name if col == 'species': return row.species.name
CultivarsDataTableViewの columns に合わせ、JavaScriptの columnDefs
を定義します。
columnDefs: [ {targets: 0, data: 'id'}, {targets: 1, data: 'family'}, {targets: 2, data: 'species'}, {targets: 3, data: 'name'}, ]
また、DataTableを表示するHTMLテンプレートのtableタグは
<table id="demo" class="table table-striped table-bordered dataTable" style="width:100%"> <thead> <tr> <th>ID</th> <th>科</th> <th>種</th> <th>品種</th> </tr> </thead> </table>
とします。
この場合、画面はうまく表示されます。
しかし、外部キーの外部キーの列 (ここでは 科
) でソートしようとすると、エラーとなります。
Djangoの実行ログにも以下が記録されています。
raise FieldError("Cannot resolve keyword '%s' into field. " django.core.exceptions.FieldError: Cannot resolve keyword 'family' into field. Choices are: id, name, species, species_id
columnsの定義変更
エラーで落ちている ordering
メソッドのソースコードを見ると、columnsなどで定義した列名をDjangoのQuerySetのorder_byメソッドに渡しているようです。
そのため、order_byで解釈できる列として定義してあげれば良さそうでした。
そこで、Django側を
class CultivarsDataTableView(BaseDatatableView): model = Cultivars columns = [ 'id', # order_byで解釈できるように定義 'species__family__name', 'species__name', 'name', ] def render_column(self, row, col): # 列名が変わったので、render_columnメソッドのif文も変更 if col == 'species__family__name': return row.species.family.name if col == 'species__name': return row.species.name return super().render_column(row, col)
とします。
また、columnsの定義が変わったので、JavaScript側の columnDefs
も
columnDefs: [ {targets: 0, data: 'id'}, {targets: 1, data: 'species__family__name'}, {targets: 2, data: 'species__name'}, {targets: 3, data: 'name'}, ]
のように変更します。
その結果、 科
列をクリックしてもエラーが起きなくなりました。
まとめ
- Django側
- 外部キーの属性を表示したい場合は、
columns
に__
で結合して定義する - 上記の名前で描画できるよう、
render_column()
メソッドをオーバーライドし、外部キーの該当の値を指定する
- 外部キーの属性を表示したい場合は、
- JavaScript側
- columnDefsの
data
キーに、 Djangoのcolumns
で定義した名前を指定する
- columnDefsの
ソースコード
GitHubに上げました。 fk_app
が今回のアプリです。
https://github.com/thinkAmi-sandbox/django-datatables-view-sample