Djangoレッスン(2) モデルを使おう

[前回](https://qiita.com/youichiro/items/51d36fd8c05fae170a21)はDjangoプロジェクトの作成から、自分の書いたHTMLファイルをブラウザで表示するところまで行いました。今回はDjangoのアーキテクチャ(全体像)を説明し、「モデル」と呼ばれるデータを管理する機能について解説します。

## Djangoのアーキテクチャ
前回はあらかじめ用意したHTMLファイルを表示するのみでしたが、実際のアプリケーションでは条件に応じて表示するものを出し分けることが要求されます。HTMLファイルのように内容が変わらない情報のことを`静的コンテンツ`と呼ぶのに対し、ユーザのリクエストに応じて内容が変わる情報を`動的コンテンツ`と呼びます。動的コンテンツを配信する仕組みをこれから作っていくわけです。

多くのアプリケーションでは、情報を`データベース`と呼ばれるデータの保管場所に保存し、必要に応じてデータを取り出して利用します。HTMLにはデータベースを操作する機能がないため、webページとデータベースを仲介する機能が必要になります。

<img width="1226" alt="スクリーンショット 2019-10-28 0.46.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/40b13f25-bab4-e040-1e4f-a8e03b5ede83.png">

この仕組みをDjangoでは、`モデル` `テンプレート` `ビュー` `URLディスパッチャ`というコンポーネントに分割して実現します。ユーザのリクエストを受け取ってからレスポンスを返す流れを説明します。

<img width="1434" alt="スクリーンショット 2019-10-28 0.47.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/8217e944-2ce4-fd28-7471-17b2a56dd464.png">

- ユーザからのリクエストをURLディスパチャが受け取り、その情報をビューに渡す
- ビューは、リクエストの内容に応じてモデルを呼び出す
- モデルは、ビューからの命令に従って、データベースに接続し、データの挿入/抽出及びデータの整形を行い結果をビューに返す
- ビューはその結果をテンプレートに渡す
- テンプレートは渡されたデータを所定の枠にはめ込むことでページを作成し、URLディスパッチャに返す
- URLディスパッチャは出来たページをユーザにレスポンスする

ざっくり言うと、モデルはデータを管理する担当、テンプレートはページを作成する担当、ビューはモデルとテンプレートを繋げる担当です。このようなDjangoのアーキテクチャは、Model、Template、Viewの頭文字をとって`MTVモデル`と呼ばれます。


## メモアプリ
このチュートリアルではメモアプリを作ることを最初の目標にして進めていきます。メモアプリに必要な以下の機能を実装します。

- メモを作成する
- メモを一覧表示する
- メモの詳細を表示する
- メモを編集する
- メモを削除する

今回はまずメモデータを保存するためのモデルを作成します。

## モデルの定義
`memo_app/models.py`を編集します。

```python:memo_app/models.py
from django.db import models

class Memo(models.Model):
    id = models.AutoField(primary_key=True)
    text = models.TextField(null=False)

    class Meta:
        db_table = 'memos'
```

`Memo`クラスを定義しました。`models.Model`を継承することでDjangoのモデルとして機能するようになります。このクラスを`Memoモデル`と呼ぶことにしましょう。このモデルは`id`と`text`の2つの属性を持ちます。”Memo”は”id”と”text”の値を持っている、という概念を作ると言ってもいいでしょう。Memoモデルに属する1つのデータをMemoモデルの`オブジェクト`と呼びます。ある1つのオブジェクトはidとtextの2つの値を持つことになります。
下図に名称をまとめてみました。定義したモデルに従ってデータベースにテーブルが作成されるため、対応関係も載せました。
<img width="1435" alt="スクリーンショット 2019-10-28 2.10.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/cb668bbb-0662-3f0e-81ee-ac12bfb32e97.png">

idはMemoモデルの中の1つ1つのデータに対して振られる連番のことです。`id = models.AutoField(primary_ley=True)`はどんなモデルにも付けるのでテンプレだと思ってください。こう定義することで勝手にidを振ってくれます。

textはメモの本文である文字列データのことです。`TextField`にすることで(長い)文字列として扱ってくれます。`null=False`はtextの値を空白にすることを禁止するオプションです。

下の`db_table='memos'`ではデータベースにテーブルを作成する際のテーブル名を指定しています。指定しなくてもテーブル名は自動で付きます(が少々気に入らないのでいつも指定しています)。

models.AutoFieldやmodels.TextFieldのようなフィールドや、(null=False)のようなフィールドオプションは他にもまだあるので、必要に応じて調べて使うようにしましょう。

[Django: モデルフィールドリファレンスの一覧 - Qiita](https://qiita.com/nachashin/items/f768f0d437e0042dd4b3)


## モデルの作成
`memo_app/models.py`のモデル定義に従って、データベースにテーブルを作成します。データベースを操作する必要はなく、Djangoが提供するコマンドを打つことで行います。2ステップあります。

**マイグレーションファイルの生成**

```bash
$ python manage.py makemigrations

Migrations for 'memo_app':
  memo_app/migrations/0001_initial.py
    - Create model Memo
```

このコマンドを実行すると、モデルファイルが読み込まれ、`マイグレーションファイル`というファイルが生成されます。今、`memo_app/migrations/0001_initial.py`が生成されました。
この時点ではまだデータベースは変更されていません。次のコマンドを打つと変更されます。

**マイグレート**

```bash
$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, memo_app, 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 memo_app.0001_initial... OK
  Applying sessions.0001_initial... OK
```

このコマンドを打つことで、マイグレーションファイルの内容を実際にデータベースに反映させます。最初の実行時にはDjangoがデフォルトで提供するユーザや権限などに関する内容も反映されます。

## 管理画面
Memoモデルが作成されたことを確認する方法はいくつかあるのですが、管理画面を見るのが分かりやすいかなと思います。

`memo_app/admin.py`を編集します。

```python:memo_app/admin.py
from django.contrib import admin
from .models import Memo

admin.site.register(Memo)
```

サーバを起動してブラウザで`localhost:8000/admin`を開くと、ログイン画面が出てきます。

```bash
$ python manage.py runserver
```

まだユーザを作っていないのでログインできません。管理者用ユーザを作成しましょう。一回サーバを停止して以下のコマンドを打ちます。ユーザ名、メールアドレス、パスワードを入力しましょう。

```bash
$ python manage.py createsuperuser
# ユーザ名、メールアドレス、パスワードを入力する
```

再度サーバを起動し、ユーザ名とパスワードを入力してログインしましょう。

<img width="958" alt="スクリーンショット 2019-10-28 1.50.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/03ea98ad-2a9f-e0b7-09cb-7fb5c311466f.png">

これが管理画面です。Djangoが勝手に作ってくれています。`Memos`がadmin.pyで登録したMemoモデルです。Memosをクリックし、'MEMOを追加'ボタンを押すとフォーム画面が出てきます。Textフォームに適当に文字を入力して保存すると、オブジェクトが作成されます。編集や削除もできます。試しにオブジェクトを3つくらい作成しておきましょう。

## メモ一覧ページの作成
では、Memoモデルに保存されているオブジェクトの一覧を表示するページを作成していきましょう。まず、`memo_app/views.py`に新たな関数を追加します。

```python:memo_app/views.py
from django.shortcuts import render
from .models import Memo

def index(request):
    return render(request, 'index.html', {'text': 'This page is a memo_app/index'})

# ↓追加
def list_view(request):
    return render(request, 'memo_list.html', {'memos': Memo.objects.all()})
```

render関数は前回の記事で出てきましたね。第2引数にテンプレート、第3引数にテンプレートに渡す辞書を指定します。
`Memo.objects.all()`は`Memoモデルから全てのオブジェクトを取得する`ことを意味します。この書き方は後ほど詳しく見ることにして、保存してあるオブジェクトのリストが返されると思ってください。

次に`memo_list.html`を作成します。

```html:memo_app/templates/memo_list.html
<!DOCTYPE html>
<html lang=“ja”>
<head>
  <meta charset=“utf-8”>
  <title>memo_app</title>
</head>
<body>
  <h1>Memo List Page</h1>
  {% for memo in memos %}
      <p>{{ memo.id }} {{ memo.text }}</p>
  {% endfor %}
</body>
</html>
```

このテンプレートではビューから`memos`という変数名でMemoモデルのオブジェクトリストを受け取っています。これを表示するのですが、Djangoにはhtmlファイルでpython風なコードを書くことができる`テンプレートエンジン`([Jinja2](https://palletsprojects.com/p/jinja/))という機能が備わっています。ただしpythonのようにいろいろな処理はできず、決められた書き方に従う必要があるので注意です。memosはリスト(正確にはQuerySet)なのでfor文を回し、各オブジェクトのidとtextの値をpタグで表示します。

最後に`memo_app/urls.py`でurlを定義します。

```python:memo_app/urls.py
from django.urls import path
from . import views

app_name ='memo_app'
urlpatterns = [
    path('index/', views.index, name='index'),
    path('memo_list/', views.list_view, name='memo_list')  # 追加
]
```

サーバを起動し、`localhost:8000/memo_app/memo_list`を開いてみましょう。一覧で表示されたでしょうか。

<img width="719" alt="スクリーンショット 2019-10-28 2.20.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/82750a8a-58ec-fe2f-863f-e0a41e664faa.png">


## データベース操作
データベースにデータを挿入したり抽出したりするためには、本来はSQLという言語を使う必要があります。しかし開発する際にSQLは扱いづらいため、データベースの操作を簡単に実行できる仕組みがよく導入されます。これを[ORマッパー](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)(Object-relational mapping)と呼びます。
Djangoでも`モデルマネージャー`というORマッパーが用意されています。

ORマッパーを試すために、djangoシェルという機能を使いましょう。以下のコマンドを打つとシェルが起動します。

```bash
$ python manage.py shell
```

ここではpythonのインタラクティブモードと同じようにpythonを実行することができます。試しに以下を実行してみましょう。

<img width="1332" alt="スクリーンショット 2019-10-28 2.59.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/2636b82c-8e14-8c01-795c-6d108f4b405e.png">

In[1]ではMemoモデルをインポートしています。
In[2]はviews.pyで書きましたね。`.all()`は全てのオブジェクトが返ってきます。`.objects`は決まり文句ですので気にしなくてOKです。返り値がQuerySetになっていますが、とりあえずはリストだと思ってもらって大丈夫です。
In[3]ではリストのように[0]で1つ目の要素を取り出せていますね。各要素はオブジェクトなので、`オブジェクト.属性`で各値を取り出すことができます(In[4], In[5])。
In[6]は`.first()`で一番最初の要素を取り出しています。これはIn[3]と同じ結果になります。

<img width="1283" alt="スクリーンショット 2019-10-28 3.04.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/bd7a5491-1390-38d4-862b-d05a651409aa.png">

ある条件を満たすオブジェクトのみを抽出したい時は、`.filter(属性=value)`を使います。
In[8]ではidが3であるオブジェクトを取得しています。結果がQuerySetであることに要注意です。QuerySetはリストのことなので、In[9]のように`リスト.属性`ではエラーになります。In[10]では`.first()`で最初の要素を取り出すことで`オブジェクト.属性`を実行することができます。

他にも様々な操作ができるので、ぜひいろいろ試してみてください。
[クエリを作成する | Django ドキュメント | Django](https://docs.djangoproject.com/ja/2.2/topics/db/queries/)
[QuerySet API reference | Django ドキュメント | Django](https://docs.djangoproject.com/ja/2.2/ref/models/querysets/#queryset-api)



---
今回は最初にDjangoのアーキテクチャ(全体像)を説明し、モデルの作成、管理画面の表示、モデルオブジェクトの表示を行いました。次回はモデルオブジェクトの追加、編集、削除を行うページの作成を行います。

```
今回のキーワード:
  - MTVモデル(モデル、テンプレート、ビュー)
  - モデルの属性とオブジェクト
  - マイグレート
  - 管理画面
  - テンプレートエンジン
  - ORマッパー
```