LoginSignup
8
7

More than 3 years have passed since last update.

【Docker】Djangoで複数データベース(MySQL) & django-jetの管理サイト & django-reversionによるテーブル変更履歴管理

Last updated at Posted at 2019-10-05

バージョン情報

Python:3.7.2
Django:2.2.5

pyenvで仮想環境の準備・適用

仮想環境作成

$ pyenv virtualenv 3.7.2 sample_project_env

仮想環境適用

$ cd /~省略~/sample_project
$ pyenv local sample_project_env

Djangoプロジェクトの作成

Djangoをインストール

$ pip install django==2.2.5

プロジェクト作成

$ mkdir sample_project ※sample_projectフォルダの直下にさらにsample_projectフォルダを作成
$ cd sample_project
$ django-admin startproject config .

※こんな感じになります。
image.png

dockerで各種コンテナ立ち上げ

docker-compose.yml

dbコンテナを2つ(db1, db2)立てていますが、CREATE DATABASE文を2つ記載したDDLをdocker-entrypoint-initdb.dにマウントすればdbコンテナ1つで複数データベースの構築はできますし、むしろそっちのが良いですw
(db_dataフォルダの中にDDLを入れればdocker-entrypoint-initdb.dにマウントされるようになってます。)

version: '2'
services:
  web:
    build:
      context: ../
      dockerfile: sample_project/Dockerfile
    container_name: sample_project
    volumes:
      - '../:/src'
    environment:
      - LC_ALL=ja_JP.UTF-8
    ports:
      - '8000:8000'
    depends_on:
      - db1
      - db2
      - redis
    entrypoint: sample_project/wait-for-it.sh db1 user user
    command: python3 sample_project/manage.py runserver 0.0.0.0:8000 --settings=config.settings
    tty: true

  redis:
    image: 'redis:4.0.8'
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - './redisdata:/data'

  db1:
    image: mariadb:latest
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=sample_project_1
      - MYSQL_USER=user
      - MYSQL_PASSWORD=user
    volumes:
      - db1_data:/var/lib/mysql
      - ./db1_data:/docker-entrypoint-initdb.d
    ports:
      - '3307:3306'

  db2:
    image: mariadb:latest
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=sample_project_2
      - MYSQL_USER=user
      - MYSQL_PASSWORD=user
    volumes:
      - db2_data:/var/lib/mysql
      - ./db2_data:/docker-entrypoint-initdb.d
    ports:
      - '3308:3306'

volumes:
  db1_data:
    driver: local
  db2_data:
    driver: local

Dockerfile

