【第3回】Djangoで日記アプリを作ろう~投稿フォーム編~
エンジニアになろう!
【第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」の中から、以下のような箇所を探してみて下さい。
1DATABASES = {
2 'default': {
3 'ENGINE': 'django.db.backends.sqlite3',
4 'NAME': BASE_DIR / 'db.sqlite3',
5 }
6}
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 に、以下のコードを記述しましょう。
1from django.db import models
2from django.utils import timezone
3import uuid
4
5
6class Diary(models.Model):
7 id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
8 date = models.DateField(verbose_name='日付', default=timezone.now)
9 title = models.CharField(verbose_name='タイトル', max_length=40)
10 text = models.CharField(verbose_name='本文', max_length=200)
11 created_at = models.DateTimeField(verbose_name='作成日時', default=timezone.now)
12 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 コマンド実行時には作成されません。
1from django import forms
2from .models import Diary
3
4
5class DiaryForm(forms.ModelForm):
6 class Meta:
7 model = Diary
8 fields = ('date', 'title', 'text',)
forms モジュールの ModelForm クラスを継承した DiaryForm を作成していますね。
Diary を model というフィールド変数に設定し、実際に入力フォームで利用するフィールドを、fields 変数にタプル形式で与えています。
fields は、入力フォームで利用するカラム名を渡しますが、逆にフォームで利用しないカラム名を渡すことも可能です。
それが、exclude フィールドです。
例えば、今回の DiaryForm の例だと、
1exclude = ('id', 'created_at', 'upadted_at',)
と記述しても OK です。
Viewの実装
次に、日記の投稿画面に対応するビューを実装しましょう!
フォームを伴った画面作成用に、便利なビュークラスがあります。
それが CreateView です。
以下のコードを、diary ディレクトリの views.py に追記しましょう。
1class DiaryCreateView(CreateView):
2 template_name = 'diary_create.html'
3 form_class = DiaryForm
4 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」とします。
1class DiaryCreateCompleteView(TemplateView):
2 template_name = 'diary_create_complete.html'
第1回、第2回と続けて見て下さっている方は、第2回に作成した IndexView を含めて、合計3つのビューが実装されているはずです。
テンプレートの作成
では、テンプレートの作成に移りましょう!
日記投稿画面と、日記投稿完了画面の2つのテンプレートを作成します。
日記投稿画面は、以下のようなテンプレートとします。
テンプレート名は、「diary_create.html」としましょう!
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>MyDiaryApp</title>
6</head>
7<body>
8
9<form method="post">
10 {% csrf_token %}
11 {{ form.as_p }}
12 <button type="submit">投稿</button>
13</form>
14
15</body>
16</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<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>MyDiaryApp</title>
6</head>
7<body>
8<p>日記の投稿が完了しました!</p>
9<a href="{% url 'diary:index' %}">トップへ戻る</a>
10</body>
11</html>
このテンプレート内の a タグに着目してください。
href がしている url が特徴的です。
これも、Django テンプレート言語の書き方です。
今回の記述だと、「diary アプリの index という名前のビューに対応する url」が、最終的に埋め込まれます。
つまり、トップページへの url です。
urls.pyの編集
ビューを2つ作成したので、urls.py を編集します。
diary ディレクトリ内の urls.py に対して、以下のような追記を行いましょう。
1from django.urls import path
2from . import views
3
4app_name = 'diary'
5urlpatterns = [
6 path('index/', views.IndexView.as_view(), name='index'),
7 path('diary/create/', views.DiaryCreateView.as_view(), name='diary_create'),
8 path('diary/create/complete/', views.DiaryCreateCompleteView.as_view(), name='diary_create_complete'),
9]
上記では、以下を記述しています。
- 日記の投稿
- 投稿の完了に対応する url
- ビュー
- ビューの逆引き名
マイグレーションの実施
マイグレーションとは、データベースの定義を自動で行うことです。
Django では、model.py に定義されたモデルを利用して、データベース内部のテーブルを自動作成します。
以下のコマンドを実行してみましょう。
1python3 manage.py makemigrations
コマンドライン上に、以下のような出力が発生したと思います。
1Migrations for 'diary':
2 diary/migrations/0001_initial.py
3 - Create model Diary
上記コマンドを実行すると、まず mydiaryproject 直下に、「db.sqlite3」というファイルができています。
これが SQLite です。
またdiaryアプリ内に、「migrations ディレクトリ」が新規で作成されていると思います。
その中に、「0001_initial.py」というファイルができています。
これが、マイグレーションファイルと呼ばれているもので、テーブル定義に関する情報を持っています。
マイグレーションファイルの中身
マイグレーションファイルの中身をのぞいてみましょう。
1# Generated by Django 3.1.2 on 2020-11-13 15:10
2
3from django.db import migrations, models
4import django.utils.timezone
5import uuid
6
7
8class Migration(migrations.Migration):
9
10 initial = True
11
12 dependencies = [
13 ]
14
15 operations = [
16 migrations.CreateModel(
17 name='Diary',
18 fields=[
19 ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
20 ('date', models.DateField(default=django.utils.timezone.now, verbose_name='日付')),
21 ('title', models.CharField(max_length=40, verbose_name='タイトル')),
22 ('text', models.CharField(max_length=200, verbose_name='本文')),
23 ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')),
24 ('update_at', models.DateTimeField(blank=True, null=True, verbose_name='編集日時')),
25 ],
26 ),
27 ]
models.py の Diary モデルと同じような情報が並び、これだけだとファイルの存在意義が分かりにくいですね…。
ですが models.pyとは別に、テーブル用のファイルが存在することで、データベースのロールバックが可能になります。
0001というように番号が付与されているのは、そのようなデータベースのヒストリーの保存のためです。
では、このマイグレーションファイルを元に、マイグレーション処理を実行していきたいと思います。
設定ファイルの編集
その前に、1つだけ設定ファイルに編集を加えます。
config ディレクトリ内部の setting.py より、下記のような箇所を見つけて下さい。
1INSTALLED_APPS = [
2 'django.contrib.admin',
3 'django.contrib.auth',
4 'django.contrib.contenttypes',
5 'django.contrib.sessions',
6 'django.contrib.messages',
7 'django.contrib.staticfiles',
8]
INSTALLED_APPS は、マイグレーションを実行するときに、参照されるリストです。
データベースを必要とするアプリケーションは、上記リストにアプリ名を記載します。
上記リストに、diary を追加しましょう
1INSTALLED_APPS = [
2 〜省略〜
3 'django.contrib.staticfiles',
4 'diary', # 追加
5]
いよいよマイグレーションを実行!
では、マイグレーションを実行する、下記のコマンドを実行して下さい。
1python3 manage.py migrate
以下のような出力が発生したと思います。
1Operations to perform:
2 Apply all migrations: admin, auth, contenttypes, diary, sessions
3Running migrations:
4 Applying contenttypes.0001_initial... OK
5 Applying auth.0001_initial... OK
6 Applying admin.0001_initial... OK
7 Applying admin.0002_logentry_remove_auto_add... OK
8 Applying admin.0003_logentry_add_action_flag_choices... OK
9 Applying contenttypes.0002_remove_content_type_name... OK
10 Applying auth.0002_alter_permission_name_max_length... OK
11 Applying auth.0003_alter_user_email_max_length... OK
12 Applying auth.0004_alter_user_username_opts... OK
13 Applying auth.0005_alter_user_last_login_null... OK
14 Applying auth.0006_require_contenttypes_0002... OK
15 Applying auth.0007_alter_validators_add_error_messages... OK
16 Applying auth.0008_alter_user_username_max_length... OK
17 Applying auth.0009_alter_user_last_name_max_length... OK
18 Applying auth.0010_alter_group_name_max_length... OK
19 Applying auth.0011_update_proxy_permissions... OK
20 Applying auth.0012_alter_user_first_name_max_length... OK
21 Applying diary.0001_initial... OK
22 Applying sessions.0001_initial... OK
admin や auth など、Django プロジェクトが標準で備えているモデルに加えて、diary というモデルに関してもマイグレーション処理がなされていることが分かります。
Django プロジェクトを起動して確認
では、Django プロジェクトを起動しましょう!
1python3 manage.py runserver
「http://127.0.0.1:8000/index/ 」にアクセスすると、以下のような画面が出てくるはずです。
また「日記の投稿」リンクをクリックすると、日記投稿フォーム画面に遷移します。
実際に、日記の投稿をしてみましょう。
投稿ボタンをクリックすると、以下のような画面に遷移します。
「トップへ戻る」ボタンをクリックすると、トップ画面(index/)に戻ります。
第4回へつづく!
今回は、日記の投稿フォームを作成しました。
以下の5つがポイントです。
- テーブル定義を行う Model クラス
- Model を利用したフォーム定義を行う ModelForm クラス
- フォームを利用したビューを簡単に実装する CreateView クラス
- Django テンプレート言語を用いた url の記述方法
- マイグレーション実行に必要なコマンド
次回は、日記の一覧画面と詳細画面を作成していきます。
これらの画面も、Django の便利な View クラスを利用すれば、簡単に実装できてしまいます。
次回もお楽しみに!
第4回はこちら!
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit