間が空きましたが、前回記事に引き続き、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について述べます。またよろしくお願いします。