間が空きましたが、前回記事に引き続き、Django
のジェネリックビューについてお話しします。
今回はCreateView
です。CRUD
のC
に該当します。
以前の記事はこちらです。ジェネリックビューとは何なのか、という部分について説明を書いています。当記事を見る前に前回記事を見ることをお勧めします。
バージョン等(再掲)
基本的に前回と同様です。一部bootstrap
を使うので、追加しています。
ツール | バージョン |
---|---|
Python | 3.7.2 |
Django | 2.1.7 |
bootstrap | 4.1.3 |
前提: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
を使います。
CreateViewについて
CreateView
とは、名前の通りデータの生成を行うビューです。DBでいうとINSERT
を行います。
CreateViewの効果
レコードを1行作ろうとすると、本来なら
- modelに対応したHTMLとformを作る
POST
が来たら、ブラウザで入力された値を取得する- 値を検証する
- 保存modelのインスタンスを作る
- 入力値を一つずつmodelのフィールドに渡す
- modelの保存処理を呼び出す
- ブラウザの画面を遷移させる
という作業が必要です。面倒なのでできる限り省力化しよう、というのがCreateViewの目指すところです。
CreateViewを使うと、最低限やることは
- 対象modelを指定する
- 入力したいフィールドを指定する
- 描画用HTMLを指定する
- 更新後の遷移先を指定する
だけになります。すべて指定するだけでよいです。
ケース1:単体で完結するモデル
tableの作成
tableは、name
と主キーしかないです。他のテーブルとリレーションが無い場合、最低限の定義だけで保存処理まで作れます。
- views/table.py
from django.views.generic import CreateView from django.urls import reverse_lazy class TableCreateView(CreateView): """テーブル作成""" model = Table fields = ('name', ) template_name = 'create.html' success_url = reverse_lazy('home')
クラス変数は、それぞれこのような意味です。
変数 | 意味 |
---|---|
model | Createの対象となるモデルのクラス |
fields | modelのうち、Create時に入力対象とするフィールド。テンプレートでformが自動生成される |
form_class | 自動生成されるformではないものを使いたい場合、そのformのクラスを指定する |
template_name | 入力時に用いるテンプレート名。省略すると[model名]_form.html になる |
success_url | 作成成功時に遷移するページのURL |
続いてTemplate
です。これはフォームを表示する汎用的なものにしています*1。
- templates/create.html
{% extends "base.html" %} {% load static %} {% block title %}{{ title }}{% endblock %} {% block body %} <h3 class="h3 border-bottom">{{ title }}</h3> <form method="post"> {% csrf_token %} {{ form.as_p }} <input class="btn btn-primary" type="submit" value="登録"> </form> {% endblock %}
見た目はこのようになります。一部bootstrap
を使っています。
上記nameを入力し、「登録」を押すだけで、Createが行われます。これだけです。クラス作って最低限の定義を行うだけで、Create用のViewが準備できます。すごく便利です。
作成後のデータを開く
上記のままだと、登録後にトップページが開く*2ようになります。
しかし、作成したものを開きたい、とします。その場合は、get_success_url
をオーバーライドします。ただ、その前に遷移先ページを作成します。
- urls.py
path('table/<int:pk>', table.TableDetailView.as_view(), name='table'),
を追加します。
urlpatterns = [ path('', HomeView.as_view(), name='home'), path('table/new', table.TableCreateView.as_view(), name='table_new'), path('table/<int:pk>', table.TableDetailView.as_view(), name='table'), # 追加 ]
そして、TableDetailView
という詳細ビューを用意します。内容は最初に紹介した記事のDetailView
の内容と同じですので割愛します。そして、table.py
のsuccess_url
を削除し、代わりにget_success_url
をオーバーライドします*3。
from django.views.generic import CreateView from django.urls import reverse from ..models import Table class TableCreateView(CreateView): """テーブル作成""" model = Table fields = ('name', ) template_name = 'create.html' def get_success_url(self): return reverse('table', kwargs={'pk': self.object.id})
reverse
の引数は、
順番 | 内容 |
---|---|
第一引数 | urls.pyで指定したname |
kwargs(名前付き引数) | URLに渡す引数 |
となります。追加したURLと変数<int:pk>
(<型:変数名>)に合わせた定義です。こう書くと、Createの後に作成されたオブジェクトのページに遷移します。
なお、get_success_url
でself.object
というものを参照しています。これに関しては、
CreateView を使うとき、self.object にアクセスできます。これは作成されているオブジェクトです。オブジェクトがまだ作成されていない場合、値は None になります。
という記述が以下のページにある通り、生成後のオブジェクト(ここではmodel
に指定したTable
のインスタンス)が格納されています。なので、CreateView
で生成後のオブジェクトを元に処理したい場合、同じ手法が使えます。
ケース2:リレーションを持つモデル
columnの作成
columnは、tableを参照する外部キー制約があります。普通にフォームを作ると、
- tableの選択
- nameの入力
という形になりますが、columnは普通テーブルに追加するので、テーブルの画面から「列の追加」ボタンがある感じで作ります。
- urls.py
path('table/<int:pk>/column/new', column.ColumnCreateView.as_view(), name='col_new'),
という、TableDetailView
から呼ばれることを前提としたURLを用意します。
urlpatterns = [ path('', HomeView.as_view(), name='home'), path('table/new', table.TableCreateView.as_view(), name='table_new'), path('table/<int:pk>', table.TableDetailView.as_view(), name='table'), path('table/<int:pk>/column/new', column.ColumnCreateView.as_view(), name='col_new'), # 追加 ]
- views/column.py
from django.views.generic import CreateView from django.shortcuts import get_object_or_404 from ..models import Table, Column class ColumnCreateView(CreateView): model = Column fields = ('name', ) template_name = 'create.html' def get_success_url(self): return reverse('inputs:table', kwargs={'pk': self.kwargs.get('pk')})
まずは、先ほどと同じく最低限で定義します。
これで登録しようとすると、
例外が発生します。
NOT NULL constraint failed
とあるように、外部キーであるtable
を指定していないために発生します。なので、事前に渡します。
更新前に、作成されようとしているオブジェクトに値を渡すには、form_valid
をオーバーライドします。
- views/column.py
from django.views.generic import CreateView from django.shortcuts import get_object_or_404 from ..models import Table, Column class ColumnCreateView(CreateView): model = Column fields = ('name', ) template_name = 'create.html' def form_valid(self, form): # テーブルを置く table = get_object_or_404(Table, pk=self.kwargs.get('pk')) form.instance.table = table return super().form_valid(form) def get_success_url(self): return reverse('inputs:table', kwargs={'pk': self.kwargs.get('pk')})
上記に書いていますが、form_valid
の引数form
にはinstance
という項目があり、ここに作成しようとしているオブジェクトが入っています。
なので、そこにセットした後に継承元のform_valid
を呼び出すと、保存が行われます。
そもそも、このタイミングでやるのが適切か?とは思いましたが、
Saves the form instance, sets the current object for the view, and redirects to get_success_url().
(Google翻訳:フォームインスタンスを保存し、現在のオブジェクトをビューに設定して、get_success_url()にリダイレクトします。)
という記述がここにあることから、おそらく問題はないのだと思います。
余談:保存処理が呼ばれるタイミング
CreateView
の場合、実際の保存はsuper().form_valid(form)
のタイミングで行われています。
継承元のソースを追っていくと、
- edit.py
class ModelFormMixin(FormMixin, SingleObjectMixin): # 中略 def form_valid(self, form): """If the form is valid, save the associated model.""" self.object = form.save() return super().form_valid(form)
というメソッドがあり、保存処理が呼ばれていることが分かります。
おわりに
CreateView
が有効に使えるのは、単一のmodelに対して保存する場合です。その場合は上に書いたような、簡単な定義だけで動かせてしまいます。
追加データを取るのは、display view
と同じくget_context_data
等で行えますし、保存前の処理も変更できます。ある程度は柔軟に使えます。
ただ、多くの入力項目があり、複数のテーブルに書き込む必要があるページの場合、色々大変そうです。今回は調べていませんが、FormSet
なるものもあり、複数のformを扱うこともできるようです。
便利な使い方はまだまだありそうです。
次回は、UpdateView
について述べます。またよろしくお願いします。