ワイ「Pythonの勉強したいなァ・・・やっぱ最初はTODOやな！」[第二話]

## はじめに

続き物の記事になります。前回の記事を読んでいただくとより流れが掴めるかと思われますが、本記事から読み始めて頂いても問題ありません。

- [ワイ「Pythonの勉強したいなァ・・・Djangoってのがあるんか」](https://qiita.com/Ibuki-Matsumoto/items/649438a3adedc41247c0)

## 環境情報

- MacBook Air (Monterey)
- Docker Compose version v2.6.1

## 導入

俺は高校生探偵、工○新○。

幼馴染で同級生の毛◯蘭と遊園地へ遊びに行って、黒ずくめの男の怪しげな取引現場を目撃した。

<br />

<br />

<br />

ワイ「なんて感じで面白い人生を送りたかったな〜(ﾎｼﾞﾎｼﾞ」

ワイ「さて、前は `Python` + `Django` + `Docker` で環境構築したんやったな 」

ワイ「何作るかな〜」

ワイ「せや、やっぱ最初に作るんは伝家の宝刀 `"TODOアプリ"` やな(ｷﾘｯ」

## 本編・・・の前に(前回を読んでいない場合は飛ばしてください)

<details><summary>前回の反省(長いので折りたたみ)</summary>

ワイ「そういえば、あの後色々調べてたら他のサイトでやってるチュートリアルを見つけたんやが」

ワイ「なんやワイの作ったサンプルと全然見た目ちゃうんよな〜」

ワイ作↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/98c2c51f-75c4-6d28-544a-6681198ebe5f.png)

[djangogirls](https://tutorial.djangogirls.org/ja/django_start_project/)様から引用↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/c4d69825-ed16-fbef-5349-035720453644.png)

ワイ「なんでやろな」

ワイ「ん・・・？」

ワイ「あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛」

[公式サイトのダウンロードページ](https://www.djangoproject.com/download/)より
![スクリーンショット 2022-08-10 10.17.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/ecf27adb-cefe-a29a-2e75-f33ecc3dff31.png)

ワイ氏が書いた`requirements.txt`

```txt:requirements.txt
Django>=1.8,<2.0
psycopg2
```

<font color="Red">＼(^o^)／</font>

ワイ「アホや・・・ちゃんと調べろや自分・・・」

ワイ「しゃあないな、バージョンアップするか」

### アップデート作業

ワイ「まずは`requirements.txt`をちょちょいとな」

ワイ「今回はdjangogirls通りにやってみるか」

```txt:requirements.txt
- Django>=1.8,<2.0
+ Django~=3.2.10
psycopg2
```

ワイ「直したし作り直しやな」

ワイ「ついでにディレクトリも作り直すか、TODO作るしな」

ワイ「前回作ったディレクトリとかも削除するで」

```sh:ターミナル
// 関連ファイルの削除
rm manage.py

// プロジェクトディレクトリの削除
rm -rf composeexample

// コンテナの停止＆削除
docker-compose down

// パッケージ類の再インストール・コンテナ作成
docker-compose build

// Djangoプロジェクトの作成
docker-compose run web django-admin.py startproject config .
```

ワイ「ほんでデータベースの設定やな」

```sh:ターミナル
cd config && vi settings.py

// 設定部分を以下に書き換える
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}

TIME_ZONE = 'Asia/Tokyo'

LANGUAGE_CODE = 'ja'

// コンテナの起動
docker-compose up -d
```

ワイ「今回からは`up -d`でバックグラウンドで起動するで」

ワイ「ほな起動したし確認しよか」

[localhost:8000](localhost:8000)
![スクリーンショット 2022-08-10 10.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/8d17affc-7c4c-5250-7025-0428bf53a33a.png)

ワイ「ええかんじやな！」

</details>

## 本編

ワイ「とりあえずおさらいしよか」

```sh:今のrootディレクトリ構成
-rw-r--r--  1 user  staff   155B  8  5 12:17 Dockerfile
-rw-r--r--  1 user  staff   349B  8  5 12:33 docker-compose.yml
-rwxr-xr-x  1 user  staff   660B  8 10 10:00 manage.py*
-rw-r--r--  1 user  staff    24B  8 10 09:52 requirements.txt
drwxr-xr-x  8 user  staff   256B  8 10 10:02 config/
```

```sh:config内のsettings.pyにおける設定変更
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}

TIME_ZONE = 'Asia/Tokyo'

LANGUAGE_CODE = 'ja'
```

ワイ「こんなもんやな」

ワイ「今回はdjangogirlsとかその辺を見ながらやってくで」

### アプリケーションの作成

ワイ「別ディレクトリに分けて開発していくみたいやな」

ワイ「ほなコマンド打ってくで」

```sh:アプリケーションの作成
docker-compose exec web python manage.py startapp todo

ll                                                     total 32
-rw-r--r--  1 user  staff   155B  8  5 12:17 Dockerfile
drwxr-xr-x  8 user  staff   256B  8 10 11:07 config/
-rw-r--r--  1 user  staff   349B  8  5 12:33 docker-compose.yml
-rwxr-xr-x  1 user  staff   662B  8 10 11:02 manage.py*
-rw-r--r--  1 user  staff    24B  8 10 09:52 requirements.txt
drwxr-xr-x  9 user  staff   288B  8 10 11:07 todo/
```

ワイ「新しく`todo`ディレクトリができたな」

ワイ「なになに・・・作ったアプリを登録せなあかんみたいやな」

ワイ「`config/settings.py`を変更するんか」

```sh:config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo.apps.TodoConfig', // この１行を追記する
]
```

ワイ「これでええみたいやな」

### TODOテーブルの作成

ワイ「どうやらテーブル作る時は`モデル`ってのを定義するみたいやな」

ワイ「なんやLaravelみたいやな」

ワイ「まま、ええわ。ほなやってくで」

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

# Create your models here.


class Todo(models.Model):
    title = models.CharField("名前", max_length=100)
    description = models.TextField("タスク内容", blank=True)

    def __str__(self):
        return self.title
```

ワイ「こんなかんじでええらしいな」

ワイ「モデルについての詳しい話は[公式のドキュメント](https://docs.djangoproject.com/ja/3.2/ref/models/fields/#django.db.models.TextField)読んでな」

#### マイグレーション

ワイ「モデルを定義したらマイグレーションファイルを作るんやな」

```sh:マイグレーションファイルの作成
docker-compose exec web python makemigration todo

Migrations for 'todo':
  todo/migrations/0001_initial.py
    - Create model Todo
```

ワイ「できたみたいやな、簡単や」

ワイ「マイグレーションファイルを作ったってことは・・・」

ワイ「"migrate"やな」

```sh:マイグレーション
docker-compose exec web python migrate todo

Operations to perform:
  Apply all migrations: todo
Running migrations:
  Applying todo.0001_initial... OK
```

#### スーパーユーザの作成

ワイ「作ったテーブル(モデル)を確認したいな」

ワイ「なになに・・・これか」

[Django admin](https://tutorial.djangogirls.org/ja/django_admin/)

ワイ「とりあえず`admin.py`に登録したら見れるらしいな」

```python:todo/admin.py
from django.contrib import admin

from .models import Todo

# Register your models here.
admin.site.register(Todo)
```

ワイ「こうやな」

ワイ「admin言うくらいやしスーパーユーザがいるみたいや」

```sh:superUserの作成
docker-compose exec web python manage.py createsuperuser
```

```sh:エラー文
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.UndefinedTable: relation "auth_user" does not exist
LINE 1: ...user"."is_active", "auth_user"."date_joined" FROM "auth_user...
                                                             ^


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/code/manage.py", line 22, in <module>
    main()
  File "/code/manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 79, in execute
    return super().execute(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 100, in handle
    default_username = get_default_username(database=database)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/management/__init__.py", line 141, in get_default_username
    auth_app.User._default_manager.db_manager(database).get(
  File "/usr/local/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 431, in get
    num = len(clone)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 262, in __len__
    self._fetch_all()
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
    cursor.execute(sql, params)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python3.9/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "auth_user" does not exist
LINE 1: ...user"."is_active", "auth_user"."date_joined" FROM "auth_user...
```

ワイ「また長いエラー文やな」

```sh
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
```

ワイ「`python manage.py migrate`しろって書いてるな」

ワイ「さっきやったのとは別なんかいな」

```sh:ターミナル
docker-compose exec web python manage.py migrate

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

ワイ「ほ〜ん(完全理解)」

ワイ「もっかいやってみるか」

```sh:superUserの作成
docker-compose exec web python manage.py createsuperuser

ユーザー名 (leave blank to use 'root'): 好きな名前
メールアドレス: 空でok
Password:
Password (again):
Superuser created successfully.
```

ワイ「できたんか？」

ワイ「早速adminページにアクセスや」

![スクリーンショット 2022-08-10 11.53.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/01b36dfe-4887-4986-a539-ee74e6df2bc4.png)
ワイ「さっき作ったユーザの情報を入れて・・・と」

![スクリーンショット 2022-08-10 11.54.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/f085c1d7-95e0-b1f1-aa31-c7fab093a0e2.png)
ワイ「おぉ、入れたしちゃんと`TODO`ってあるな」

ワイ「Todosってのを押したらデータを入れれるんか」
![スクリーンショット 2022-08-10 12.59.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/d2a3512d-13ab-0788-0b2d-60452754431a.png)
ワイ「今回は適当に一つデータ入れてみたで」

### template

ワイ「次は見える部分の実装やな」

```sh:テンプレートの作成
mkdir -p todo/templates/todo
touch todo/templates/todo/todo_list.html
```

ワイ「なんか`todo`ディレクトリの中にtodoディレクトリを作るんは違和感あるな」

？？「後々楽する為ですよ」

ワイ「ほへぇ〜」

ワイ「とりあえずアレ・・・書くか」

```html:todo/templates/todo/todo_list.html
<html>
  <head>
    <title>todo</title>
  </head>
  <body>
    <h1>hello world</h1>
  </body>
</html>
```

ワイ「やっぱ最初はこれや」

### urlのお話

ワイ「djangoのurlは`urls.py`ってやつで定義するんやな」

ワイ「これは`todo/`じゃなくて`config/`の方にあるな」

ワイ「困ったら以下をコピペしてな」

```python:config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todo.urls')),
]
```

ワイ「これでドメイン直下へのアクセスは`blog.urls`にリダイレクトされるんやな」

ワイ「ほな`blog.urls`をつくってくで」

```sh:urls.pyの作成
touch todo/urls.py
```

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

urlpatterns = [
    path('', views.todo_list, name='todo_list'),
]
```

ワイ「これで`localhost:8000/`へのアクセスは`todo_list.html`にリダイレクトされるな」

### viewをつくる

ワイ「さっき`blog.urls`で指定した`view.todo_list`の部分を実装するで」

```python:todo/views.py
from django.shortcuts import render


def todo_list(request):
    return render(request, 'todo/todo_list.html', {})
```

ワイ「書いてることは単純やな」

ワイ「`render`っていうテンプレをもとにviewを作る関数を呼び出して」

ワイ「その中であれこれ組み立ててできた値を戻り値にするで」

ワイ「詳しい話はまた今度や、今回は雰囲気だけ掴むで」

### いざ確認

ワイ「とりあえずこんなもんやろか」

ワイ「完了したら [localhost:8000](localhost:8000) にアクセスや」
![スクリーンショット 2022-08-10 12.44.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/4fbdfd0d-b3e0-2da2-1d0d-61dcde58486f.png)

ワイ「こんにちは、世界、ってな」

？？・閲覧者「やかましいわ」

### 登録したTodoをテンプレートに渡してみる(Django ORM)

ワイ「Todoアプリやしとりあえず登録したTodoの確認から始めてみるで」

ワイ「[ここ](#スーパーユーザの作成)でデータ登録してない人はなんでもええから登録してな」

ワイ「まずはちゃんとデータが取れるか確認するで」

```sh:ターミナル
docker-compose exec web python manage.py shell
// 対話モードに入る
// 行ごとに入力・エンターキーを押してください

>>> from todo.models import Todo
>>> Todo.objects.all()

<QuerySet [<Todo: sample>]>
```

ワイ「大丈夫そうやな」

ワイ「ほな実際にロジックとして書いていくで」

```python:todo/views.py
from django.shortcuts import render
from .models import Todo


def todo_list(request):
    todos = Todo.objects.all()
    return render(request, 'todo/todo_list.html', {'todo': todos})
```

### 渡されたデータを表示する

ワイ「Djangoやとテンプレートタグってのがあるんか」

以下djangogirlsより
> テンプレートタグとは？
HTML中で本当はPythonのコードを書くことはできません。なぜならブラウザが理解できないからです。ブラウザはHTMLだけ分かります。HTMLはどちらかというと静的で、それに対してPythonはもっとずっと動的なことを私たちは知っています。
Djangoテンプレートタグ はHTMLにPyhtonのようなコードを埋め込むことができて、動的なウェブサイトがより早く簡単に作れます!

ワイ「だそうや」

ワイ「ほな書いてくで」

```html:todo/templates/todo/todo_list.html
<html>
  <head>
    <title>todo</title>
  </head>
  <body>
    <h1>hello world</h1>
    {{ todo }} // 追記部分
  </body>
</html>

```

ワイ「こんな感じで`{{ 渡したデータ }}`で書けるみたいやな」

ワイ「ほなアクセスアクセス〜」

[locahost:8000](localhost:8000)
![スクリーンショット 2022-08-10 13.16.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/65d90137-c56f-48a1-e85f-b1c4609fa12c.png)
ワイ「さっきの対話モードとおんなじ表記やな」

ワイ「これはリストやからループでええ感じに取り出せそうやな」

```html:todo/templates/todo/todo_list.html
<html>
  <head>
    <title>todo</title>
  </head>
  <body>
    <h1>hello world</h1>
    {% for td in todo %}
    <div>
      <p>タスク名：{{ td.title}}</p>
      <p>タスク内容：{{ td.description}}</p>
    </div>
    {% endfor %}
  </body>
</html>
```

ワイ「リストの中身は`models`で登録したテーブルのカラムやからこんな感じでええやろ」

ワイ「ほな確(ry」

[localhost:8000](localhost:8000)
![スクリーンショット 2022-08-10 13.23.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/fe231a59-a02a-9ed8-6ec3-c1525d6f0cd1.png)
ワイ「ええやん」

### CSSで可愛くオシャレしよう

ワイ「とりあえずデータは表示したけど」

ワイ「見辛いし味気ないな」

ワイ「ここはcssちゃんでちょちょいのちょいや」

ワイ「・・・と言うとでも思ったか」

ワイ「ワイは`tailwindcss`ちゃん単推しや！！！！」

ワイ「ちなみに普通のcss書く場合は[ここ](https://tutorial.djangogirls.org/ja/css/)を参考にしてな」

ワイ「さっそく書いてくで」

```html:todo/templates/todo/todo_list.html
<html>
  <head>
    <title>todo</title>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body>
    <h1 class="text-red-500 text-5xl mb-5">hello world</h1>
    <div class="mx-auto w-9/12">
      {% for td in todo %}
      <div class="border-4 p-8">
        <p class="text-2xl border-b-2 mb-2">タスク名：{{ td.title}}</p>
        <p>タスク内容：{{ td.description}}</p>
      </div>
      {% endfor %}
    </div>
  </body>
</html>
```

ワイ「こんな感じや」

[localhsot:8000](localhost:8000)
![スクリーンショット 2022-08-10 13.41.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/29e5e6a9-ca4a-a3aa-8119-81f954b260e3.png)
ワイ「完璧やな！ええデザインや」

### ワイくんは・・・

ワイ「とりあえずこんなもんでええか」

ワイ「今日はもう疲れてもたわ」

ワイ「次はこの画面で登録したり消したりできるようにするで〜」

## 最後に

前回に引き続き拙い文章＋長編となってしまいましたがここまでお読み頂きありがとうございました。

また、前回の記事に関しても少なからず反響を頂けまして記事にした甲斐があったと共に、反省もしております。
時間を見つけて記事の修正予定ですのでお許しください。

何か問題点等ありましたらお気軽にコメント、もしくは[Twitterの方](https://twitter.com/Squl_d)へ連絡頂けますと幸いです。

今回は一通りDjangoの機能に触れながらデータベースの情報を表示するところまで進めました。

次回はワイくんも言っていた通り

- 登録機能
- 削除機能
- ~~デザインの改修~~

の三本立ての予定です。

ではまた次回。