ライブラリ django-datatables-view
を使ってjQuery DataTableのServer Side Processing を行った際、ソートを実装することがありました。
ただ、ソートを実装しようとしたところ詰まったことがあったため、メモを残します。
目次
環境
なお、サンプルの見栄えを良くするため、Bootstrap 4.5.2 も使っています。
ソートを意識しない時の実装
このようなモデルがあったとします。
class Color(models.Model): name = models.CharField('色', max_length=10) class Apple(models.Model): name = models.CharField('品種', max_length=50) color = models.ForeignKey('sort_app.Color', on_delete=models.CASCADE) breeding = models.CharField('交配', max_length=100) season = models.IntegerField('旬') born_in_nagano = models.BooleanField('長野県生まれ')
このAppleモデルをDataTableに表示します。
その時の要件として、
season
(旬) が実行月と一致した場合は、チェックボックスにチェックを入れる- 最初は、id列でソート
があった時に、以下のような表示をしたいとします。
この時の django-datatables-view
を使ったViewの実装はこんな感じです。
class AppleAllFieldDataTableView(BaseDatatableView): model = Apple columns = [ 'id', 'name', 'color__name', 'breeding', 'season', 'born_in_nagano', ] def render_column(self, row, col): if col == 'color__name': return row.color.name if col == 'season': if row.season == timezone.now().month: return True return False if col == 'born_in_nagano': # Booleanな項目の場合な場合、返し方によりJSでの型が異なるので注意 # 暗黙的に返す -> JSではstring型 # 明示的に返す -> JSではboolean型 return row.born_in_nagano return super().render_column(row, col)
注意点として、Djangoのモデルで BooleanField
は、そのままJavaScript側に渡すと string
型になります。
それでは扱いづらいので、 render_column()
メソッドをオーバーライドし、明示的にPythonのTrue/Falseを返すようにします。これにより、JavaScriptには boolean
型の値が渡されます。
続いて、JavaScript側の実装です。
DataTableの中でチェックボックスを表示できるよう、render
属性を使って描画するHTMLを指定します。
$('#all-columns').DataTable({ autoWidth: false, serverSide: true, processing: true, responsive: true, pageLength: 25, // サンプルデータ量が多いため、デフォルト値を変えておく ajax: { 'url': '/sort-app/all-columns/data/', 'type': 'GET', }, columnDefs: [ {targets: 0, data: 'id'}, {targets: 1, data: 'name'}, {targets: 2, data: 'color__name'}, {targets: 3, data: 'breeding'}, { targets: 4, data: 'season', render: data => { const checked = data ? 'checked="checked"' : ''; return `<input type="checkbox" ${checked} />`; } }, { targets: 5, data: 'born_in_nagano', render: data => { const checked = data ? 'checked="checked"' : ''; return `<input type="checkbox" ${checked} />`; } }, ], });
なお、JavaScript側の render()
やDjango側の render_column()
は、あくまで見た目を整えるだけであり、実際のデータとは異なります。
そのため、旬
列でソートしたとしても、DB上のデータは xx月であることから、チェックボックスがONの順番ではソートされません。
これをベースに、いろいろ試していきます。
複合ソートキーでの結果を初期表示する場合
JavaScript側で対応が必要になります。
jQuery DataTableでは、初期表示するソートキーの列は order
属性を使います。
DataTables example - Default ordering (sorting)
[ソートキー列のindex, ソート順文字列]
という要素を用意し、それをArrayで指定します。ソート順文字列は asc
・ desc
のどちらかを指定します。
$('#all-columns').DataTable({ serverSide: true, processing: true, // ... columnDefs: [ /* 略 */ ] order: [ [3, 'asc'], [5, 'desc'], // ソートキーが重複した場合にもソートが常に一定となるよう、idをソートキーに加えておく [0, 'asc'], ], });
これにより、
- 交配
- 長野県生まれ
- ID
の複合キーで、指定した順(昇順/降順)にて、ソートされるようになりました。
ソート不可列がある時のソートについて
JavaScript側、Django側の双方で対応が必要になります。
JavaScript側の修正
DataTableでソート不可列を指定するには、columnDefsの中で該当の列に orderable: false
を指定します。
また、ソート不可列を order
属性に含めないよう、注意します。
$('#some-columns').DataTable({ // ... columnDefs: [ {targets: 0, data: 'id'}, // ... { targets: 5, data: 'born_in_nagano', searchable: false, orderable: false, // ソート不可 render: data => { const checked = data ? 'checked="checked"' : ''; return `<input type="checkbox" ${checked} />`; } }, ], order: [ // ソート不可な列を追加するとエラーになるので注意 [3, 'asc'], [0, 'asc'], ] });
Django側の修正
django-datatables-viewのREADMEにあるサンプルを読むと、
# https://bitbucket.org/pigletto/django-datatables-view/src/master/ # define column names that will be used in sorting # order is important and should be same as order of columns # displayed by datatables. For non sortable columns use empty # value like '' order_columns = ['number', 'user', 'state', '', '']
との記載があります。
そのため、 order_columns
を追加で定義し、ソート不可項目のIndexの要素には、空文字を指定します。
class AppleSomeFieldDataTableView(BaseDatatableView): model = Apple columns = [ 'id', 'name', 'color__name', 'breeding', 'season', 'born_in_nagano', ] order_columns = [ 'id', 'name', 'color__name', 'breeding', 'season', '', # born_in_nagano部分はソート不可にする ] def render_column(self, row, col): ...
結果
権限により、列の表示可否が変わる時のソートについて
例えば、「ログインしている場合のみ、 交配
列を表示する」という仕様が追加になったとします。
この場合、
- ログイン状態により、DataTableの列表示可否を変更
- ソート機能を変更
の2つの対応が必要になります。
そのため、順番に作業を行います。
ログイン状態により、DataTableの列表示可否を変更
テンプレートの変更
DataTableのヘッダの表示を動的に変更します。
<table id="perms-columns" class="table table-striped table-bordered dataTable" style="width:100%"> <thead> <tr> <th>ID</th> <th>品種</th> <th>色</th> {# 交配は、ログインしていれば見える #} {% if request.user.is_authenticated %} <th>交配</th> {% endif %} <th>旬</th> <th>長野県生まれ</th> </tr> </thead> </table>
JavaScript側の変更
columnDefsを動的に制御するため、以下の関数を追加します。
function getColumnDefs() { const results = []; let colIndex = 0; results.push({targets: colIndex++, data: 'id'}); results.push({targets: colIndex++, data: 'name'}); results.push({targets: colIndex++, data: 'color__name'}); if ($('#username').length) { results.push({targets: colIndex++, data: 'breeding'}); } results.push({ targets: colIndex++, data: 'season', render: data => { const checked = data ? 'checked="checked"' : ''; return `<input type="checkbox" ${checked} />`; } }); results.push({ targets: colIndex++, data: 'born_in_nagano', render: data =>{ const checked = data ? 'checked="checked"' : ''; return `<input type="checkbox" ${checked} />`; } }); return results; }
そして、この関数の戻り値を、columnDefsに指定します。
なお、 columnDefs: () => { /* .. */ }
のような定義では動作しませんでした。
columnDefs: getColumnDefs(),
Django側の修正
get_columns
メソッドをオーバーライドし、ログインしている場合のみ 交配
列を追加します。
class ApplePermsFieldDataTableView(BaseDatatableView): model = Apple def get_columns(self): results = [ 'id', 'name', 'color__name', ] if self.request.user.is_authenticated: results.append('breeding') results.extend([ 'season', 'born_in_nagano', ]) return results
ソート機能を変更
列表示が動的になる場合、JavaScriptとDjango側で表示する列数を合わせる必要があります。具体的には以下の4項目です。
- JavaScript
- columnDefs
- order
- Django
- columns
- (必要に応じて) order_columns
ここまでで対応していないのはJavaScript側の order
ですので、対応を行います。
まずは columnDefs同様、列を動的に変更する関数を用意します。
function getOrder() { const results = []; let bornIndex = 4; // 非表示時の "born_in_nagano" 位置 if ($('#username').length) { results.push([3, 'asc']); bornIndex++; // 列が増えるため、"born_in_nagano" 位置を修正 } results.push([bornIndex, 'desc']); // ソートキーが重複した場合にもソートが常に一定となるよう、idをソートキーに加えておく results.push([0, 'asc']); return results; }
続いて、 order属性にこの関数の戻り値を割り当てます。
$('#perms-columns').DataTable({ // ... columnDefs: getColumnDefs(), // 変更 order: getOrder(), });
結果
以上の対応により、動的に列数が変わったとしても、初期ソート状態やソート機能が利用できるようになります。
未ログイン時
交配
列が表示されず、 長野県生まれ
列の降順だけでのソートとなります。
ログイン済時
交配
列の昇順・ 長野県生まれ
列の降順でソートされています。
ソースコード
GitHubに上げました。 sort_app
アプリが今回のソースコードです。
https://github.com/thinkAmi-sandbox/django-datatables-view-sample