Django-Mysql-Docker

## Djangoを始める
```docker:Dockerfile
FROM python:3.9.10
ENV PYTHONUNBUFFERED 1
RUN apt-get update && apt-get -y install default-mysql-client
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
COPY . /code/
```

```yml:docker-compose.yml
version: '3'
services:
web:
    build: .
    container_name: djangoapp
    volumes:
      - .:/code
    ports:
      - "8000:8000"

volumes:
  data-volume:
```
```txt:requirements.txt
Django==3.2
mysqlclient
```

```python:ターミナル
% docker-compose run web django-admin startproject アプリ名
```

docker-compose.ymlにcommandを追加して、webを立ち上げる.
```yml:docker-compose.yml
version: '3'
services:
  web:
    build: .
    container_name: djangoapp
    command: python3 myapp/manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
      - 
volumes:
  data-volume:
```
### mysqlを追加
docker-compose.ymlにmysqlを追加。
myapp/mysqlにDockerfileとmy.cnfを追加
```yml:docker-compose.yml
version: '3'
services:
  db:
    build:
      context: .
      dockerfile: myapp/mysql/Dockerfile
    platform: linux/x86_64
    container_name: djangoapp_mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: djangoapp-db
      MYSQL_USER: djangoapp
      MYSQL_PASSWORD: djangoapp
      TZ: 'Asia/Tokyo'
    volumes:
      - data-volume:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
  web:
    build: .
    container_name: djangoapp
    command: python3 myapp/manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  data-volume:
```
```dockerfile:myapp/mysql/Dockerfile
FROM mysql:5.7

COPY myapp/mysql/my.cnf /etc/mysql/conf.d/my.cnf

ENV LC_ALL ja_JP.UTF-8

ENV TZ Asia/Tokyo
ENV LANG=ja\_JP.UTF-8

CMD ["mysqld"]

EXPOSE 3306
```
```ruby:myapp/mysql/my.cnf
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character_set_server=utf8mb4
collation_server=utf8mb4_bin
# タイムゾーンの設定
default_time_zone=SYSTEM
log_timestamps=SYSTEM
# MySQL8.0以上用のデフォルト認証プラグインの設定
default_authentication_plugin=mysql_native_password
# mysqlオプションの設定
[mysql]
# 文字コードの設定
default_character_set=utf8mb4
# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default_character_set=utf8mb4
```

## コンテナ内に入り、サーバーを起動。管理者登録
```python:ターミナル
% docker container exec -it コンテナ名 bash

% python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
・・・・・・
Quit the server with CONTROL-C.

% python manage.py createsuperuser
Username (leave blank to use 'root'): 
Email address: 
Password: 
Password (again): 
Superuser created successfully.
```

## mysqlを導入


```python:ターミナル 
% docker container exec -it mysqlコンテナ名 bash

% env    
PATH=
MYSQL_DATABASE=
SHLVL=
TERM=
GOSU_VERSION=
MYSQL_VERSION=
HOME=
MYSQL_USER=
MYSQL_PASSWORD=
TZ=
MYSQL_ROOT_PASSWORD=
PWD=
HOSTNAME=
MYSQL_MAJOR=

% hostname -i
IPアドレス

% mysql -h IPアドレス -P PROT -u root -p
Enter password: ##MYSQL_ROOT_PASSWORD
Welcome to the MySQL monitor.  
・・・・・・
mysql> quit
Bye 
```

## mysqlへのアクセスが拒否される。
```python:ターミナル
% docker container exec -it コンテナ名 bash
% python manage.py test polls
Traceback (most recent call last):
 ・・・・・・
MySQLdb.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
・・・・・・
django.db.utils.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")
```

```python:ターミナル
% python manage.py dbshell
CommandError: You appear not to have the 'mysql' program installed or on your path.

% mysql
bash: mysql: command not found

% apt-get -y install default-mysql-client
・・・・・・
E: Unable to locate package default-mysql-client

% apt-get update
・・・・・・        

% apt-get -y install default-mysql-client
・・・・・・

% mysql
ERROR 2002 (HY000): Cant connect to local MySQL server through socket /run/mysqld/mysqld.sock (2)

% docker container exec -it コンテナ名 bash
% hostname -i
IPアドレス

% exit
exit

% python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
Exception in thread django-main-thread:
Traceback (most recent call last):
・・・・・・
MySQLdb.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
・・・・・・
django.db.utils.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")
```

