Django2日目です。今日の作業で、この動画の40分の部分まで進むことができました。
しかし、これではまだ「動画の言うとおりにしたら、とりあえず動いた」だけの状態です。完璧主義は良くないと思いますが、せめて「今日の自分は何をやったのか」くらいは覚えておきたいです。
なので、今日行った作業を復習して、自分のものにしたいと思います。
1. 今日やったこと
今日取り組んだのは、日記アプリの日記のデータを投稿することができるようにする作業でした。
ユーザーがデータを投稿するためのフォーマットを作成し、投稿されたデータをアプリケーションが受け取れるようにするためのインフラを整える作業を行いました。
具体的な作業内容を記載していきます。
Pythonのデータを画面に表示させる
まずは、htmlファイルにPythonのデータを埋め込んで、画面上に表示させる方法を学びました。
具体的には、日記アプリのトップページに、東京の現在時刻を表示させます。
Pythonのデータを表示させたい箇所、今回はtemplates/diary/index.htmlの任意の場所に、以下のコードを追加しました。
<p>{{ datetime_now }}</p>
この二重の波括弧でくくったdatetime_now変数は、HTML側の変数なのだそうです。ここに、Python側のデータを埋め込むことができます。このような機能をテンプレートと言います。
二重波括弧とHTML変数の間には、半角スペースを入れておきます。
続いて、リクエストを受け取りレスポンスを返す役割を持っている、diary/views.pyに、現在の日時を取得するためのコードを追加します。
from datetime import datetime
from zoneinfo import ZoneInfo
class IndexView(View):
def get(self, request):
datetime_now = datetime.now(
ZoneInfo("Asia/Tokyo")
).strftime("%Y年%m月%d日 %H:%M:%S")
return render(
request, "diary/index.html", {"datetime_now": datetime_now}
)
現在時刻とタイムゾーンを取得するためのdatetimeクラスとZoeInfoクラスをインポート、IndexViewクラスのgetメソッドの中で、datetime_now変数を定義し、戻り値のrenderオブジェクトの第3引数に辞書を追加しました。
辞書のキーにHTML側の変数、バリューにPython側の変数を指定することで、HTML変数に、Python変数の値を渡すことができます。
最大のポイントは、この第3引数によって、HTML変数にPythonのデータを渡すことができている点でしょう。これによって、アプリの表示画面にPythonの要素を表示することが可能になるのです。
この現在日時は、リクエストが送られてレスポンスを返すViewの中のgetメソッドで取得されているため、リクエストを送り直す・画面を更新する度に日時が更新される事になります。
マイグレーション
続いて、マイグレーションを行いました。
マイグレーションとは、Pythonで書かれたモデル(models.py)の内容を、SQLを直接書かずにデータベースの構造に自動的に反映させる機能なのだそうです。
具体的には、ユーザーが日記のデータを投稿できるようにするために、アプリが日記データをデータベースに保存することができるようにしました。
まず、diary/models.pyで、モデルを定義しました。これは恐らく、そのテーブルがどんなカラムを持っているのかを決めているという意味でしょう。ここで定義したモデルを、データベースが受け取れるようにするのがマイグレーションです。
最初に、config/settings.pyスクリプトの中のINSTALLED_APPS変数のリストの中に、diary/apps.py内の自動で作られたDiaryConfigクラスを追加します。
INSTALLED_APPS = [
# 略
"diary.apps.DiaryConfig"
]
これにより、この後行うマイグレーションの際に、このdiaryアプリを参照してくれるようになります。
次にモデルを作成しました。
モデルをクラスで定義するのですが、
- クラス: テーブルに対応
- クラス変数: テーブルのカラムに対応
という形です。
1つの日記に複数のページを投稿する、というイメージで実装します。
1つのページが持っているデータとして、
- ページを識別するID
- ページのタイトル
- ページの本文
- ページの日付
- ページを作成した日時
- ページを更新した日時
以上の項目を定義します。後ろ2つの項目は、後からデータを管理しやすいようにするためのものです。
diary/models.pyスクリプトに、以下のように記述します。
from django.db import models
import uuid
class Page(models.Model):
id = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID"
)
title = models.CharField(max_length=100, verbose_name="タイトル")
body = models.TextField(max_length=2000, verbose_name="本文")
page_date = models.DateField(verbose_name="日付")
create_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
def __str__(self):
return self.title
uuidを日記のページのIDに使用しています。
uuidとは、非常に高い確率で、他の識別子とは被らない識別子を生成する規格です。
今回定義したPageクラスは、django.bd.models.Modelクラスを継承しています。
他のポイントとしては、フィールドのverbose_name引数に、日本語でカラムのタイトルを指定している事です。
さらに、__str__というメソッドを追加しています。戻り値をself.titleとしていますが、これはどのデータかを判断する時に分かりやすいクラス変数を設定しています。
ここまでできたら、マイグレーションを行います。
まずは、コマンドラインで以下のコードを実行しました。
python manage.py makemigrations
こちらは、マイグレーション用のファイルを作成するコマンドです。
これによって、diary/migrations/0001_initial.pyというファイルが自動生成されました。
このファイルの先頭の0001はマイグレーション番号になっています。マイグレーションをコマンドラインで行う時に、このシーケンス番号を指定します。
python manage.py sqlmigrate diary 0001
このコマンドの後ろで、アプリ名、マイグレーション番号を指定しています。
ここから、diaryアプリ用のPageというテーブルを作成するマイグレーションを行います。
python manage.py migrate
これで、Pageのデータを格納するテーブルを作成できました。
新規作成画面
次に、実際に日記のページを投稿するためのフォームを作成しました。
templates/diary/page_form.htmlファイルを作成。
formタグの中に、入力項目とデータを送信するためのボタンを作りました。
<! DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Toshizuの日記</title>
</head>
<body>
<h2>日記を書く</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">保存</button>
</form>
</body>
</html>
formタグのmethodに"post"という文字列を指定。これはリクエストメソッドをPOSTにするための設定です。
その中に{% csrf_token %}を記載します。これは、Webアプリの脆弱性の1つ、CSRF検証を自動で行ってくれるものなので、formタグに入れておきます。
次に入力項目を作ります。{{ form.as_p }}とするだけです。こうすると、自動で入力項目が生成されます。
さらにその下にボタンタグを作って、type="submit"として保存と表示するようにします。
HTMLファイルはこれで完了です。
続いてFormを作ります。これはリクエストで受け取ったデータのバリデーション(値チェック)を行ったり、データの型変換を自動で行ってくれるものです。
まず、diary/forms.pyを作成。以下のように記述します。
from django.forms import ModelForm
from .models import Page
class PageForm(ModelForm):
class Meta:
model = Page
fields = ["title", "body", "page_date"]
継承させる基底クラスのModelFormクラス、先ほど作ったdiary/models.pyのPageクラスをインポートします。
ModelFormクラスを継承したPageFormクラスを作成。これが日記のページ用のフォームクラスです。
このクラスの中にさらにMetaというクラスを作りました。このMetaクラスのクラス変数にmodelにPageクラス、fieldsに、リストでユーザーが入力する項目を文字列で指定します。
次に、データを保存するためのViewを作成しました。
from .forms import PageForm # <- add
# 略
class PageCreateView(View):
def get(self, request):
form = PageForm()
return render(request, "diary/page_form.html", {"form": form})
def post(self, request):
form = PageForm(request.POST)
if form.is_valid():
form.save()
return redirect("diary:index")
return render(request, "diary/page_form.html", {"form": form})
PageCreateViewクラスの、各メソッドの返り値のrenderの第3引数に、PageFormオブジェクトを指定します。これによって、PageFormクラスで定義した入力項目をHTMLに渡して、{{ form.as_p }}に、入力項目が自動で生成されます。
postメソッドは、ユーザーが保存ボタンを押した時に動くメソッドです。
form変数に代入するPageFormクラスの引数にrequest.POSTを渡します。リクエストにはユーザーが入力したデータが含まれているため、このように書くと、ユーザーが入力したデータを持つ、Formクラスのオブジェクトが生成されます。
入力項目が有効であるかどうかのチェックが行われ、okならデータを保存してindexへリダイレクト、無効な値があれば、そのページに留まる、というロジックを組みました。
return redirect("diary:index")は、どこにリダイレクト、つまり自動的に移動するのかを、アプリ名とパス名で指定しています。
このパスの名前indexは、diary/urls.pyの
path("", views.index, name="index")
この、name="index"に紐づいています。
データをデータベースに保存するための処理は、form.save()とするだけです。
データベースを操作しているのに、SQLが出てきません。
このように、プログラム内のオブジェクトを操作することで、出たベースのデータを操作する技術を**ORM(オーアールマッパー)**と言います。
ORMの良いところは、操作が簡単という事だけではなく、データベースの種類が変わったとしても、Pythonのコードを変更しなくても良いことにあります。
そうしたら、diary/urls.pyに新しいパスを追加しました。
# 略
urlpatterns = [
path("", views.index, name="index"),
path("page/create/", views.page_create, name="page_create"
]
最後に、日記アプリのトップページから、日記を投稿するページへのリンクを作っていなかったので、それを作成しました。
<br>
<a href="{% url 'diary:page_create' %}">日記を書く</a>
2. 所感・感想
ひとまず、動くものを作ることができました。
初めてやったばかりなので、分からないことの方が圧倒的に多いです。しかし、こうやってやったことを振り返ってみるだけでも頭の中の整理ができたので、効率が悪くてもやる価値があるのではないかと思っています。
それに、後から見返して復習する時にも役に立ちます。
あと、自分が作ったものがちゃんと思い通りに動いてくれると嬉しいものがありますね。慣れてきたら、個人的に自分のローカルPCで使用するものをビルドしてみるのも、勉強になっていいかもしれないと思いました。