FROM ubuntu:18.04
RUN apt-get -y update \
    && apt-get install -y locales python3-pip python3.7 libmysqlclient-dev mysql-client\
    && mkdir /src \
    && rm -rf /var/lib/apt/lists/* \
    && echo "ja_JP UTF-8" > /etc/locale.gen \
    && locale-gen
WORKDIR /src
ADD ./ /src/
RUN LC_ALL=ja_JP.UTF-8 pip3 install -r sample_project/requirements.txt

wait-for-it.sh

#!/bin/sh

set -e

host="$1"
shift
user="$1"
shift
password="$1"
shift
cmd="$@"

echo "Waiting for mysql"
until mysql -h "$host" -u "$user" -p"$password" &> /dev/null
do
        >$2 echo -n "."
        sleep 1
done

>&2 echo "MySQL is up - executing command"
exec $cmd

requirements.txt

celery==4.2.1
cryptography==2.1.4
Django==2.2.5
django-celery-results==1.0.4
django-debug-toolbar==1.9.1
django-environ==0.4.5
django-jet==1.0.8
django-lint2==0.1
django-redis==4.10.0
django-reversion==3.0.4
mysqlclient==1.3.13
redis==3.2.0
requests==2.22.0
sqlparse==0.2.4
urllib3==1.25.3
vine==1.3.0
python-dateutil==2.8.0

コンテナ立ち上げ

$ docker-compose up --build -d
.
..
...
Successfully built aa2166b8e593
Successfully tagged sample_project_web:latest
Creating sample_project_db1_1   ... done
Creating sample_project_db2_1   ... done
Creating sample_project_redis_1 ... done
Creating sample_project         ... done

Djangoの起動画面確認

OK!!!
image.png

複数データベースの作成

モデル作成用にアプリケーション作成

$ python3 manage.py startapp app1
$ python3 manage.py startapp app2

モデル作成

app1/models.py
from django.db import models

# Create your models here.
class Db1Table1(models.Model):
    column1 = models.CharField(max_length=10)

    def __str__(self):
        return self.column1
app2/models.py
from django.db import models

# Create your models here.
class Db2Table1(models.Model):
    column1 = models.CharField(max_length=10)

    def __str__(self):
        return self.column1

Database routerを作成

データベースルーター(Database router)を設定すると、アプリケーション毎にデータベースを切り替えることができます。
(app1がdb1、app2がdb2といった感じに。)

config/db_router.py
class DbRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'app1':
            return 'db1'
        if model._meta.app_label == 'app2':
            return 'db2'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'app1':
            return 'db1'
        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 == 'app1':
            return db == 'db1' or db == 'default'
        if app_label == 'app2':
            return db == 'db2'
        return None

.envにDB接続情報を記載

DEBUG=True

DATABASE_URL_1=mysql://user:user@db1:3306/sample_project_1
DATABASE_URL_2=mysql://user:user@db2:3306/sample_project_2

settings.pyを編集

config.settings.py
.
..
...
import os
from config import environ # 追加
.
..
...
 # 追加
env_file = os.path.join(BASE_DIR, '.env')
env = environ.Env()
env.read_env(env_file)
.
..
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app1', # 追加
    'app2', # 追加
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
.
..
...
DATABASES = {
    'default': env.db_url(var='DATABASE_URL_1'),
    'db1': env.db_url(var='DATABASE_URL_1'),
    'db2': env.db_url(var='DATABASE_URL_2'),
}
# 作成したDatabase routerを設定
DATABASE_ROUTERS = ['config.db_router.DbRouter']
.
..
...
# ついでにLANGUAGE_CODE、TIME_ZONE、USE_I18N、USE_L10N、USE_TZも書き換える
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = False
.
..
...

マイグレーションファイル作成

ホストマシンからはマイグレーション操作が失敗するので、一旦dockerで立ち上げたwebコンテナに潜ってからマイグレーションファイルを作成。

$ docker exec -it sample_project /bin/bash
# cd sample_project/
# python3 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
#

マイグレート実行(DBに各種テーブル作成)

マイグレートは2回に分けて実行します。
▼1回目
→db1にapp1のモデルに定義したテーブルとDjangoの管理テーブルを作成します。

# python3 manage.py migrate

▼実行結果

# python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, app1, app2, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying app1.0001_initial... OK
  Applying app2.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
#

▼2回目
→db2にapp2のモデルに定義したテーブルとDjangoの管理テーブルを作成します。
→ここではマイグレートを実行するデータベースを指定しています。
(データベースを指定しない場合、settings.pyの'default'に指定したデータベースが使用されてしまいます。)

# python3 manage.py migrate --database=db2

▼実行結果

# python3 manage.py migrate --database=db2
Operations to perform:
  Apply all migrations: admin, app1, app2, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying app1.0001_initial... OK
  Applying app2.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
#

データベース確認

データベースごとに作成するテーブルを分けることができました。
image.png

image.png

スーパーユーザーの作成

ユーザー名とパスワードは「root」にしました。
(adminとかにしたら管理サイトには入れなくなったりしたのでrootが良さげ(原因不明))

# python3 manage.py createsuperuser
ユーザー名 (leave blank to use 'root'): root
メールアドレス: root@root.jp
Password:
Password (again):
このパスワードは ユーザー名 と似すぎています。
このパスワードは短すぎます。最低 8 文字以上必要です。
このパスワードは一般的すぎます。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

もう一個のデータベースにも作成したいなら同様にデータベースを指定してあげればOK

# python3 manage.py createsuperuser --database=db2

Django管理サイト確認

管理サイトに表示するテーブルを指定する

app1/admin.py
from django.contrib import admin
from app1.models import Db1Table1

# Register your models here.
admin.site.register(Db1Table1)
app2/admin.py
from django.contrib import admin
from app1.models import Db2Table1

# Register your models here.
admin.site.register(Db2Table1)

管理サイト表示

http://localhost:8000/admin
に接続
image.png
image.png

問題なく表示され、管理サイトからテーブルへのデータ追加等も行うことができました。

管理サイトのテンプレートにdjango-jetを使用する

django-jetのreadme

settings.pyを編集

config.settings.py
.
..
...
INSTALLED_APPS = [
    'jet.dashboard', # django.contrib.adminとjetより前に記載する
    'jet', # django.contrib.adminより前に記載する
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app1',
    'app2',
]
...
..
.

urls.pyを編集する

config.urls.py
from django.conf.urls import url, include # 追加
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^jet/', include('jet.urls', 'jet')), # 追加
    url(r'^jet/dashboard/', include('jet.dashboard.urls', 'jet-dashboard')), # 追加
]

jetとdashboardのマイグレートを実行する

例のごとく一度dockerで立ち上げたwebコンテナに潜ってから

$ docker exec -it sample_project /bin/bash
# cd sample_project/

jetのマイグレート

# python3 manage.py migrate jet
Operations to perform:
  Apply all migrations: jet
Running migrations:
  Applying jet.0001_initial... OK
  Applying jet.0002_delete_userdashboardmodule... OK

dashboardのマイグレート

# python3 manage.py migrate dashboard
Operations to perform:
  Apply all migrations: dashboard
Running migrations:
  Applying dashboard.0001_initial... OK

Djangoの管理サイトがかっこよくなります。

image.png

image.png

django-reversionで変更履歴管理

django-reversion導入前の変更履歴ページはこんな感じ
ドキュメントはココ

image.png

settings.pyを編集

settings.py
.
..
...
INSTALLED_APPS = [
    'jet.dashboard',
    'jet',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reversion', # 追加
    'app1',
    'app2',
]
...
..
.

マイグレート実行

# python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, app1, app2, auth, contenttypes, dashboard, jet, reversion, sessions
Running migrations:
  Applying reversion.0001_squashed_0004_auto_20160611_1202... OK

app1とapp2のadmin.pyを編集

app1/admin.py
from django.contrib import admin
from reversion.admin import VersionAdmin
from app1.models import Db1Table1

# Register your models here.
@admin.register(Db1Table1)
class Db1Table1Admin(VersionAdmin):
    pass
app2/amin.py
from django.contrib import admin
from reversion.admin import VersionAdmin
from app2.models import Db2Table1

# Register your models here.
@admin.register(Db2Table1)
class Db2Table1Admin(VersionAdmin):
    pass

管理サイトの変更履歴ページを確認

「日付/時刻」がリンクになりました。
image.png

押してみます。
image.png

最後に

django-jetの管理サイトはカスタマイズできるようなので適当にググってカスタマイズしてみてください!

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7