前回記事で、Django
でちゃちゃっとツール作った、みたいな話を書きました。
開発では、Django
のテンプレートビューを多用してささっと機能を増やしていく、という手法を用いました。
テンプレートビューに関して、簡単ではありますが紹介するのが、今回の記事です。
同等の機能は、他のMVC
モデルでも存在すると思いますので、こういう感じなんだなーと思っていただければ幸いです。
バージョン等
当記事の記述は、Python
等の以下のバージョンでの動作検証に基づき、執筆しています。
ツール | バージョン |
---|---|
Python | 3.7.2 |
Django | 2.1.7 |
ジェネリックビューとは
そもそも、Django
でView
という場合、MVC
モデルのController
を指します。紛らわしい。Django
やっていない人はお手数ですが都度読み替えてください。当記事中ではView
と表記します。
View
の役割は、リクエストに対してレスポンスを返すことです。ただ、ページを返す場合の応答は定型化できます。例として、
- リストを返す
- 単一のオブジェクトを返す
- 作成する
- 更新する
- 削除する
などが挙げられます。
Django
では、上のような処理を行う際、URLの引数、対象Model
などの情報を指定することで、上記処理を行ってくれるようなView
が定義されています。それをジェネリックビュー(Generic View)と呼んでいます。
例
前提:Model層
- models.py
from django.db import models class Table(models.Model): """テーブル一覧""" name = models.CharField(max_length=100) class Column(models.Model): """カラム定義(物理名格納)""" table = models.ForeignKey(Table, on_delete=models.CASCADE) name = models.CharField(max_length=100)
上記二つのModel
を使います。
リストを返す(ListView)
結果を一覧として返します。もっとも単純な場合、フィルタ等なしで単に一覧を返す形となります。
Tableの一覧
- views/table.py
from django.db.models import Count from django.views.generic import ListView from ..modesl import Table class TableListView(ListView): """テーブル一覧""" model = Table context_object_name = 'tables' paginate_by = 50
最も単純な形です。クラスの中で、変数として様々な指定を行います。よく使うのはこんな感じです。
変数名 | 説明 |
---|---|
model | Modelのクラスを指定する |
context_object_name | templateで、modelを参照する場合の名前 |
paginate_by | ページングする場合の、1ページに表示するレコード数。指定した場合、自動的にページングが有効になる |
template_name | 省略すると、「モデル名(小文字)_list.html」になる |
これをもとにtemplate
を書きます。
- templates/table_list.html
{% extends "inputs/base.html" %} {% block title %}テーブル一覧{% endblock %} {% block body %} <h3>テーブルの一覧</h3> {% include 'inputs/paging.html' %} <table class="table table-striped table-bordered table-hover table-responsive table-sm"> <thead> <tr> <th>テーブル名</th> </tr> </thead> <tbody> {% for table in tables %} <tr> <td> <a href={% url 'inputs:table' table.id %}>{{ table.name }}</span> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
base.py
は、<body>
以外全部定義したものです。{% for table in tables %}
とあるように、View
でcontext_object_name = 'tables'
と指定した名称をこちらで使用できます。
ページング処理に関しても、template
に渡されるオブジェクト名等は決まっているため、{% include 'inputs/paging.html' %}
のように一つのtemplate
にまとめており、都度includeしています。個別にページングを作る必要が無くてとても便利です。
Columnの一覧
ここでは、あるテーブルに属するカラムを取得する、とします。よって、引数等でTable
のIDを取得します。
- views/table.py
from django.views.generic import ListView from ..modesl import Column class TableDetailView(ListView): model = Column context_object_name = 'columns' paginate_by = 50 template_name = 'inputs/columns.html'
Table
とあまり変わりません。しかし、今回は
- 絞り込みが必要
- テーブル名を画面に表示
をやりたいとします。この場合、フィールド指定のみでは実現できません。そこで、ListView
からメソッドを継承します。
def get_queryset(self): return Column.objects.filter(table=self.kwargs.get('pk')) def get_context_data(self, *, object_list=None, **kwargs): context = super(TableDetailView, self).get_context_data(**kwargs) context['table'] = get_object_or_404(Table, id=self.kwargs.get('pk')) return context
継承したメソッドは以下の通りです。
メソッド名 | 説明 |
---|---|
get_queryset | 取得する一覧のクエリを指定 |
get_context_data | 一覧以外で必要なデータを取得し、割り当てる |
一覧は、何もしないとすべてのカラムを取得してしまうため、指定したテーブルに限定しています。
また、カラムが属するテーブル情報が欲しいため、get_context_data
で取得し、context
にセットしています。
上記のメソッドで指定しているpk
という値は、URL
で指定しています。
- urls.py
from django.urls import path from .views.home import HomeView from .views import table urlpatterns = [ path('', HomeView.as_view(), name='home'), path('table', table.TableListView.as_view(), name='tables'), path('table/<int:pk>', table.TableDetailView.as_view(), name='table'),
path('table/<int:pk>', table.TableDetailView.as_view(), name='table'),
のように指定すると、引数でpk
が渡ってきます。
詳細を返す(DetailView)
単一項目を返す際に利用します。ここから、更新や削除機能を提供するのが常道と思われます。
Column詳細
- views/column.py
class ColumnDetailView(DetailView): """カラム詳細""" model = Column template_name = "inputs/col.html" context_object_name = 'col' def get_queryset(self): return super().get_queryset().select_related( 'table' )
ここでも、必要であればメソッド継承を行います。get_context_data
を使う場合、既に対象をオブジェクトを取得しているため、さらに何か取得する必要がある場合、それを参照できます。ここでは、カラム名とそれが所属するテーブルの情報も欲しいため、それを合わせて取得しています*1。
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['kinds'] = ColumnKind.objects.filter( column=self.object ).select_related( 'definition' ) return context
詳細は省きますが、列定義とは別に列分類というものがあり、それを追加取得しています。
上記、filter
で参照しているself.object
に、DetailView
内で自動で取得されたColumn
オブジェクトへの参照が格納されています。改めてColumn
を取得する必要がないため、クエリ発行回数の節約になります。
なお、urlは以下のように指定します。
- urls.py
urlpatterns = [ path('column/<int:pk>', column.ColumnDetailView.as_view(), name='col'), ]
view
内でpk
を一度も参照していないことに気づかれたでしょうか?
DetailView
を使う場合、urlに指定した値からうまくオブジェクトを取得してくれます。楽です。
参照リンク
おわりに
これらを使うと、View
がファットになってしまう*2状況は避けられません。しかし、なんでもTemplateView
で書くよりは何をやっているのか明確です。
ここでこの変数を指定したらこの処理やりますよ、というのは、動的言語らしい感じがします。それゆえに楽できるわけですが、複雑になるとカオス感が加速します。
用法用量を守って正しくお使いくださいという言葉が身に沁みます。
個人的な意見を言うなら、フレームワークを使うと確かに楽だけど、アプリを発展させるときに足かせになるのもまた、フレームワークだと思います。その確認も兼ねて、使ってみている最中です。
次回は更新ビューについて書きます。