コンテナ内からMysqlのデータベースにはアクセスできることを確認
```python:ターミナル
% docker container exec -it コンテナ名 bash
% mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

% mysql -h IPアドレス -P 3306 -u root -p
Enter password: 
Welcome to the MySQL monitor.  
・・・・・・

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| django-db          |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.03 sec)

mysql> use django-db
Database changed
mysql> show tables;
Empty set (0.01 sec)

mysql> quit
Bye

% exit
exit
```

migrateできない
```python:ターミナル
% docker container exec -it コンテナ名 bash
% python manage.py migrate
Traceback (most recent call last):
・・・・・・
MySQLdb.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
・・・・・・
django.db.utils.OperationalError: (1044, "Access denied for user 'django'@'%' to database 'django_test_db_1'")

% python manage.py dbshell
ERROR 1044 (42000): Access denied for user 'django'@'%' to database 'django_test_db_1'
CommandError: "mysql --user=django --password=django --host=db --port=3306 django_test_db_1" returned non-zero exit status 1.

% hostname -i
IPアドレス

% mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

% mysql --user=root --password=root --host=IPアドレス --port=ポート番号
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 5.7.35 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> exit
Bye
```

設定を変更
```python:settings.py
% NAMEを削除
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django-db',   # docker-compose.ymlファイルに合わせる
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': 'db',
        'PORT': '3306',
        'TEST': {
            'NAME': "test_django_test_db_1",
            'MIRROR': "default",
        },
    }
}
```

```python:ターミナル
% docker container exec -it コンテナ名 bash

% python manage.py dbshell
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.7.35 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> exit
Bye

% python manage.py test polls
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests.test_was_published_recently_with_future_question)
was_published_recently() returns False for questions whoes pub_date is in the future.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/code/polls/tests.py", line 16, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (failures=1)
```

### migrateエラー
```python:ターミナル
% python manage.py makemigrations accounts
Migrations for 'accounts':
  accounts/migrations/0001_initial.py
    - Create model CustomUser
    - 
% python manage.py migrate
Traceback (most recent call last):
  ・・・・・・
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database 'default'.
```

adminをコメントアウトする。
```python:ターミナル
% python manage.py migrate
Operations to perform:
  Apply all migrations: accounts, auth, contenttypes, sessions
Running migrations:
  Applying accounts.0001_initial... OK
```

adminのコメントアウトを外す
```python:ターミナル
% python manage.py migrate
Operations to perform:
  Apply all migrations: accounts, admin, auth, contenttypes, sessions
Running migrations:
  No migrations to apply.
```

## Mysql 永続化
```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: django-db
      MYSQL_USER: django
      MYSQL_PASSWORD: django
      TZ: 'Asia/Tokyo'
    volumes:
      - data-volume:/var/lib/mysql
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
  web:
    build: .
    command: python3 manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  data-volume:
```

## DB接続エラー
```ruby:ターミナル
python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  No migrations to apply.
root@コンテナID:/code# python manage.py test polls
Creating test database for alias 'default'...
Got an error creating the test database: (1044, "Access denied for user 'django'@'%' to database 'test_django-db'")
root@コンテナID:/code# exit
exit

# 修正が適応されていない可能性があるため、Dockerを止める。
% docker-compose stop
% docker-compose down

# 再度Dockerを立ち上げる
docker-compose up -d
Creating network "django_test_default" with the default driver
Creating django_test_db_1 ... done
Creating django_test_web_1 ... done

# dockerコンテナ内に入る
docker container exec -it コンテナID bash
root@コンテナID:/code# python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, 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 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 auth.0012_alter_user_first_name_max_length... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK
root@コンテナID:/code# 
```

## Mysqlを日本語にする
mysqlのDockerfileを作成する
```docker:mysql/Dockerfile
FROM mysql:5.7

COPY myapp/mysql/my.cnf /etc/mysql/conf.d/my.cnf

RUN apt-key adv --keyserver pgp.mit.edu --recv-keys 467B942D3A79BD29
RUN apt-get update
RUN apt-get install -y locales
RUN sed -i -e 's/# \(ja_JP.UTF-8\)/\1/' /etc/locale.gen
RUN locale-gen
RUN update-locale LANG=ja_JP.UTF-8

ENV LC_ALL ja_JP.UTF-8

ENV TZ Asia/Tokyo
ENV LANG=ja\_JP.UTF-8

CMD ["mysqld"]

EXPOSE 3306
```

