CakePHP使いがDjangoでサイトを作ってみた 〜MySQLのデータベースを使う〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
美人モデルのデヴィナさん

この記事を三行にまとめると

MySQLのデータベースに接続して、いろいろとデータを取り扱ってみたいと思います
CakePHPとそんなに大きくは変わらない印象です
モデルに関する記事を書く時は基本的にモデルさんのフリー素材を使うことにしている
今回はモデルの使い方を見ていきましょう。MySQLのデータベースに接続して、いろいろとデータを取り扱ってみたいと思います。あまり深くは突っ込まずに基本的なところだけさらっとなでていきます。



前提条件

今回はブログ記事を登録するテーブルでデータの取得や追加を行うという前提で考えていきましょう。テーブルの作成自体はDjangoから行うこともできますが、ここは手軽にphpMyAdminとかを使って作ったことにしておきます。

#app_blogsテーブル
+----+-----------+--------+------+---------------------+
| id | author_id | title  | text |      created        |
+----+-----------+--------+----------------------------+
|  1 |     1     | title1 | text | 2018-10-01 12:00:00 | 
|  2 |     2     | title2 | text | 2018-10-02 12:00:00 | 
|  3 |     1     | title3 | text | 2018-10-03 12:00:00 | 
|  4 |     3     | title4 | text | 2012-10-04 12:00:00 | 
|  5 |     3     | title5 | text | 2012-10-05 12:00:00 | 
+----+-----------+--------+------+---------------------+

(int) id・・・プライマリーキー
(int) author_id・・・投稿者のID
(varchar) title・・・記事タイトル
(text) text・・・記事本文
(datetime) created・・・投稿日時

テーブルの中身はこんな感じだとします。(int)とか(text)は各カラムの型を意味します。

テーブル名を「app_blogs」としていますが、Djangoのデフォルト設定ではテーブル名はアプリケーション名とテーブル名をアンダーバーでつないだものになります。だからアプリ名を「app」とすれば「app_table」となるし、もしアプリ名を「papp」とかにすれば「papp_table」「papp_blogs」といった感じになります。



MySQLのインストール

まずはDjangoでMySQLを使えるようにするために「PyMySQL」というモジュールをインストールします。

$ pip install PyMySQL



接続設定

次にMySQLの接続設定をsettings.pyに書きます。

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'name',
    'USER': 'user',
    'PASSWORD': 'password',
    'HOST': 'localhost',
    'PORT': '3306',
  }
}

import pymysql
pymysql.install_as_MySQLdb()

特殊な設定を必要としなければ、とりあえずこんだけ書いとけば接続できると思います。CakePHPでデータベースの接続設定を書いたことがあれば、ほぼ同じような書き方なのであまり戸惑うことはないかもです。NAMEがデータベース名、USERとPASSWORDがユーザー名とパスワード、HOSTが接続先のホスト名です。あとポートね。

あと先ほどインストールしたPyMySQLモジュールもインポートしておきます。



models.pyの設定

接続設定が完了したら、次はモデルにテーブルの設定を書きます。ファイルの分割とか独自の設定をしていなければモデルの設定はmodels.pyというファイルを使います。

#models.py
from django.db import models

class Blogs(models.Model) :
  author_id = models.IntegerField()
  title = models.CharField(max_length = 100)
  text = models.TextField()
  created = models.DateTimeField(auto_now_add = True)

テーブル名は先頭にアプリ名がついていましたが、モデルのクラス名にはアプリ名はつけず、それ以外の部分(今回の場合だとblogs)の先頭を大文字にすればオーケーです。

あとはフィールド名を変数として型の設定を書く。上記で言うと「IntegerField」は数値型、「CharField」は文字列型、「TextField」はテキスト型、「DateTimeField」はdatetime型です。CakePHPの場合、プリマリーキーのフィールド名はデフォルトが「id」でしたが、Djangoも同じ仕様なのか、idというフィールドを使う場合は明示的にモデルに設定を書かなくても問題ないっぽいです。

それからcreatedの設定のところに「auto_now_add = True」という記述がありますが、これはデータを新規に登録する時に自動的に現在時刻を入れるための設定です。これがないと自分で時間を入れるコードを書かないといけない。「auto_now = True」という設定を記述すると更新時にも自動的に現在時刻を入れてくれます。

その他のフィールドの型やオプションについて確認したい場合はこちらを。

モデルフィールドリファレンス



SELECT文

ここまでで必要な設定はそろったので、ここからは実際にデータを扱っていきましょう。

まずはSELECT文っすね。views.pyでデータを取得してみたいと思います。

#views.py
from app.models import Blogs

class index(TemplateView) :
  def get(self, request, *args, **kwargs) :
    data = Blogs.objects.all()

ビューの書き方については置いといて、見て欲しいのは「Blogs.objects.all()」のところ。これは全件を取得するためのコードです。CakePHPで言うと「find(‘all’)」的なやつ。

返ってくるデータはオブジェクト型になります。なので、for文などで使う場合はこんな感じになります。

data = Blogs.objects.all()
for val in data :
  print(val.id)
  print(val.title)

val[‘id’]ではなくval.idとかになります。



一件だけ取得したい場合は「get()」メソッドを使います。

data = Blogs.objects.get(id = 1)

これでidが1のデータを取得できます。

あるいは「filter()」というメソッドを使って取得することもできます。

data = Blogs.objects.filter(id = 1)

get()は一件だけ取得するメソッドなのに対し、filter()というは条件に一致するデータを全件取得するメソッドなので、他の条件を指定すれば複数件取得することもできます。

#author_idが1のデータ
data = Blogs.objects.filter(author_id = 1)

#今日のデータ
data = Blogs.object.filter(created = '2018-12-01 00:00:00')

