Django 複数DB を実装する(Django の app 毎に DB を使用)

シェアする

Django で複数 DB の実装をします。

複数 DB を使いたいケースとしては、主に以下のケースがあると思います。

ここでは、Django の app 毎に DB を使用するケースを実装して行きたいと思います。

完成イメージ

バージョン

  • Python 3.6.4
  • Django 2.0.3
  • MySQL 5.7.21
  • mysqlclient 1.3.12

インストール

Python, Django

Python と Django のインストールは、Python Web フレームワーク Django の環境を構築するの記事を参考にしてください。

MySQL, mysqlclient

MySQL と mysqlclient のインストールは、Djangoでマイグレーションを行う + MySQLの記事を参考にしてください。

実装

settings.py

ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
    ...
    'app1.apps.App1Config',
    'app2.apps.App2Config',
]
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db1',
        'USER': 'root',
        'PASSWORD': 'Eba7|B33veK+',
        'OPTIONS': {
            'charset': 'utf8mb4',
        }
    },
    'db2': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db2',
        'USER': 'root',
        'PASSWORD': 'Eba7|B33veK+',
        'OPTIONS': {
            'charset': 'utf8mb4',
        }
    },
}
DATABASE_ROUTERS = ['multidb_app.db_router.DbRouter']

settings.py の DATABASES に DB を2つ定義します。今回は、db1 を default とし、app1 用と Django の認証やセッション管理などを兼務しています。

さらに、DATABASE_ROUTERS を定義します。詳細は後述しますが、この DATABASE_ROUTERS で app と db のマッピングを定義します。

db_router.py

class DbRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'app1':
            return 'default'
        if model._meta.app_label == 'app2':
            return 'db2'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'app1':
            return 'default'
        if model._meta.app_label == 'app2':
            return 'db2'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        return True

    def allow_migrate(self, db, app_label, model=None, **hints):
        if app_label == 'auth' or app_label == 'contenttypes' or app_label == 'sessions' or app_label == 'admin':
            return db == 'default'
        if app_label == 'app1':
            return db == 'default'
        if app_label == 'app2':
            return db == 'db2'
        return None

DB ルータです。

DB ルータでは、4つの関数を定義します。詳細は、マニュアルを確認してください。

ここの定義が大変で、私はいつも db や app_label を print 文で出力しながらいい感じに仕上げていきます。print 文で出力すると、同じ db 名や app_label 名が何回も飛んでくるのがわかると思います。

ここでは、Read と Write、migrate を定義しています。認証やセッションについては、考慮していないので、それらを実装する場合は、見直しが必要だと思います。

models.py

app1/models.py

from django.db import models

class Db1Table1(models.Model):
    column1 = models.CharField(max_length=10)

    def __str__(self):
        return self.column1

db1 用の models.py です。テーブル1つにカラムを1つ定義しています。

app2/models.py

from django.db import models

class Db2Table1(models.Model):
    column1 = models.CharField(max_length=10)

    def __str__(self):
        return self.column1

db2 用の models.py です。こちらもテーブル1つにカラムを1つ定義しています。

urls.py

from django.urls import path

from app1.views import app1
from app2.views import app2

urlpatterns = [
    path('app1/', app1),
    path('app2/', app2),
]

views.py

app1/views.py

from django.http import HttpResponse
from app1.models import Db1Table1

def app1(request):
    Db1Table1.objects.create(column1='app1')
    return HttpResponse('app1')

動作確認用に、/app1 にアクセスすると、db1 にテーブルを作成するようにします。

app2/views.py

from django.http import HttpResponse
from app2.models import Db2Table1

def app2(request):
    Db2Table1.objects.create(column1='app2')
    return HttpResponse('app2')

こちらも、動作確認用に、/app2 にアクセスすると、db2 にテーブルを作成するようにします。

これで仕込みを完了です。

DB 作成

create database

$ mysql -u root -p -e 'create database db1 default charset utf8mb4'
$ mysql -u root -p -e 'create database db2 default charset utf8mb4'

Django のマイグレーションは、データベースを作成しない(私が知らないだけ)ので、先にデータベースを作成します。

makemigrations

$ python3.6 manage.py makemigrations
Migrations for 'app1':
 app1/migrations/0001_initial.py
 - Create model Db1Table1
Migrations for 'app2':
 app2/migrations/0001_initial.py
 - Create model Db2Table1
$

makemigrations コマンドで、models.py からマイグレーションファイルを生成します。

migrate

$ python3.6 manage.py migrate
$ python3.6 manage.py migrate --database=db2

マイグレーションファイルからテーブルを作成します。

makemigrations コマンドは、1回の実行でしたが、こちらは、データベース毎の2回実行します。

show tables

mysql> show tables from db1;
+----------------------------+
| Tables_in_db1 |
+----------------------------+
| app1_db1table1 |
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
11 rows in set (0.00 sec)

mysql> show tables from db2;
+-------------------+
| Tables_in_db2 |
+-------------------+
| app2_db2table1 |
| django_migrations |
+-------------------+
2 rows in set (0.00 sec)

mysql>

DB ルータで定義したような構成になっている確認します。うまくテーブルを作れていない場合は、DB ルータの migrate 関数を調整し、データベースの作成からやり直します。

tree

$ tree
.
├── app1
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── app2
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── multidb_app
    ├── __init__.py
    ├── db_router.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

5 directories, 22 files

ファイル構成はこのようになりました。

動作確認

runserver

$ python3.6 manage.py runserver 0.0.0.0:8000

まずは runserver を実行します。

http://192.168.33.10:8000/app1/

runserver を実行し、http://192.168.33.10:8000/app1/ にアクセスすると、「app1」と出力し、db_router を通って、db1  にレコードを作成します。

http://192.168.33.10:8000/app2/

http://192.168.33.10:8000/app2/ は、「app2」と出力し、db_router を通って、db2 にレコードを作成します。

select * from db1.app1_db1table1

mysql> select * from db1.app1_db1table1;
+----+---------+
| id | column1 |
+----+---------+
| 1 | app1 |
+----+---------+
1 row in set (0.00 sec)

mysql>

select * from db2.app2_db2table1

mysql> select * from db2.app2_db2table1;
+----+---------+
| id | column1 |
+----+---------+
| 1 | app2 |
+----+---------+
1 row in set (0.00 sec)

mysql>

まとめ

Django で複数 app 毎に db を接続しました。

settings.py の DATABASES に、複数の DB を定義し、DATABASE_ROUTERS に DB ルータを定義します。

この DB ルータはなかなか厄介ですが、ここをうまく定義できれば、views.py などの app 下では、複数 DB を全く意識することなく実装することができます。

次回は、この DB ルータを使って、マスタースレーブ 構成を実装して見たいと思います。

シェアする

フォローする