
【第3回】Djangoで日記アプリを作ろう~投稿フォーム編~
2021.12.20
【第3回】Djangoで日記アプリを作ろう ~投稿フォーム編~
前回は、TemplateView を利用して、日記アプリのトップページを作成しました。
今回は日記投稿フォーム画面を作成し、さらにトップページに投稿画面へのリンクを貼りましょう!
この投稿フォームの作成を通して、下記の内容を学びます。
- Mode lクラスを利用したモデルの実装方法
- Model Form クラスを用いたフォームの実装方法
- CreateView を用いたビューの実装方法
- テンプレート内部に url を記述する方法
- マイグレーションの実行方法
フォームとモデルの概念
まずは、フォームとモデルという概念を整理しましょう!
フォーム
フォームは、ユーザーに対して何かしらの入力を求める場面で、登場する概念です。
なので、フォームの実装では、「ユーザーからどのようなデータを受け取るか?」という定義を行います。
モデル
一方、モデルは Django アプリとデータベースとのやりとりで登場する概念です。
よって、モデルの実装では「データベースにおけるテーブルのカラム情報」を記載します。
フォーム経由でユーザーからデータを受け取り、必要であればビューでそのデータに追加情報を加えます。
その後、「テーブルに書き込む」というのが、基本的なフォームデータの流れです。
Django ORM マッパー
Django アプリとデータベースのやりとりを支える機能が、Django ORM マッパーです。
この機能により、開発者はモデルをPythonクラスとして実装し、その後「マイグレーション」という操作を実行するだけで、接続しているデータベースにテーブルを作成することができます。
クエリを用いて、データベース上にテーブルを作成する必要はありません。
Djangoにおけるデータベースの設定
次に、データベースの設定をしていきましょう。
とは言ったものの、本連載の範囲ではデータベースの設定は特に必要ありません。
実は Django プロジェクト作成時に、SQLite というデータベースとの接続設定が、既に行われています。
データベース自体も、後ほど説明する「makemigrations」コマンドにより、自動で作成されます。
SQLite は、Python に標準的に備わっている、簡易的なデータベースです。
db.sqlite3 というファイルで作成されるため、削除が簡単でやり直しが効きやすく開発時には便利です。
本連載では、SQLite を利用していきます。
SQLite との接続設定を確認してみましょう!
config ディレクトリ内部の「setting.py」の中から、以下のような箇所を探してみて下さい。
1 2 3 4 5 6 | DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } |
ENGINE キーに対して、SQLite3 用のデータベースバックエンドが指定されています。
Django が標準で提供する、他のデータベースバックエンドについては、以下のリンク先を参照ください。
【Django ドキュメント:設定】
https://docs.djangoproject.com/ja/3.1/ref/settings/#std:setting-DATABASE-ENGINE
日記投稿フォームを作ろう!
では、日記の投稿フォームを作っていきましょう。
所々で、設定ファイルの編集が必要となりますので、お忘れなく!
Modelの実装
今回は、以下のような定義の日記テーブルを作成することにします。
カラム名 | 型 | 意味 |
id | UUID | データのid |
date | Date | 日付 |
title | Char | 日付のタイトル |
text | Char | 日付の本文 |
created_at | DateTime | 作成日時 |
updated_at | DateTime | 編集日時 |
(updated_at は、今後データの編集を行うことを見据えて、ここで定義しておきます)
Django では、上記のテーブル定義を元に、SQL でテーブルを作りません。
モデルを実装することで、自動でテーブルを作成します。
diary ディレクトリ内の models.py に、以下のコードを記述しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 | from django.db import models from django.utils import timezone import uuid class Diary(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) date = models.DateField(verbose_name='日付', default=timezone.now) title = models.CharField(verbose_name='タイトル', max_length=40) text = models.CharField(verbose_name='本文', max_length=200) created_at = models.DateTimeField(verbose_name='作成日時', default=timezone.now) updated_at = models.DateTimeField(verbose_name='編集日時', blank=True, null=True) |
Django の models モジュールにある、Model クラスを継承した Diary クラスを作成しています。
テーブルのカラムが、「UUIDField」や「DateField」といった、Django のクラスを利用して定義されています。
id に関しては、model 上で定義をしなくても、自動で id カラムは作成されます。
ただし、データを作成する度に、連番で id が付与される仕様となっています。
今回は id を UUID とするために、あえて UUIDField を用い、 id フィールドを上書きしています。
各 Field クラスは、default や verbose_name など、様々なフィールドを持っています。
詳細は、以下のリンク先を参照してください。
【Django ドキュメント:Model field referenc】
https://docs.djangoproject.com/en/3.1/ref/models/fields/
LANGUAGE CODE と TIMEZONEの変更
Django デフォルトの言語コードは「en-us」、つまり英語になっています。
このままだと、日付型において11月が「Nov.」のように、英語に翻訳されてしまいます。
config ディレクトリの settings.py の中に、 LANGUAGE_CODE = 'en-us' という記述を探し、「ja-jp」に書き換えましょう。
また、Django のデフォルトの TIMEZONE は、協定世界時(UTC)となっています。
つまり現状では、Diary モデルの created_at の default 値に、UTC 基準の時間が入ります。
これも、日本時間に変更しましょう。
config ディレクトリの settings.py の中に、 TIME_ZONE = 'UTC' という記述があります。
これを、 TIME_ZONE = 'Asia/Tokyo' に変更します。
これで日本時間を扱えるようになります。
Formの実装
続いて、フォームの実装をしていきましょう!
実装方法が2種類ある
Django では、Form の実装方法が2種類あります。
Form クラスを利用する場合と、ModelForm クラスを利用する場合です。
Form クラスの実装スタイルは、Model の実装と類似しており、フォームの値と型を1つ1つ記述していきます。
一方 ModelForm クラスの実装スタイルは、Model 定義を再利用し「どのカラムをフォーム値として利用するか?」という記述をしていきます。
Model を定義した状況では、ModelForm クラスを利用した方が、コーディングの量が少なくなります。
なので、本連載でも ModelForm を利用していきます。
フォームを実装してみよう
では、ModelForm を利用してフォームを実装しましょう。
今回の日記アプリでは、ユーザーから「日付」「日記のタイトル」、そして「日記の本文」のデータを受け取るとします。
Diary ディレクトリ内に、forms.py ファイルを作成して、以下のコードを記述しましょう!
ちなみに forms.py は、startapp コマンド実行時には作成されません。
1 2 3 4 5 6 7 8 | from django import forms from .models import Diary class DiaryForm(forms.ModelForm): class Meta: model = Diary fields = ('date', 'title', 'text',) |
forms モジュールの ModelForm クラスを継承した DiaryForm を作成していますね。
Diary を model というフィールド変数に設定し、実際に入力フォームで利用するフィールドを、fields 変数にタプル形式で与えています。
fields は、入力フォームで利用するカラム名を渡しますが、逆にフォームで利用しないカラム名を渡すことも可能です。
それが、exclude フィールドです。
例えば、今回の DiaryForm の例だと、
1 | exclude = ('id', 'created_at', 'upadted_at',) |
と記述しても OK です。
Viewの実装
次に、日記の投稿画面に対応するビューを実装しましょう!
フォームを伴った画面作成用に、便利なビュークラスがあります。
それが CreateView です。
以下のコードを、diary ディレクトリの views.py に追記しましょう。
1 2 3 4 | class DiaryCreateView(CreateView): template_name = 'diary_create.html' form_class = DiaryForm success_url = reverse_lazy('diary:diary_create_complete') |
DiaryCreateView には、3つのフィールドが定義されていますね!
template_name は、DiaryCreateView に紐づくテンプレートを表しています(diary_create.html は後ほど作成します)。
form_class というフィールド変数は重要です。
この変数に DiaryForm を渡すことによって、diary_create.html 内部で、DiaryForm を用いた入力フォームを作成することができます。
success_url には、入力フォームを投稿した後に、遷移する url を指定します。
一見すると、diary アプリの「diary_create_complete」という名前のビューを呼び出しているようですが、 reverse_lazy() という見慣れないメソッドが使われています。
reverse_lazy() は、url を遅延評価するメソッドです。
「diary:diary_create_complete」という書き方は、urls.py の情報を利用した url の指定方法です。
Django プロジェクト起動の際には、urls.py の評価よりも、ビュークラスの評価が先に行われます。
ですので、reverse_lazy で url の遅延評価を行わなければ url の解決ができず、プロジェクトの起動ができません。
最後に、投稿ボタンを押した後の画面に対応するビューを、TemplateView を用いて実装しましょう。
投稿後に表示するテンプレートは、「diary_create_complete.html」とします。
1 2 | class DiaryCreateCompleteView(TemplateView): template_name = 'diary_create_complete.html' |
第1回、第2回と続けて見て下さっている方は、第2回に作成した IndexView を含めて、合計3つのビューが実装されているはずです。
テンプレートの作成
では、テンプレートの作成に移りましょう!
日記投稿画面と、日記投稿完了画面の2つのテンプレートを作成します。
日記投稿画面は、以下のようなテンプレートとします。
テンプレート名は、「diary_create.html」としましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>MyDiaryApp</title> </head> <body> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">投稿</button> </form> </body> </html> |
post リクエストを送信している form タグに着目をして下さい。
action 属性が設定されていません。
DiaryCreateView の success_url 属性に指定した url に、自動で action を起こします。
また、form タグの中には、他にも見慣れない記述が2つあります。
{% csrf_token %}という記述
1つ目が {% csrf_token %} という記述です。
これは Django において、POST リクエストを送信するときには必須の記述で、クロスサイトリクエストフォージェリを防ぐ仕組みです。
なお、 { } という記法は、Django テンプレート言語と呼ばれているものです(今後の記事で詳しく説明します)。
{{ form.as_p }}という記述
もう1つ、 {{ form.as_p }}という記述があります。
DiaryCreateView の form_class 属性と結びついており、form は DiaryForm のインスタンスを意味しています。
また、as_p という記述は、form の各要素を p タグで囲むという意味になります。
続いて投稿完了画面を作りましょう。
以下のようなテンプレートを作成して下さい。
テンプレート名は、「diary_create_complete.html」とします。
1 2 3 4 5 6 7 8 9 10 11 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>MyDiaryApp</title> </head> <body> <p>日記の投稿が完了しました!</p> <a href="{% url 'diary:index' %}">トップへ戻る</a> </body> </html> |
このテンプレート内の a タグに着目してください。
href がしている url が特徴的です。
これも、Django テンプレート言語の書き方です。
今回の記述だと、「diary アプリの index という名前のビューに対応する url」が、最終的に埋め込まれます。
つまり、トップページへの url です。
urls.pyの編集
ビューを2つ作成したので、urls.py を編集します。
diary ディレクトリ内の urls.py に対して、以下のような追記を行いましょう。
1 2 3 4 5 6 7 8 9 | from django.urls import path from . import views app_name = 'diary' urlpatterns = [ path('index/', views.IndexView.as_view(), name='index'), path('diary/create/', views.DiaryCreateView.as_view(), name='diary_create'), path('diary/create/complete/', views.DiaryCreateCompleteView.as_view(), name='diary_create_complete'), ] |
上記では、以下を記述しています。
- 日記の投稿
- 投稿の完了に対応する url
- ビュー
- ビューの逆引き名
マイグレーションの実施
マイグレーションとは、データベースの定義を自動で行うことです。
Django では、model.py に定義されたモデルを利用して、データベース内部のテーブルを自動作成します。
以下のコマンドを実行してみましょう。
1 | python3 manage.py makemigrations |
コマンドライン上に、以下のような出力が発生したと思います。
1 2 3 | Migrations for 'diary': diary/migrations/0001_initial.py - Create model Diary |
上記コマンドを実行すると、まず mydiaryproject 直下に、「db.sqlite3」というファイルができています。
これが SQLite です。
またdiaryアプリ内に、「migrations ディレクトリ」が新規で作成されていると思います。
その中に、「0001_initial.py」というファイルができています。
これが、マイグレーションファイルと呼ばれているもので、テーブル定義に関する情報を持っています。
マイグレーションファイルの中身
マイグレーションファイルの中身をのぞいてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # Generated by Django 3.1.2 on 2020-11-13 15:10 from django.db import migrations, models import django.utils.timezone import uuid class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Diary', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('date', models.DateField(default=django.utils.timezone.now, verbose_name='日付')), ('title', models.CharField(max_length=40, verbose_name='タイトル')), ('text', models.CharField(max_length=200, verbose_name='本文')), ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')), ('update_at', models.DateTimeField(blank=True, null=True, verbose_name='編集日時')), ], ), ] |
models.py の Diary モデルと同じような情報が並び、これだけだとファイルの存在意義が分かりにくいですね…。
ですが models.pyとは別に、テーブル用のファイルが存在することで、データベースのロールバックが可能になります。
0001というように番号が付与されているのは、そのようなデータベースのヒストリーの保存のためです。
では、このマイグレーションファイルを元に、マイグレーション処理を実行していきたいと思います。
設定ファイルの編集
その前に、1つだけ設定ファイルに編集を加えます。
config ディレクトリ内部の setting.py より、下記のような箇所を見つけて下さい。
1 2 3 4 5 6 7 8 | INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] |
INSTALLED_APPS は、マイグレーションを実行するときに、参照されるリストです。
データベースを必要とするアプリケーションは、上記リストにアプリ名を記載します。
上記リストに、diary を追加しましょう
1 2 3 4 5 | INSTALLED_APPS = [ 〜省略〜 'django.contrib.staticfiles', 'diary', # 追加 ] |
いよいよマイグレーションを実行!
では、マイグレーションを実行する、下記のコマンドを実行して下さい。
1 | python3 manage.py migrate |
以下のような出力が発生したと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Operations to perform: Apply all migrations: admin, auth, contenttypes, diary, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying diary.0001_initial... OK Applying sessions.0001_initial... OK |
admin や auth など、Django プロジェクトが標準で備えているモデルに加えて、diary というモデルに関してもマイグレーション処理がなされていることが分かります。
Django プロジェクトを起動して確認
では、Django プロジェクトを起動しましょう!
1 | python3 manage.py runserver |
「http://127.0.0.1:8000/index/ 」にアクセスすると、以下のような画面が出てくるはずです。

また「日記の投稿」リンクをクリックすると、日記投稿フォーム画面に遷移します。
実際に、日記の投稿をしてみましょう。

投稿ボタンをクリックすると、以下のような画面に遷移します。

「トップへ戻る」ボタンをクリックすると、トップ画面(index/)に戻ります。
第4回へつづく!
今回は、日記の投稿フォームを作成しました。
以下の5つがポイントです。
- テーブル定義を行う Model クラス
- Model を利用したフォーム定義を行う ModelForm クラス
- フォームを利用したビューを簡単に実装する CreateView クラス
- Django テンプレート言語を用いた url の記述方法
- マイグレーション実行に必要なコマンド
次回は、日記の一覧画面と詳細画面を作成していきます。
これらの画面も、Django の便利な View クラスを利用すれば、簡単に実装できてしまいます。
次回もお楽しみに!
第4回はこちら!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン