Djangoでのデータベースの扱い方②

f:id:monozukuri-bu:20190724220046j:plain

どうも、夏映画を見に行きたいhashiです。

前回に続きDjango(ジャンゴ)というPythonのWebアプリケーションフレームワークでのデータベース操作についてまとめます。
今回は主にデータの挿入、更新とバリデーションチェックを扱います。

動作環境

Python 3.7.3
Django 2.2.1

テーブルの生成

Djangoでのテーブル定義は以下のように記述します。
クラス名がテーブル名、クラス変数の左辺が列の名前、右辺に型の情報やオプションを記述しています。

"""models.py"""
from django.db import models


class UserTable(models.Model):
    """ユーザー情報のテーブル"""
    user_id = models.IntegerField("ID", primary_key=True)
    name = models.CharField("ユーザー名", max_length=30)
    age = models.IntegerField("年齢", blank=True, null=True)


class PhoneTable(models.Model):
    """ユーザーの持つ電話のテーブル"""
    user = models.ForeignKey(UserTable, on_delete=models.CASCADE)
    tel = models.CharField("電話番号", max_length=15)
    email = models.EmailField("メールアドレス")
    contract_date = models.DateField("契約日", blank=True, null=True)

UserTable(表構造)

NULLを許容する 制約
user_id integer NOT NULL PRIMARY KEY
name varchar(30) NOT NULL
age integer

PhoneTable(表構造)

NULLを許容する 制約
id integer NOT NULL PRIMARY KEY
user_id integer NOT NULL REFERENCES
tel varchar(15) NOT NULL
email varchar(254) NOT NULL
contract_date date

このように、ユーザー情報と携帯電話の情報という2つのテーブルが作られます。
定義した覚えのないidという列がPhoneTableにありますが、これは primary_key=Trueを指定していないテーブルに自動的に追加される自動連番のプライマリーキーです。
これによって各テーブルにプライマリーキーが必ず1つだけ存在するようになっています。

テーブルへのデータの挿入、更新

あらかじめテーブルにいくつかデータを入れておきます。これからの操作はこのデータを用いて行います。

UserTable

user_id name age
1 tanaka 34
2 sato
3 suzuki 40
4 sasaki 24

PhoneTable

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp

データの挿入

以下のような疑似SQL文を実行してデータを挿入したいとします。

INSERT INTO PhoneTable(user_id, tel, email, contract_date) VALUES (1, '0123-45-6789', 'insert@insert.jp', '2020-01-01');

ユーザーIDが1のユーザーの電話機の情報を新規に登録する際、Djangoでは以下のように記述します。

phone = PhoneTable()  # オブジェクトの作成
phone.user = UserTable.objects.get(user_id=1)  # 紐づくユーザー
phone.tel = '0123-45-6789'  # 電話番号
phone.email = 'insert@insert.jp'  # メールアドレス
phone.contract_date = '2020-01-01'  # 契約日
phone.save()  # データベースに反映させる

PhoneTableクラスのオブジェクトを作成、各フィールドに値を入れた後にsave()メソッドを呼ぶことで値をデータベースに挿入することができます。
また、

phone.user = UserTable.objects.get(user_id=1)

は、以下のように書き換えることもできます。

phone.user_id = 1 # (外部キーのフィールド名)_id=(参照先列の値)

挿入後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp
9 1 0123-45-6789 insert@insert.jp 2020年1月1日

このように、末尾にデータが挿入されました。

データの更新

以下のような疑似SQL文を実行して、データを更新したいとします。

UPDATE PhoneTable
SET user_id = 2,
    tel = '000-000-0000',
    email= 'aa@a.aaa',
    contract_date = '2019-07-07'
WHERE id = 9;

id=9の電話機の情報を更新する際、Djangoでは以下のように記述します。

phone = PhoneTable()  # オブジェクトの作成
phone.id = 9 # 主キーの指定
phone.user = UserTable.objects.get(user_id=2)  # 紐づくユーザー
phone.tel = '000-000-0000'  # 電話番号
phone.email = 'aa@a.aaa'  # メールアドレス
phone.contract_date = '2019-07-07'  # 契約日
phone.save()  # データベースに反映させる

もしくは

phone = PhoneTable.object.get(id=9)  # オブジェクトの取得
phone.user = UserTable.objects.get(user_id=2)  # 紐づくユーザー
phone.tel = '000-000-0000'  # 電話番号
phone.email = 'aa@a.aaa'  # メールアドレス
phone.contract_date = '2019-07-07'  # 契約日
phone.save()  # データベースに反映させる

なんと前者ではデータの挿入時と記述方法が同じになっています。
この時どうやってDjangoがINSERTとUPDATEを見分けるかはドキュメントによると、

  • オブジェクトのプライマリキー属性が True と評価される値にセットされている場合 (例えば None や空の文字列以外の場合)。Django は UPDATE を実行します。
  • If the object's primary key attribute is not set or if the UPDATE didn't update anything (e.g. if primary key is set to a value that doesn't exist in the database), Django executes an INSERT.

とあり、主キーの値を指定することに加えて、その値を持つ行がデータベース内に既に存在する場合、UPDATEが実行されるようです。
上に示したコードでは主キーであるidを指定しており、id=9のデータがすでに存在するためUPDATEが実行されることになります。

docs.djangoproject.com

更新後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
3 2 0120-999-999 test@t.com 2019年7月4日
4 3 0120-202-102 temp@temp.com 2018年11月7日
5 2 0120-68-1147 phone@t.com 2019年7月23日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp
9 2 000-000-0000 aa@a.aaa 2019年7月7日

データのバリデーションチェック

入力データが必ずしも表構造にあっているとは限らないため、入力の確認をする必要があります。
そこで使われるのがfull_clean()メソッドです。
もしこのメソッドを呼び出したオブジェクトに不正な値(型の違い、一意制約違反など…)があれば 例外(ValidationError)を投げて教えてくれます。

try:
    user.full_clean()
except ValidationError as e:# 入力に不正な値があるとき
    # エラーの出たフィールドをユーザーに教えるなどの手続きをここに書く
else: # 入力に不正な値がないとき
    user.save() # 安心してデータベースに反映することができる

ほかにもバリデーションチェックを行う方法はありますが省略します。

データの削除

以下のような疑似SQL文を実行してデータを削除したいとします。

DELETE PhoneTable
WHERE user_id = 2;

user_id = 2の電話機の情報を削除する際、Djangoでは以下のように記述します。

phone = PhoneTable.objects.filter(user_id=2)  # 削除対象のオブジェクトの取得
phone.delete()  # データベースから削除する

filter()メソッドを使用して得られたクエリセットに delete() メソッドを呼ぶことで一括削除を行えます。

削除後のデータ

id user_id tel email contract_date
1 1 03-1234-9876 tanaka@t.com 2018年10月1日
2 1 090-1234-5678 tanaka2@t.com 2019年8月1日
4 3 0120-202-102 temp@temp.com 2018年11月7日
6 1 0120-976-543 testtest@t.com
7 1 03-1234-9876 karikari@test.com
8 4 111 poi@poi.jp

まとめ

Djangoにおけるデータベースへのデータ挿入、更新と削除方法についてまとめました。
前回の記事と合わせて、これで一通りCRUD操作が扱えるようになりました。