django-datatables-viewで、モデルの複数列を結合して表示しようと考えた時に詰まったことがあったため、メモを残します。
目次
環境
なお、サンプルの見栄えを良くするため、Bootstrap 4.5.2 も使っています。
やりたいこと
以下のような2つのモデルがあったとします。
class Color(models.Model): name = models.CharField('色', max_length=10) class Apple(models.Model): name = models.CharField('品種', max_length=50) color = models.ForeignKey('concat_col_app.Color', on_delete=models.CASCADE) breeding = models.CharField('交配', max_length=100)
DataTableの表示では、 color
と name
の値を組み合わせ、以下のように タイトル
列として表示したいとします。
エラーとなる方法
画面の表示に関するものなので、Django側は render_column
を使い
class AppleConcatDataTableView(BaseDatatableView): model = Apple columns = [ 'id', 'title', # 組み合わせて表示したい列を追加 'breeding', ] def render_column(self, row, col): # title列が入ってきたら、colorとnameを組み合わせて表示 if col == 'title': return f'[{row.color.name}] {row.name}' return super().render_column(row, col)
としてみました。
また、JavaScript側も、 title
列を受け取れるようにします。
$('#demo').DataTable({ autoWidth: false, serverSide: true, processing: true, responsive: true, ajax: { url: '/concat-col-app/data/', type: 'GET', }, columnDefs: [ {targets: 0, data: 'id'}, {targets: 1, data: 'title'}, {targets: 2, data: 'breeding'}, ] });
しかし、初期表示はうまくいくものの、Searchやタイトル列をクリックするとエラーが表示されてしまいます。
また、Djangoのログにも以下が記録されています。
... raise FieldError("Cannot resolve keyword '%s' into field. " django.core.exceptions.FieldError: Cannot resolve keyword 'title' into field. Choices are: breeding, color, color_id, id, name
動作する方法
Djangoのログを見ると、Djangoのmodelに title
が見当たらないのが原因のようです。
READMEにある Another example of views.py customisation のうち、 get_initial_queryset
メソッドの説明を読むと
def get_initial_queryset(self): # return queryset used as base for futher sorting/filtering # these are simply objects displayed in datatable # You should not filter data returned here by any filter values entered by user. This is because # we need some base queryset to count total number of records. return MyModel.objects.filter(something=self.kwargs['something']) https://bitbucket.org/pigletto/django-datatables-view/src/master/
と書かれています。
そのため、django-datatables-viewの get_initial_queryset
メソッドをオーバーライドしてQuerySetに title
属性を追加することで、filterやsortにも対応できそうです。
そこで、Django側のViewを修正します。
今回はレコードごとに列を追加するため、Djangoの annotate句と
- Concat
- Value
を使うこと、出力したい [色] 品種名
という形式の値を持つ title 属性を追加します。
- QuerySet の各アイテムに対する集計を生成する | アグリゲーション | Django ドキュメント | Django
- Concat | Database Functions | Django documentation | Django
from django.db.models import Value from django.db.models.functions import Concat from django_datatables_view.base_datatable_view import BaseDatatableView from concat_col_app.models import Apple class AppleConcatDataTableView(BaseDatatableView): model = Apple columns = [ 'id', 'title', 'breeding', ] def get_initial_queryset(self): # title列を追加 return super().get_initial_queryset().annotate( title=Concat(Value('['), 'color__name', Value('] '), 'name') )
なお、表示内容については、get_initial_querysetで作成した title 列の値そのもので良いため、今回は render_column のオーバーライドは不要です。
これにより、Searchやタイトル列クリックによるソートを行っても動作するようになりました。
以下の画像でも、タイトル列の記号にある通り、タイトル列でのソートができていることが分かります。
ソースコード
GitHubに上げました。 concat_col_app
アプリが今回のソースコードです。
https://github.com/thinkAmi-sandbox/django-datatables-view-sample