【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その2～

# はじめに

みなさん、初めまして。
Djangoを用いた投票 (poll) アプリケーションの作成過程を備忘録として公開していこうと思います。
Qiita初心者ですので読みにくい部分もあると思いますがご了承ください。

シリーズ

- [【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その0～](https://qiita.com/oboerarenai_user/items/4ff87b2afbad8c16ad40)
- [【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた～その1～](https://qiita.com/oboerarenai_user/items/4357841493832cd6ca2a)

# 作業開始

## Database の設定

DjangoチュートリアルではSQLite(軽量のRDBMS)を使用します。
（本番環境では拡張性があるOSSのDBを使ったほうがよいと記載されています。） 

SQLite以外のDBを使用する際は DATABASE > default　> ENGINE を変更します。
また、USER,PASSWORD,HOSTなどの追加設定も必要です。

```python:condig/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```

DBをマイグレートします。マイグレートとは、直訳すると移動・移行といった意味合いがあり、ここではアプリで使うDBの定義を作成しコミットする機能という理解でいいでしょうか。

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py migrate
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

(poll-HcNSSqhc) C:\django\poll>
```

## モデルの作成

MVCのModelを作成します。ModelとはDBを通じたデータ処理を行う関数のこと。
polls/modeles.pyではDBのテーブル、カラムを指定します。

```python:polls/modeles.py
from django.db import models

# Create your models here.

# テーブル：polls_Question
# カラム：question_text VARCHAR(200), pub_date DATETIME


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

# テーブル：polls_Choice
# カラム：question FOREIGN(CASCADE) KEY, choice_text VARCHAR(200), votes INTEGER
# CASCADE=外部キー制約 参照先の変更に追従する 参照先が無くなると同時に削除される


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

## モデルを有効にする

pollsアプリケーションをインストールしたこと(PollsConfigクラスの参照)をconfigプロジェクトに登録します。

```python:polls/apps.py

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'
```

config/setting.py に polls/apps.py > PollsConfig をドットつなぎで記述します。

```python:config/setting.py

INSTALLED_APPS = [
    'polls.apps.PollsConfig'
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

DBマイグレーションします。モデルの変更を行いますがあくまで一時ファイル上のお話で、DBにはマイグレート(コミット)しません。

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py makemigrations polls
Migrations for 'polls':
  polls\migrations\0001_initial.py
    - Create model Question
    - Create model Choice

(poll-HcNSSqhc) C:\django\poll>
```

DBマイグレーションの実動作は以下の通り確認できます。
CREATE TABLE、CREATE INDEX、COMMITなどSQLが発行されています。

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py sqlmigrate polls 0001
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;

(poll-HcNSSqhc) C:\django\poll>
```

DBマイグレートします。つまりDBにコミットします。

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

(poll-HcNSSqhc) C:\django\poll>
```

## API で遊んでみる

Pythonを対話モードで起動しましょう。

```python
(poll-HcNSSqhc) C:\django\poll>python manage.py shell
Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:37:30) [MSC v.1927 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 
```

shellでDBを操作していきます。
polls/models.pyファイルからChoiceクラス、Questionクラスを読み込みましょう。
まだオブジェクト（質問）はありあせんね。

```python

>>> from polls.models import Choice,Question
>>> Question.objects.all()
<QuerySet []>
>>> 
```

オブジェクト(質問)を作成します。

```pthon

>>> from django.utils import timezone
>>> 
>>> q=Question(question_text="What's new?",pub_date=timezone.now()) 
>>> q.save()
>>> 
>>> q.id
1
>>>
>>> q.question_text
"What's new?"
>>>
>>> q.pub_date
datetime.datetime(2020, 10, 6, 8, 27, 6, 527001, tzinfo=<UTC>)
>>>
```

saveしてオブジェクト(質問)を確認します。戻り値1なので存在するようです。

```python

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

ただ、戻り値1ではオブジェクトの内容が不明ですね。せっかくなら内容を表示させましょう。polls/modeles.pyを編集します。

```python:polls/modeles.py

import datetime
from django.db import models
from django.utils import timezone

# Create your models here.

# テーブル：polls_Question
# カラム：question_text VARCHAR(200), pub_date DATETIME


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

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

# テーブル：polls_Choice
# カラム：question FOREIGN(CASCADE) KEY, choice_text VARCHAR(200), votes INTEGER(初期値0)
# CASCADE=外部キー制約 参照先の変更に追従する 参照先が無くなると同時に削除される


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
```

もう一度オブジェクト(質問)を確認します

```python

>>> from polls.models import Choice, Question
>>> 
>>> Question.objects.all()
<QuerySet [<Question: What's>]>
>>> 
```
いろいろなデータの取り出し方を見ていきましょう。

```python

>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's>]>
>>>

>>> Question.objects.get(pk=1)
<Question: What's>
>>>
```

投票日(pub_date)が1日以内であるか判定しましょう。

```python

>>> q = Question.objects.get(pk=1)                              
>>>     
>>> q.was_published_recently()
True
>>>
```

Questionが外部参照しているChoiceテーブルのデータを作成します。
つまり、質問に対して選択肢を作成します。このとき、質問-選択肢のリレーションは1対多です。

```Python

#特定の質問に対する選択肢を作成すると宣言する
>>> q.choice_set.all()
<QuerySet []>
>>> 
#選択肢を3つ作成する　Questionオブジェクト→Choiceオブジェクトにアクセスしている
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> 
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> 
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
>>> 
#Choiceオブジェクト→Questionオブジェクトもアクセス可能
>>> c.question
<Question: What''s this?>
>>>
#全選択肢を確認する
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>>
>>> q.choice_set.count()
3
>>> 
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>>
#Choiceを一つ削除する
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
(1, {'polls.Choice': 1})
>>> 
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>]>
>>> 
```

## Django Adminの紹介

### 管理ユーザを作成する

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py createsuperuser
Username (leave blank to use '***'): admin
Email address: admin@example.com
Password:
Password (again):
The password is too similar to the username.
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

(poll-HcNSSqhc) C:\django\poll>
```
### 開発サーバーの起動

```python

(poll-HcNSSqhc) C:\django\poll>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
October 06, 2020 - 21:09:30
Django version 3.1.2, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
```
[http://127.0.0.1:8000/admin/ ](http://127.0.0.1:8000/admin/)にアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/cd799cdd-6ab1-1e23-24cd-8ac622244171.png)

### admin サイトに入る

ログインするとこのような画面です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/80330f69-4fbd-b780-1ef6-eb5e4cdfef5b.png)

### Poll アプリを admin 上で編集できるようにする

polls/admin.pyに追記します。

```django:polls/admin.py

from django.contrib import admin

# Register your models here.
from .mdoeles import Question

admin.site.register(Question)
```

### admin の機能を探究してみる

PollsアプリのQuestionが追加されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/2dad9d36-ce64-c729-4ab0-bdf1ede7485f.png)

Questionをクリックし遷移した先では、先の程の「What'S This?」がありました。
質問の新規作成や編集ができるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763383/4923c14d-3cfa-97ca-71dc-3c8714a67a3f.png)

今日はここまでです。ありがとうございました。