はじめてのDjango (2) アプリケーションの作成とデータモデルの設計，そして，adminサイトの簡単なカスタマイズ

# はじめに

このドキュメントは，もともとはラボの新メンバー向けの入門テキストとして作成したもので，はじめてDjangoにふれる人にざっとその全体像をつかんでもらうことを狙っています．Djangoの日本語での参考資料も充実してきたので今さらという気がしないでもないですが，見直しを機にQiitaに移すことにしました．万が一でもどなたかの参考になれば幸いです．

説明のための具体例として，Djangoのオフィシャルチュートリアルにある投票アプリをとりあげています（が，説明上の都合で少しコードを追加，変更しているところもあります）．素人の独学がベースで，特に前半は公式チュートリアルをやってみた感想のような記事です（今回は全7回中の2回目）．

全体のコードはまとめて[GitHub](https://github.com/j54854/introDjango)に置きました．

## 変更履歴

- [2021/04/26] Djangoのバーションを3.2に更新し，それ基づいて内容を微修正しました．

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

アプリケーションを格納する枠組み（プロジェクト）ができたので，その中に自分のアプリケーションを追加してみましょう．外側のmysiteのディレクトリで次のコマンドを打ち込みます．

```console
$ python manage.py startapp polls
```

pollsはアプリケーションの名称です．この結果，外側のmysiteのディレクトリの中に，次のような構成のpollsというディレクトリが作成されます．

```
polls/
    __init__.py
    admin.py (adminサイトの設定ファイル)
    apps.py
    migrations/
        __init__.py
    models.py (データモデル用のファイル)
    tests.py
    views.py (view関数用のファイル)
```

これがアプリケーションの本体です．ただし，このままではプロジェクトはこのアプリケーションを認識してくれません．

そこで，もう一度プロジェクトの設定ファイルsettings.pyを開いて，`INSTALLED_APPS`のリストにこのアプリケーションを追加しておきましょう（`polls.apps.PollsConfig`は，pollsディレクトリの中のapps.pyファイルの中のPollsConfigクラスを指します）．

```python
INSTALLED_APPS = [
    'polls.apps.PollsConfig',  # これを追加する
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

# データモデルの設計

続いて，このアプリケーションで使うデータをモデル化し，それをデータベースに格納できるようにしていきます．データモデルの構成は，models.pyに記述します．このファイルを開いて下記の内容を書き込みましょう．

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

データは表のような形式でデータベースの中に格納されていきます．データモデルとはその表の列構成だと考えればいいでしょう．

Djangoでは，データモデルを`models.Model`を継承したクラスで表現します．各クラスが1つの表に対応し，その中の`field（models.****Field()`で指定されている項目）がそれぞれ1つの列を定義しています（モデル名は大文字で始め，フィールド名には小文字のみを用いること）．そして，表の各行に対応する具体的なデータは，そのクラスのインスタンスとして扱われます．

上の例では，Questionクラスに対応する表には2つの列（行番号を区別するためのidというfieldが自動的に追加されるので，厳密には3つの列）があり，それらには文字列と日時のデータが格納されます．

Choiceクラスに対応する表には3つの列（同じく，厳密には4つの列）があり，それらにはそれぞれ，対応するQuestionへの参照，文字列，整数のデータが格納されます．

Djangoには他にも多くの種類のfieldが用意されています（詳しくは[ここ](https://docs.djangoproject.com/en/3.2/ref/models/fields/#field-types)に整理されています）．

データモデルが定義できたので，それを実際にデータベースの構成に反映させましょう．そのために，外側のmysiteディレクトリで次のコマンドを実行します．

```console
$ python manage.py makemigrations
$ python manage.py migrate
```

これ以降，データベースの構成を更新したい際には，上記の2つのコマンドを使用することになります．

1つ目のコマンドは，データベースの構成を操作するためのマイグレーションファイルを作成するもので，2つ目のコマンドは，そのマイグレーションファイルを実際に適用してデータベースの構成を更新するものです．

ここで開発用サーバを立ち上げてadminサイトにログインしても，追加したデータモデルはまだ反映されていません．adminサイトでデータモデルを編集できるようにするためには，admin.pyを編集して，それをadminサイトに登録する必要があります．

ひとまずadmin.pyに下記のように書き込んで，上で作成した2つのデータモデルのクラスを登録してみましょう．

```python
from django.contrib import admin
from .models import Choice, Question

admin.site.register(Question)
admin.site.register(Choice)
```

QuestionとChoiceいう2つの項目がadminサイト上（のPOLLSのタブの下に）に現れたのがわかります．Questionの方をクリックして，試しに質問項目をいくつか追加してみましょう．

# adminサイトの簡単なカスタマイズ

追加した質問項目には，`Question object (1)`のような味気のない名前がつけられています．これをもう少しわかりやすい名前に変更してみましょう．そのために，models.pyに戻って，データモデルを表すクラスにいくつかメソッドを追加してみます．具体的には，models.pyの中身を次のように更新します．

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

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

    def __str__(self):
        return self.question_text

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

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

`__str__()`メソッドは，個々のデータ（データモデルのインスタンス）の名前を表記するのに使われます．adminサイトで確認すると，質問項目の名前が質問文自体に変更されていることがわかります．

`was_published_recently()`メソッドは，よくみるとわかるように，この質問項目の登録日時が現時点からさかのぼって1日以内（だから最近に追加された）かどうかを判定するブール値を返すメソッドになっています（このように，データモデルのクラスには様々なメソッドを追加することができます）．

現状では質問項目に対する回答の選択肢（Choice）は，質問項目（Question）とは独立して表示されていますが，これらは関連付けておいたほうがわかりやすいし，編集も容易です．これは，admin.pyを次のように書き換えることで簡単に実現できます．

```python
from django.contrib import admin
from .models import Choice, Question

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3

class QuestionAdmin(admin.ModelAdmin):
    inlines = [ChoiceInline]
    list_display = ('question_text', 'pub_date', 'was_published_recently')

admin.site.register(Question, QuestionAdmin)
```

また，`admin.StackedInline`を`admin.TabularInline`に書き換えると，Choiceの部分がもう少しコンパクトな表形式の表示に変わります．

```python
class ChoiceInline(admin.TabularInline):  # ここを変更
    model = Choice
    extra = 3
```

# おわりに

以上で2回目は終了です．[第3回](https://qiita.com/j54854/items/201ecbe55017fd2a7996)に続きます．