```ruby:my.cnf
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character_set_server=utf8mb4
collation_server=utf8mb4_bin
# タイムゾーンの設定
default_time_zone=SYSTEM
log_timestamps=SYSTEM
# MySQL8.0以上用のデフォルト認証プラグインの設定
default_authentication_plugin=mysql_native_password
# mysqlオプションの設定
[mysql]
# 文字コードの設定
default_character_set=utf8mb4
# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default_character_set=utf8mb4
```
docker-compose.ymlを修正
```yml:docker-compose.yml
version: '3'
services:
  db:
    # imageをDockerfileから取得する。
    # image: mysql:5.7
    build:
      context: .
      dockerfile: myapp/mysql/Dockerfile
    platform: linux/x86_64
    container_name: djangoapp_mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: djangoapp-db
      MYSQL_USER: djangoapp
      MYSQL_PASSWORD: djangoapp
      TZ: 'Asia/Tokyo'
    volumes:
      - data-volume:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
  web:
    build: .
    container_name: djangoapp
    command: python3 myapp/manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  data-volume:
```

settings.pyにOPTIONSを追加する。
```python:settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangoapp-db',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': 'db',
        'PORT': '3306',
        'OPTIONS': {
            'charset': 'utf8mb4', #OPTIONSを追加する
        },
    }
}
```

## 国際化
```python: ターミナル
% django-admin makemessages -l ja
CommandError: Can't find msguniq. Make sure you have GNU gettext tools 0.15 or newer installed.
```

Dockerfileに追記してgettextをインストール
```Dockerfile:Dockerfile
RUN apt-get update && apt-get install -y gettext libgettextpo-dev
```

コンテナ外からコマンドを入力
```python: ターミナル
docker-compose exec web django-admin makemessages -l ja
```






## 参考資料
### Docker
[Dockerを用いてDjangoの開発環境を構築してみる](https://inglow.jp/techblog/docker-django/)
[[課題] CommandError: You appear not to have the 'mysql' program installed or on your path. と出る](https://zenn.dev/wtkn25/articles/django-mysqlclient-install)
[dockerからmysqlに接続できない【ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)】](https://qiita.com/KOBA-RYOTA/items/3cf5070b54845e151034)
[docker-composeでMySQLに接続する](https://qiita.com/M_Nagata/items/120831bb4e4a3deace13)
[【django自動テスト】接続エラー Got an error creating the test database](https://qiita.com/HyunwookPark/items/89772612cbc5783cac7f)
[docker-compose.ymlを設定しMySQLのコンテナを操作する](https://do-do-tech.com/docker-compose-yml%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97mysql%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B/)
[Django カスタムユーザーのmigrateができない](https://qlitre-weblog.com/cant-migrate-django-custom-user/)
[Python：Can’t find msguniq. Make sure you have GNU gettext tools 0.15 or newer installed.](https://qol-kk.com/wp2/blog/2021/01/28/post-2501/)
[Dockerで「 Can't find msgfmt.」の対処法](https://blogdodo.com/tatsuyeee/blogs/1595/)

### MySQL
[Django+MySQLの開発環境をdocker-composeで構築する](https://qiita.com/bakupen/items/f23ce3d2325b4491a2dd)
[DockerでDjango+mysql環境の構築＆プロジェクト作成](https://zenn.dev/agepan/articles/docker-article-001)
[【django】Dockerで簡単に開発環境を構築【MySQL】](https://self-methods.com/django-docker-easy/)
[docker-compose.ymlの書き方について解説してみた](https://qiita.com/yuta-ushijima/items/d3d98177e1b28f736f04)
[DockerでPython実行環境を作ってみる](https://qiita.com/jhorikawa_err/items/fb9c03c0982c29c5b6d5)
[【入門】DockerでMySQL環境を構築する方法とデータの永続化手順まとめ](https://www.kagoya.jp/howto/cloud/container/dockermysql/)
[Docker 上で MySQL データを永続化する](https://kiyobl.com/docker-mysql-persistence/)

### DB
[立ち上げたMySQLのDockerコンテナでAccess deniedされた時の対処](https://zenn.dev/tojima/articles/32bbfe85dd0022)

### 日本語対応
[Docker+Django+Nginx+MySQL+Gunicornで環境構築~M1Mac対応](https://qiita.com/shun198/items/f6864ef381ed658b5aba)
[Docker mysql NO_PUBKEYエラー時の対処（Dockerfileに記載する方法）＋apt-get updateできない場合](https://zenn.dev/kitsune_love/articles/fe3fc90ca79057)
[Docker環境のmysqlで日本語入力ができないときの対処法](https://qiita.com/shin0909/items/3193fd96f90f2b5f758c)
[Docker mysqlコンテナで日本語入力できない](https://qiita.com/tomo__x_/items/bd5d86d5f423f8f89b22)
[DockerのMySQLで日本語が使えない！？](https://tech.anti-pattern.co.jp/docker-no-mysql-de-japanese/)