#今日のauthor_idが1のデータ
data = Blogs.object.filter(author_id = 1, created = '2018-12-01 00:00:00')

filter()は結構柔軟な使い方ができるので、もしかしたら一番よく使うメソッドかもしれません。

#条件に合致する最初の一件だけを取得
data = Blogs.objects.filter(author_id = 1).first()

#条件に合致する最後の一件だけを取得
data = Blogs.objects.filter(author_id = 1).last()

#条件に合致する件数を取得
data = Blogs.objects.filter(author_id = 1).count()

#条件に合致するものを日時の古い順に取得(ASC)
data = Blogs.objects.filter(author_id = 1).order_by('created')

#条件に合致するものを日時の新しい順に取得(DESC)
data = Blogs.objects.filter(author_id = 1).order_by('-created')

ORDER BYでDESCを使いたい場合、一番シンプルな書き方はカラム名にマイナスをつけるだけで良いようです。知らないと「どうやってDESCするんだ?」ってなるけど、知ってると書くのが楽で良いですね。

条件の指定はキーワード引数ではなくディクショナリを渡すこともできます。

where = {
  'author_id': 1,
  'created' : '2018-12-01 00:00:00',
}
data = Blogs.objects.filter(**where)

他にもいろいろな指定ができるので必要に応じて使い倒してみてください。イコール以外(idが10以上とかLIKE検索やBETWEENなど)はちょっと変則的な書き方になるのですが、全部やるとちょっと長くなってしまうので、今回はやりません。ここ(↓)を見てもらえればやり方が載ってますんで、すみませんがこっちで確認してみてください。

QuerySet API reference

それからfilter()の場合は条件に該当するレコードが一件もなくてもエラーにはなりませんが、get()の場合はデータがないとエラーになります。だから確実にレコードが存在すると分かっていない場合はtryを使った方が良いでしょう。

try :
  data = Blogs.objects.filter(id = 1)
except :
  print('not found')



INSERT文

次はデータの登録です。新規にデータを登録する際には「create()」というメソッドを使います。

Blogs.objects.create(author_id = 1, title = 'タイトル', text = '本文')

各フィールドをキーワード引数として登録したいデータを渡してあげればデータが登録されます。

INSERTの場合もディクショナリで一つの変数にまとめることは可能です。

save = {
  'author_id': 1,
  'title' : 'タイトル',
  'text' : '本文',
}
Blogs.objects.create(**save)



UPDATE文

データの更新は一件だけ更新する場合と、条件に合致するデータをまとめて更新する場合(updateAll的なやつ)の二つがあります。

一件だけ更新する場合は、最初に更新したいデータを取得してきて、そのデータに対して「save()」メソッドを使います。

data = Blogs.objects.get(id = 1)
data.title = 'タイトル編集'
data.text = '本文編集'
data.save()

これでidが1のデータのタイトルと本文が更新されます。

UPDATEの場合は僕のやり方が間違えてなければの話なんですが、save()メソッドにディクショナリでデータを渡すことはできないみたいです。

modify = {
  'title': 'タイトル編集',
  'text': '本文編集',
}
data = Blogs.objects.get(id = 1)
data.save(**modify)

こんな感じのことをやろうとしてもエラーになってしまう。一つずつフィールドを指定してデータを入れていくしかないみたいですね。一つ二つだったら良いけど、何十個もあるフィールドを全部書いてくのは面倒ですよね。何か良いやり方があれば良いんですが……今の僕に思いつくのは「setattr()」を使うことくらいでしょうか。

#更新するデータ
modify = {
  'title': 'タイトル編集',
  'text': '本文編集',
}

#データを取得
data = Blogs.objects.get(id = 1)

#for文で一つずつ値をセットする
for key, val in modify.items() :
  setattr(data, key, val)

#更新
data.save(**modify)

こんな感じかなあ? もっと良いやり方がある場合やsetattr()を使うのはよくないって場合はぜひご指摘いただきたい。

あと「update_fields」という引数で更新したいフィールドをリストで絞り込むことができます。

data = Blogs.objects.get(id = 1)
data.title = 'タイトル編集'
data.text = '本文編集'
data.save(update_fields = ['title'])

こうするとtitleフィールドだけが更新されてtextは更新されなくなります。



条件を指定してまとめてデータを更新する場合はSELECTの時にも出てきたfilter()を使います。

Blogs.objects.filter(author_id = 1).update(title = '更新')

これでauthor_idが1のデータのタイトルが全て一括で更新されます。



DELETE文

DELETE文の使い方はUPDATEと似ています。最初にデータを取得してそのデータを削除する場合と、filter()を使って条件に合致するデータを一括で削除する場合の二つ。

#一件だけ削除
Blogs.objects.get(id = 1).delete()

#条件に合致するデータを削除
Blogs.objects.filter(author_id = 1).delete()

以上っす。






個人的にはCakePHPと使い方が似ているかなあと思いました。僕の場合はIN句やLIKEなどのところでちょっとつまづいたけど、CakePHPに慣れてる人なら感覚をつかむのはそこまで大変じゃないと思います。CakePHPの場合もget()で一件だけデータを取る場合、データがないとエラーになるしね。フレームワーク界ではそういうスタンダードな仕様があるのかもしれん。

あと内容とは全然関係ない画像をトップに表示してますが、以前CakePHP3のモデルに関する記事を書いた時にモデルさんのフリー画像を使ったところ、その記事をTwitterでシェアしてくれた方が「何でこの記事に美女画像を使ってんだ……?」みたいなことをツイートしてたので、こりゃ面白いと思って、今後開発系の記事でモデルに関わる部分の話をする時はモデルさんのフリー画像を積極的に使っていくことにしました。今回はぱくたそさんからちょうだいしました。あざっす。
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください