Django Tutorial Part 1.5 -- Model から Table を作って shell mode で Table の中身を動かす
Tags: django, tutorial, python, postgres
Published: 2021-12-19 13:42:50

# 準備

## 前提、前回の復習

https://dev.to/kaede_io/docker-compose-de-django-tutorial-no-httpresponse-woque-ren-suru-67o

前回の記事で 

* Docker Compose で Django + Postgres での環境構築
* ブラウザでのプロジェクトのトップ画面の動作確認
* polls アプリを作成して、ルーティングの動作確認

ここまでやった。
今回はその続き Django Tutorial Part 2 をやる

https://docs.djangoproject.com/en/4.0/intro/tutorial02/

---

## migrate で初期化

https://docs.djangoproject.com/en/4.0/intro/tutorial02/#creating-models

```php
docker-compose run web python manage.py migrate

Starting rest0406_db_1 ... done
Creating rest0406_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, 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 auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
```

まずはこれで DB の初期化をする。
これで新しく DB を作る準備ができると推測する。

---
---

# モデルファイルを作成して DB テーブルを作る

## Question テーブルのモデルファイルを書く

次に DB テーブルを作るためのモデルのファイルを書く。


```py
from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
```

Question とそれに関連する Choice のデータベースを作る

このふたつ、 Blitz のチュートリアルでも見たな...

## モデルを読み込んで有効化する

https://docs.djangoproject.com/en/4.0/intro/tutorial02/#activating-models

これを読み込むためには `projectName/settings.py`
に追加の設定を書き込む必要がある。

```py
INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    ...
]
```

polls/ の一つ上のディレクトリの settings.py の

INSTALLED_APPS に `'polls.apps.PollsConfig',` を追加する

---

## makemigrations で 初期化の準備ファイルを作る

> By running makemigrations, you’re telling Django that you’ve made some changes to your models (in this case, you’ve made new ones) and that you’d like the changes to be stored as a migration.

makemigrations を動かすことで
Django に「モデルが変わったよ！」と伝えているらしい。

```php
docker-compose run web \
python manage.py makemigrations polls
```

makemigrations コマンドを打つ。

```php
docker-compose run web \
python manage.py makemigrations polls

Creating dockerdjango_web_run ... done

Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Question
    - Create model Choice
```

これで初期化の準備ファイル

```py
# Generated by Django 3.2.12 on 2022-04-09 08:24

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Question',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('question_text', models.CharField(max_length=200)),
                ('pub_date', models.DateTimeField(verbose_name='date published')),
            ],
        ),
        migrations.CreateModel(
            name='Choice',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('choice_text', models.CharField(max_length=200)),
                ('votes', models.IntegerField(default=0)),
                ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.question')),
            ],
        ),
    ]
```

0001_initial.py が作成された。

このファイルを指定して sqlmigrate する。

---

## sqlmigrate で SQL を発行する

```php
docker-compose run web \
python manage.py sqlmigrate polls 0001

[+] Running 1/0
 ⠿ Container dockerdjango-db-1  Running                                                                                                    0.0s
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" bigserial NOT NULL PRIMARY KEY, "question_text" varchar(200) NOT NULL, "pub_date" timestamp with time zone NOT NULL);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" bigserial NOT NULL PRIMARY KEY, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" bigint NOT NULL);
ALTER TABLE "polls_choice" ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id" FOREIGN KEY ("question_id") REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
```

これで sql を発行する

---

## migrate で sql を実行する

```php
docker-compose run web \
python manage.py migrate

[+] Running 1/0
 ⠿ Container dockerdjango-db-1  Running                                                                                                    0.0s

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions

Running migrations:
  Applying polls.0001_initial... OK
```

これで SQL を実行し、テーブルを作成できたと解釈する。

---
---

# shell でインタラクティブモードでテーブルの中身を確認する

## shell コマンドでインタラクティブモードを起動する

シェルモードで、実際にテーブルが作成されているのか確認する。

https://docs.djangoproject.com/en/4.0/intro/tutorial02/#playing-with-the-api

shell コマンドを実行すると

```php
docker-compose run web \
python manage.py shell

[+] Running 1/0
 ⠿ Container dockerdjango-db-1  Running                                                                                                    0.0s
Python 3.10.0 (default, Oct 16 2021, 10:05:15) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 
```

インタラクティブ コンソールが起動する。

---

## Questions の中身を objects.all() で確認する

```php
>>> from polls.models import Choice, Question

Question.objects.all()
<QuerySet []>
```

migrate で作成した polls アプリのモデルの
Choice, Questions のテーブルをシェルでインポートして
Question の objects.all() で中身を見る。

すると空のクエリセットが帰ってくる。
クエリセットは JSON に変換する前の状態だと解釈する

---

## Question に question_text と pub_date のデータを入れる

```py
from django.utils import timezone
```

pub_date で使うので timezone のライブラリをインポートする

```py
q = Question(question_text="What's new?", pub_date=timezone.now())
```

Question モデルの中に
* question_text という変数名で What's new? というテキスト
* pub_date という変数名で timezone.now() の現在の時間
これらを入れ、それを q という変数に入れる。

```py
>>> q
<Question: Question object (None)>
>>> q.question_text
"What's new?"
```

変数の q そのものに アクセスしても Object (None) になってしまうが
q.question_text にアクセスすると中身の文字列が返ってくる

```py
>>> q.id
>>>
```

id は最初はない。

```py
>>> q.save()
>>> q.id
1
```

save() すると id が決まる。

```py
>>> q.pub_date

datetime.datetime(2021, 12, 19, 13, 17, 26, 609289, tzinfo=<UTC>)
```

pub_date にアクセスすると datetime がこのように返ってくる。

```py
>>> q.objects.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python3.10/site-packages/django/db/models/manager.py", line 179, in __get__
    raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
AttributeError: Manager isn't accessible via Question instances
```

q に objects.all() でアクセスしても中身は見えない。

```
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
```

Question モデルに直接 objects.all() することで中身を見れる

しかしこれだと中身の詳細出てこないので、
objects.all() をしただけでモデルの詳細まで出るようにする。

---

## __str__ を追加して text の中身を見えるようにする


```py
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
      return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text
```

polls/models.py の Question と Choice のクラスに
`__str__` という関数を追加する。

```php
>>> exit()
kaede0902@rooter rest0406 % 

docker-compose run web \
python manage.py shell

[+] Running 1/0
 ⠿ Container dockerdjango-db-1  Running                                                                                                    0.0s
Python 3.10.0 (default, Oct 16 2021, 10:05:15) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
```

反映させるためにシェルを再起動

```py
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet [<Question: How Are You?>]>
```

これで Question の中身を見る時に、object の中身の text が表示されるようになった！

---

## 条件でクエリする

```py
>>> Question.objects.filter(id=1)
<QuerySet [<Question: How Are You?>]>
>>> Question.objects.filter(id=2)
<QuerySet []>

>>> Question.objects.filter(question_text__startswith='H')
<QuerySet [<Question: How Are You?>]>
```

filter, startswith, 

でカラムの中身で検索することができるし

```py
>>> Question.objects.get(id=1)
<Question: How Are You?>
>>> Question.objects.get(id=0)
...
polls.models.Question.DoesNotExist: Question matching query does not exist.

>>> Question.objects.get(pk=1)
<Question: How Are You?>
```

id, primary key で指定して呼び出すこともできる

---