Django+Reactで学ぶプログラミング基礎(10): Djangoチュートリアル(投票アプリその5-1)

##### [前回] [Django+Reactで学ぶプログラミング基礎(9): Djangoチュートリアル(投票アプリその4-2)](https://qiita.com/mingchun_zhao/items/34018b7c89eb72b611d3)

## はじめに

[Django公式チュートリアル](https://docs.djangoproject.com/ja/4.0/intro/tutorial01/)、その5-1です。
前回は、アプリを汎用ビューに書き換えました。
今回は、アプリのテストフェーズに入ります。

## Djangoアプリ作成(その5-1): 投票(poll)アプリ

### 今回の内容

- Python対話シェルで、APIを使用し動作確認

### Djangoが提供するAPIで遊んでみる
※ [チュートリアル その2](https://docs.djangoproject.com/ja/4.0/intro/tutorial02/#playing-with-the-api)の内容をここに移動しました。

- Python対話シェルを起動し、Djangoが提供するAPIの動作確認
    - Pythonを`manage.py`経由で実行する理由
        - `manage.py`に設定されている`DJANGO_SETTINGS_MODULE`環境変数を使用するため
```
C:\kanban\pollsite>..\venv\.venv\Scripts\activate
(venv) C:\kanban\pollsite>python manage.py shell
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/99d3d36a-17db-1d76-8558-966ed3d8834e.png)

- モデルクラス二つをインポート
```
>>> from polls.models import Choice, Question
```

- 質問事項のリストを確認
```
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>, <Question: Question object (2)>, <Question: Question 
object (3)>, <Question: Question object (4)>, <Question: Question object (5)>, <Question: Question object (6)>]>
```

- 新しい質問を作成し、保存
    - デフォルトでタイムゾーンが有効になっている
        - `pub_date`フィールドには`datetime with tzinfo`データ型を指定
        - `timezone.now()`関数を使用し、現在時刻を取得(`datetime.datetime.now()`関数ではなく)
```
>>> from django.utils import timezone
>>> q = Question(question_text="気持ちはいかがでしょうか?", pub_date=timezone.now())
>>> q.save()
```

- 質問`ID`を確認
```
>>> q.id
7
```

- Python属性を使用し、モデルフィールド値にアクセス
```
>>> q.question_text
'気持ちはいかがでしょうか?'
>>> q.pub_date
datetime.datetime(2022, 6, 7, 23, 20, 34, 394580, tzinfo=datetime.timezone.utc)
```

- 属性を変更することで、フィールド値を変更し、保存
```
>>> q.question_text = "今日の予定は?"
>>> q.save()
```

- 質問事項に新しい質問が追加されたことを確認
```
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>, <Question: Question object (2)>, <Question: Question 
object (3)>, <Question: Question object (4)>, <Question: Question object (5)>, <Question: Question object (6)>, <Question: Question object (7)>]>
```

- 質問オブジェクトの表現をわかりやすく修正
    - `<Question: Question object (1)>`のような表現は可読性がない
    - `Question`モデルと`Choice`モデルに`__str__()`メソッドを追加
    - `__str__()`メソッドによるオブジェクト表現は
        - Django自動生成の管理サイトでも使用される(対話シェルだけでなく)
```polls/models.py
from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/2d942ca1-2791-65d1-71eb-8ef1b876a373.png)

- モデルクラスにクラスメソッドを追加
    - 以下2点参照するためインポート
        - Python標準モジュール`datetime`をインポート
        - Djangoタイムゾーン関連ユーティリティ`django.utils.timezone`をインポート
```polls/models.py
import datetime

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


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
```

- `polls/models.py`の変更を保存
- 新しいPython対話シェルを始める
```
>>> exit()

(venv) C:\kanban\pollsite>python manage.py shell
>>> from polls.models import Choice, Question
```

- `__str__()`メソッドが機能するか確認
```
>>> Question.objects.all()
<QuerySet [<Question: Djangoは難しいですか>, <Question: 難しいポイントは？>, <Question: 趣味は？>, <Question: 目標は？>, <Question: 幸せとは？>, <Question: 楽しいことは？>, <Question: 今日の予定は?>]>
```

- 検索APIで検索
    - Djangoは、キーワード引数によって駆動される検索APIを提供
```
>>> Question.objects.filter(id=1)
<QuerySet [<Question: Djangoは難しいですか>]>
>>> Question.objects.filter(question_text__startswith='Django')
<QuerySet [<Question: Djangoは難しいですか>]>
```

- 今年に公開された質問一覧を取得
```
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.filter(pub_date__year=current_year) 
<QuerySet [<Question: Djangoは難しいですか>, <Question: 難しいポイントは？>, <Question: 趣味は？>, <Question: 目標は？>, <Question: 幸せとは？>, <Question: 楽しいことは？>, <Question: 今日の予定は?>]>
```

- 存在しない`ID`をリクエストすると、例外発生
```
>>> Question.objects.get(id=10)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\kanban\venv\.venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\kanban\venv\.venv\lib\site-packages\django\db\models\query.py", line 496, in get
    raise self.model.DoesNotExist(
polls.models.Question.DoesNotExist: Question matching query does not exist.
```

- 主キーによる検索には、ショートカット`pk`を使用
    - 以下は`Question.objects.get(id=1)と同じ
```
>>> Question.objects.get(pk=1)
<Question: Djangoは難しいですか>
```

- 上記追加した、カスタムメソッド`was_published_recently`が機能するか確認
```
>>> q = Question.objects.get(pk=7) 
>>> q.was_published_recently()     
True
```

- 質問にいくつかの選択肢を与える
```
>>> q = Question.objects.get(pk=7)
```

- 質問に紐づく選択肢セットを確認、存在しない
```
>>> q.choice_set.all()
<QuerySet []>
```

- 選択肢を三つ作成
```
>>> q.choice_set.create(choice_text='はい', votes=0)
<Choice: はい>
>>> q.choice_set.create(choice_text='いいえ', votes=0)
<Choice: いいえ>
>>> c = q.choice_set.create(choice_text='どちらでもない', votes=0)
```

- `Choice`オブジェクトから、関連する`Question`オブジェクトにAPIでアクセス可能
```
>>> c.question
<Question: 今日の予定は?>
```

- `Question`オブジェクトから、関連する`Choice`オブジェクトにAPIでアクセス可能
```
>>> q.choice_set.all()
<QuerySet [<Choice: はい>, <Choice: いいえ>, <Choice: どちらでもない>]>
>>> q.choice_set.count()
3
```

- APIで、検索キーワード引数の形式
    - `field__lookuptype=value`
        - 区切り文字は、二重下線
    - 例えば、`pub_date`フィールドが今年となっている質問のすべての選択肢を検索
        - `question__pub_date__year`
```
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: 難しい>, <Choice: 難しくない>, <Choice: 旅行>, <Choice: 課題解決>, <Choice: 趣味>, <Choice: はい>, <Choice: いいえ>, <Choice: どちらでもない>]>
```

- 選択肢を削除
```
>>> c = q.choice_set.filter(choice_text__startswith='どちらでもない')
>>> c.delete()
(1, {'polls.Choice': 1})
```

## おわりに

Djangoが提供するAPIを使用し、モデル操作や確認を行ってみました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(11): Djangoチュートリアル(投票アプリその5-2)](https://qiita.com/mingchun_zhao/items/fb79779ef74f98098d7b)