日々精進

aikoと旅行とプログラミング

DjangoでWebアプリを作りたい【第3回 Modelの作成】

前回簡単なViewを作成しました。今回はWriting your first Django app, part 2の章を進めていきたいと思います。

データベースのセットアップ

mysite/settings.pyを開いて、データベースのセットアップを行います。Djangoのデフォルト設定ではSQLiteを使用するようになっています。SQLitePythonに含まれているため新規にインストールする必要はありません。もし他のデータベースを使用する場合、DATABASES'default'の以下の項目を書き換えて下さい。

SQLite以外を使用している場合、USER, PASSWORD, HOSTを追加で記入する必要があります。 デフォルトは以下のとおりです。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

次にmysite/settings.pyのTIME_ZONEを以下のように設定します。

TIME_ZONE = 'Asia/Tokyo'

次にmysite/settings.pyのINSTALLED_APPSを見てみましょう。INSTALLED_APPSには現在のDjangoインスタンスで有効化されているアプリケーションが書かれています。アプリケーションは複数のプロジェクトで使用することができ、配布をすることも出来ます。デフォルトでは以下のアプリケーションが有効になっています。

上記のアプリケーションは、少なくとも1つのデータベーステーブルを利用します。ではテーブルを作成しましょう。ここで以下のコマンドを実行します。

$python manage.py migrate

migrateコマンドはINSTALLED_APPSを探し、mysite/settings.pyの設定に従って必要なテーブルを作成します。

モデルを作る

次にモデルを定義していきます。今回のアプリケーションpollでは、QuestionとChoiceという2つのモデルを作成します。Questionには質問(question)と公開日(publication)を、Choiceには質問(text)と投票数(vote)を格納します。各Choiceは1つのQuestionに関係があるものとします。
Djangoでは上記の仕様を以下のように表すことが出来ます。polls/models.pyを編集しましょう。

from django.db import models


# Create your models here.
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.db.models.Modelのサブクラスで表現されています。それぞれのモデルにはクラス変数があり、データフィールドを表しています。

各フィールドは、Fieldクラスのインスタンスとして表現されています。(CharFieldは文字のフィールド、DateTimeFieldは日時のフィールド)また、django.db.models.fieldの第一引数に人間が解読可能な値を持たせることで、フィールドに名前をつけることも出来ます。(今回はpub_dateに設定しました)

Fieldクラスの中には引数を必ず必要とするものもあります。例えば今回利用したCharFieldはmax_lengthの指定が必須となっています。

Choiceクラスの中で利用しているForeignKey関数を利用して、リレーションを定義することができます。

モデルを有効にする

今まで作成したモデルを有効にします。その前にpollアプリケーションを作成したことを知らせるため、mysite/settings.pyのINSTALL_APPSに'polls.apps.PollsConfig'追記します。

INSTALLED_APPS = (
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

このように書くことでDjangoがpollsの存在を知ることが出来ます。ここまでできたら以下のコマンドを実行します。

$python manage.py makeimgrations polls
出力
0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

makemigrationsを実行することで、Djangoにmodelの変更を知らせるとともにmigrationを作成します。(migrationは変更点を保存しているファイルと考えれば良いのだろうか)

さていま作成されたmigrationファイルの内容をデータベースに反映させましょう(migrate)
次のコマンドで実行されるsqlを確認してみます。

$python manage.py sqlmigrate 0001

すると以下の様な出力を得られます。

BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
         "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
         "choice_text" varchar(200) NOT NULL, 
         "votes" integer NOT NULL
);
--
-- 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
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" RENAME TO "polls_choice__old";
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"));
INSERT INTO
    "polls_choice"("votes", "choice_text", "id", "question_id")
    SELECT
        "votes",
        "choice_text",
        "id",
        NULL
    FROM
        "polls_choice__old";
DROP TABLE "polls_choice__old";
CREATE INDEX "polls_choice_7aa0f6ee" ON  "polls_choice"("question_id");
COMMIT;

のような出力がされます。ではmigrateしましょう。

$python manage.py migrat

出力は以下のとおり

Operations to perform:
  Apply all migrations: auth, contenttypes, sessions, polls, admin
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK
マイグレーションのまとめ

DBのマイグレーションをまとめると、

  1. モデルを変更する
  2. python manage.py makemigrationsを実行する
  3. python manage,py migrateを実行する

となります。必要ならばsqlmigrateをしてもよいでしょう。

APIを触ってみる。

Pythonのシェルを起動して、APIを触ってみたいと思います。まずは以下のコマンドを実行します。

$python manage.py shell

はじめにdatabase APIを触ってみます。

>> from polls.models import Question, Choice

# Pollは1つもなし
>>> Question.objects.all()
[]

# 新たな質問の作成
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# 作成されたオブジェクトをデータベースに登録。saveは明示的に呼ばなければならない。
>>> q.save()

# オブジェクトのIDを表示する。1と1Lは等価
>>> q.id
1

# 各カラムへのアクセス
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# 値の変更
>>> q.question_text = "What's up?"
>>> q.save()

# データベースに含まれているすべてのオブジェクトを表示
>>> Question.objects.all()
[<Question: Question object>]

ここで以下のカスタムメソッドを追記します。

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

そして再びshellを実行します。

>>> from polls.models import Question, Choice

# 先ほどとは変わってWhat's up?(__str__()で指定したもの)になる。
>>> Question.objects.all()
[<Question: What's up?>]

# idが1のものや、質問がWhatで始まるものを抽出。
>>> Question.objects.filter(id=1)
[<Question: What's up?>]
>>> Question.objects.filter(question_text__startswith='What')
[<Question: What's up?>]

# 2015年の質問を取り出す。
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# 指定したidのものがない場合
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# プライマリーキーを指定して抽出
>>> Question.objects.get(pk=1)
<Question: What's up?>

# 先ほど追記した関数のテスト
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Questionに2つのChoiceを追加する。createメソッドを呼び出すと、新たなChoiceオブジェクトを作成しINSERTを行いQuestionからアクセス可能な集合に追加される。(ChoiceにはQuestionが外部キーとして設定されているため)
>>> q = Question.objects.get(pk=1)

# 今は1つもChoiceがない。
>>> q.choice_set.all()
[]

# 3つのチョイスをつくる。
>>> 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)

# 関連付けられたQuestionへのアクセス
>>> c.question
<Question: What's up?>

# Choiceの集合をすべて表示
>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.choice_set.count()
3
>>> Choice.objects.filter(question__pub_date__year=current_year)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# deleteで削除
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()