Django + MySQLをDockerで動かしたい ~tips編~

DockerでDjango + MySQLの構成で開発をしたときに困ったあれこれをメモとして残していきます。

環境構築はたくさん記事があると思うので、環境構築の手順はそちらに託すことにします。

DBにデータが登録できない(文字コードが違う)

adminからデータ登録をしようとした時に遭遇したエラーです。

エラー文と問題詳細

(1267, "Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '='")

ざっくり言うと、文字コードが違うのでデータ登録ができないと怒られています。
MySQLに入って

mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR  Variable_name LIKE 'collation%';

を実行すると、character_set_*latin1collation_*latin1_sweden_ci が入ってると思います。
シングルバイトの文字コードが指定されているところに、漢字などマルチバイトのデータを登録しようとしてエラーが出ています。

解決方法

クライアント側(Django)とサーバ側(MySQL)とでそれぞれ設定が必要です。

クライアント側(Django)の設定

app/settings.py のDBの設定欄にOPTIONSで文字コードを指定します。

# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': 'db',
        'PORT': 3306,
        'OPTIONS': {
            'charset': 'utf8mb4'
        }
    }
}
サーバ側の設定

docker runの時にオプションを指定する方法もあるのですが、今回はdocker-compose.yamlcommandとして指定します。

  db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    container_name: mysql
    volumes:
      - ./database/data:/var/lib/mysql
    ports:
      - 3333:3306
    environment:
      MYSQL_DATABASE: 'django'
      MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
    platform:
      linux/amd64

これでOKです!
あとはdockerを立ち上げ直せばうまくいくはずです!


Models.pyでFunctionKeyを使っているとうまく変更できない場合があります。
その時は、FunctionKeyを使っているモデル(テーブル)をコメントアウトして、

root@123456:/django# python manage.py makemigrations
root@123456:/django# python manage.py migrate

をして設定変更を適用した後にコメントアウトを解除してまたmigrateするとうまくいきます。

マスタデータ・テストデータを共有したい(seedしたい)

adminからデータ登録もできるのですが、一括登録したい場合面倒です。
テストデータの共有ができればチーム開発も楽になりますね。

TLDR

app/fixures配下にテストデータ用のjsonを作って、次のコマンドでポストします。

python manage.py loaddata app/fixtures/test_data.json

データが入っていることは、mysqlを起動してSELECT文を実行するか、Djangoのadmin画面から確認できます。

Models.pyにテーブル定義

app/models.pyに下記のテーブルを定義したとします。

from django.db import models

class User(models.Model):
    GENDER = (
        (1, 'male'),
        (2, 'female')
    )
    family_name = models.CharField(max_length=50)
    first_name = models.CharField(max_length=50)
    gender = models.IntegerField(choices=GENDER)
    birthday = models.DateField()

    def __str__(self):
        return f'{self.id}: {self.family_name} {self.first_name}'

test_data.jsonを作成

テストデータ投入用のJSONファイルを作成します。

[
    {
        "model": "app.user",
        "pk": 1,
        "fields": {
            "familiy_name" : "田中",
            "first_name": "太郎",
            "gender": 1,
            "birthday": 1995-01-01
        }
    },
    {
        "model": "app.user",
        "pk": 2,
        "fields": {
            "familiy_name" : "後藤",
            "first_name": "花子",
            "gender": 2,
            "birthday": 1975-10-26
        }
    }
]

データ投入

次のコマンドでポストします。

python manage.py loaddata app/fixtures/test_data.json

データが入っていることは、mysqlを起動してSELECT文を実行するか、Djangoのadmin画面から確認できます。


参考