TL;DR
素人基盤エンジニアがDockerでDjangoを触るシリーズ①:DockerでDjangoを作るの続き。
前回作ったdjangoにモデルを作成していく。
Django Girlsというdjangoのチュートリアルを掲載しているサイトがあるので、今回はそれの「Djangoモデル」以降の章に沿ってblogのサンプルを作成してみながら、djangoと戯れる。
(このサイトはとてもわかりやすくて、正直この投稿見るよりはそちらのほうがいいかもしれないが、あくまで素人が触ったときの学習の過程と調べたことを公開することで同じようなレベルの人の理解促進を狙って書いている。)
Djangoの設計方針
Model - Template - View
DjangoはMTV(モデル・テンプレート・ビュー)という考え方で設計されているらしい。MVC(モデル・ビュー・コントローラ)なら(名前だけは)聞いたことあるが。。
ひとつずつ確認していく。
Model
モデルは、データベースとのやり取りを行う。
DjangoGirlsのblogの例では、オブジェクト指向について言及した後、blogに必要なデータとして下記を挙げている。
Post
--------
title
text
author
created_date
published_date
このような、必要になるであろうデータ形式をモデルとして定義しておいて、リクエストが来たら必要なデータをDBから取得して返す。
これはMVCにおけるModelの役割とほぼ同じだと思われる。(多分)
Template
Djangoにおけるテンプレートは、見栄えの部分を指す。
htmlや、それを補完するためのCSSやjavascriptがそれにあたる。
MVCでいうと、Viewの役割。ややこしい。。
MVCではControllerがModelとViewの橋渡しをするのと同様で、MTVにおけるTemplateは直接Modelからデータを取得することはない。必ず、Viewを通してデータを扱う。
View
Templateでも少し触れたが、ViewではModelを使って取得した情報をどう見せるかという部分を記述する。その上で、関数化しTemplateとの紐づけを行う。
つまり、役割としては、
①Modelに対しデータ取得の命令を出す
②ユーザのリクエストに返すTemplateを決定する
③TemplateにModelから取得したデータを関数化して渡す
という3つがある。(だいぶ自信がないが間違えていたら直すので指摘ください。。)
ユーザがdjangoでできたwebサイトを訪問した際、まずはurls.pyの記述に従ってどのView関数で処理を行うかが決定し、選択されたView関数はModelを使いデータを取得し、選択したテンプレートにデータを渡した上でwebページを表示させる。
なんとなくのイメージは下記のような感じだろうか
U ⇒ urls.py ⇒ view.py ⇔ Model ⇔ DB
S ⇓
E ⇓
R ⇐ Templates
この3つの役割をふんわりと意識した上で、構築に進む。
構築
①アプリケーションのひな型作成
manage.py startapp
docker-compose run web python manage.py startapp blog
前回作成したdocker-compose.yamlを利用して、ローカル環境にアプリケーションのひな型を作成する。前回はweb serverを起動するためのrunserverに利用したmanage.pyだが、startappとするとモデルを作るために必要なファイル群を作成してくれる。
前回の実行分を含めて、このコマンドを実行した後のディレクトリ構成は下記のようになっているはず。
.
∟ blog
∟ admin.py
∟ apps.py
∟ __init__.py
∟ migrations
∟ models.py
∟ tests.py
∟ views.py
∟ composeexample
∟ __init__.py
∟ __pycache__
∟ settings.py
∟ urls.py
∟ wsgi.py
∟ docker-compose.yaml
∟ Dockerfile
∟ manage.py
∟ migrations
∟ requirements.txt
前回同様、linuxマシンで実行した場合はフォルダの権限がrootになるので、下記を実行する。
sudo chown -R $USER:$USER .
②settings.pyによる紐づけ
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
INSTALLED_APPSに今回作成したアプリケーションを追記する。
③モデル作成
####models.py
from django.conf import settings
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
モデルの定義を作成する。startappで作成されたblogフォルダの中に、models.pyがあるのでそれを編集する。最初からあるのは二行目の from django.db import models
のみ、あとはDjango Girlsのサンプルを拝借している。
####よくわかる解説
from django.conf import settings
project/settings.py
の変数を参照する際に利用するモジュール。
from django.db import models
modelに関する様々なメソッドを提供するモジュール。
外部キーとか、データ型とか。
from django.utils import timezone
タイムゾーンに関するモジュール。いろんな言語で提供されているユーティリティのものとまぁだいたいおなじもの。今回は投稿時の時間を取得するために使っている。
timezoneの設定はsettings.pyで行うので、何もしていない現状ではUTCになるはず。
class Post(models.Model):
Postというモデルを定義している部分。
定義するモデルは、django.db.models.Modelを継承して作成する。
さらに子クラスを作成することも可能。例えばcreate_dateは汎用的に他のモデルでも使う可能性があるので、create_dateデータを定義しているTimestampクラスを作成しておいて、Post(Timestamp)という形で継承して利用する等。
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ForeignKey()は、外部キーとなるモデルを定義する。今回は、djangoがデフォルトで持っているUserモデルを外部キーとして指定するためsettingsモジュールを利用してMODEL定義を取得しているが、普通に自分が作成したモデルを外部キーとして利用する場合はモデル名を指定する。on_deleteオプションはdjango2.0以降では必須となっており、外部キーのオブジェクトを削除した際の挙動を指定している。CASCADEは、外部キーのオブジェクトを削除すると紐づくオブジェクトをすべて削除する。
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
blogを構成するそれぞれのデータ型と制約を指定する。created_dateは、オブジェクトが生成された時間が入力されるようになっている。つまり、記事が書かれた時間。それに対し、published_dateは現時点では空欄になっており、後述するpublishメソッド内で値が入るようになっている。
def publish(self):
self.published_date = timezone.now()
self.save()
blog記事を公開するメソッド。公開した時間であるpublished_dateに今の時間を代入し、save()メソッドを呼び出している。save()メソッドなど書いた覚えがないので、もちろん継承元のmodels.Model内のメソッドで、テーブルに主キーが存在する場合はUPDATEで存在しない場合はCREATEをする。
def __str__(self):
return self.title
magicメソッド。(magicメソッドについては本筋ではないので各自調べてください。)インスタンス化したときにblog記事のタイトルがリターンされるようにしている。
④データベースのマイグレーション
djangoでは、models.pyの内容で接続しているデータベースにテーブルを作成することをマイグレーションと呼ぶらしい。
manage.py makemigrations
というコマンドを実行すると、マイグレーションのためのファイルが作成され、manage.py migrate
というコマンドで実際にデータベース上にテーブルを作成する。
早速docker-compose exec
でwebサーバに入ってコマンドを実行し、dbサーバに入ってテーブルができているか確認してみる。(これらの操作は後々docker-compose内のcommandに追記して、docker-compose up
でマイグレーションまで実施されるようにするが、今は確認のためコンテナに入ってコマンド実行をしている)
docker-compose exec web bash
(以下webコンテナ内)
manage.py makemigrations blog
下記のようなoutputが出る。
blog/migrations/0001_initial.py
- Create model Post
アプリケーションのディレクトリ下のmigrationsに連番でマイグレーションファイルが作成され、2番目以降は差分が入るらしい。
manaeg.py migrate blog
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying blog.0001_initial... OK
上のように出力されたら、マイグレーションは完了している。exitでwebサーバを抜けてdbサーバに入って確認してみる。
docker-compose exec db bash
(以下dbコンテナ内)
psql -U postgres
\d
List of relations
Schema | Name | Type | Owner
--------+-----------------------------------+----------+----------
public | auth_group | table | postgres
public | auth_group_id_seq | sequence | postgres
public | auth_group_permissions | table | postgres
public | auth_group_permissions_id_seq | sequence | postgres
public | auth_permission | table | postgres
public | auth_permission_id_seq | sequence | postgres
public | auth_user | table | postgres
public | auth_user_groups | table | postgres
public | auth_user_groups_id_seq | sequence | postgres
public | auth_user_id_seq | sequence | postgres
public | auth_user_user_permissions | table | postgres
public | auth_user_user_permissions_id_seq | sequence | postgres
public | blog_post | table | postgres
public | blog_post_id_seq | sequence | postgres
public | django_content_type | table | postgres
public | django_content_type_id_seq | sequence | postgres
public | django_migrations | table | postgres
public | django_migrations_id_seq | sequence | postgres
テーブルがたくさんできている。
authなんちゃらとdjangoなんちゃらはおそらくデフォルトで作成されるものだろう(多分)。
models.pyに書いたのはblog_postなので、blog_postテーブルの定義を確認する。
\d blog_post
Table "public.blog_post"
Column | Type | Collation | Nullable | Default
----------------+--------------------------+-----------+----------+---------------------------------------
id | integer | | not null | nextval('blog_post_id_seq'::regclass)
title | character varying(200) | | not null |
text | text | | not null |
created_date | timestamp with time zone | | not null |
published_date | timestamp with time zone | | |
author_id | integer | | not null |
Indexes:
"blog_post_pkey" PRIMARY KEY, btree (id)
"blog_post_author_id_dd7a8485" btree (author_id)
Foreign-key constraints:
"blog_post_author_id_dd7a8485_fk_auth_user_id" FOREIGN KEY (author_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
idは、djangoがモデルを定義する際、デフォルトで付与する連番で、プライマリキーになる。その他はmodels.pyで定義した通りになっている。これで、モデルの準備は完了。
ようやく2歩目といった感じだが、めげずにやっていこう。。