[Django3.0]mysqlで日本語入力できない時2

$ sudo vi /etc/mysql/my.cnf

!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/

[mysqld]
character-set-server=utf8mb4
skip-character-set-client-handshake
default-storage-engine=INNODB

[client]
# default-character-set=utf8mb4

[mysqldump]
default-character-set=utf8

[mysql]
# default-character-set=utf8

$ sudo /etc/init.d/mysql restart
$ mysql -u root -p
mysql> use hanbai;

mysql> insert into sales_master (name, office, zipcode, prefecture, address, tel, fax, mail, name_top, position_top, created_at, updated_at) values (‘東京テクノロジー株式会社’, ‘本社’, ‘100-6321’, ‘東京都’, ‘千代田区丸の内2-4-1′, ’03-1234-5678′, ’03-1234-5679’, ‘info@tokyotech.com’, ‘山田太郎’, ‘代表取締役’, ‘2020-08-22 15:00:00’, ‘2020-08-22 15:00:00’);
Query OK, 1 row affected (0.01 sec)

mysql> select * from sales_master;
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+
| id | name | office | zipcode | prefecture | address | tel | fax | mail | name_top | position_top | created_at | updated_at |
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+
| 3 | | | 100-6321 | | 2-4-1 | 03-1234-5678 | 03-1234-5679 | info@tokyotech.com | | | 2020-08-22 15:00:00.000000 | 2020-08-22 15:00:00.000000 |
| 4 | 東京テクノロジー株式会社 | 本社 | 100-6321 | 東京都 | 千代田区丸の内2-4-1 | 03-1234-5678 | 03-1234-5679 | info@tokyotech.com | 山田太郎 | 代表取締役 | 2020-08-22 15:00:00.000000 | 2020-08-22 15:00:00.000000 |
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+

あれ? 普通に出来たな。さっきまで出来なかったのに。。

mysql> TRUNCATE table sales_master;
Query OK, 0 rows affected (0.04 sec)

mysql> select * from sales_master;
Empty set (0.00 sec)

mysql> insert into sales_master (name, office, zipcode, prefecture, address, tel, fax, mail, name_top, position_top, created_at, updated_at) values (‘東京テクノロジー株式会社’, ‘本社’, ‘100-6321’, ‘東京都’, ‘千代田区丸の内2-4-1′, ’03-1234-5678′, ’03-1234-5679’, ‘info@tokyotech.com’, ‘山田太郎’, ‘代表取締役’, ‘2020-08-22 15:00:00’, ‘2020-08-22 15:00:00’);
Query OK, 1 row affected (0.00 sec)

mysql> select * from sales_master;
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+
| id | name | office | zipcode | prefecture | address | tel | fax | mail | name_top | position_top | created_at | updated_at |
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+
| 1 | 東京テクノロジー株式会社 | 本社 | 100-6321 | 東京都 | 千代田区丸の内2-4-1 | 03-1234-5678 | 03-1234-5679 | info@tokyotech.com | 山田太郎 | 代表取締役 | 2020-08-22 15:00:00.000000 | 2020-08-22 15:00:00.000000 |
+—-+————————————–+——–+———-+————+—————————-+————–+————–+——————–+————–+—————–+—————————-+—————————-+
1 row in set (0.00 sec)

OK
次はこの入力された値をDjangoで表示させたい。

[Django3.0]mysqlに日本語入力できない

migrationで作成したカラム
mysql> describe sales_master;
+————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| office | varchar(255) | YES | | NULL | |
| zipcode | varchar(8) | NO | | NULL | |
| prefecture | varchar(20) | NO | | NULL | |
| address | varchar(255) | NO | | NULL | |
| tel | varchar(15) | NO | | NULL | |
| fax | varchar(15) | YES | | NULL | |
| mail | varchar(255) | NO | | NULL | |
| name_top | varchar(255) | NO | | NULL | |
| position_top | varchar(100) | YES | | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+————–+————–+——+—–+———+—————-+
13 rows in set (0.00 sec)

これに手動でデータを挿入します。
mysql> insert into sales_master (name, office, zipcode, prefecture, address, tel, fax, mail, name_top, position_top) values (‘東京テクノロジー株式会社’, ‘本社’, ‘100-6321’, ‘東京都’, ‘千代田区丸の内2-4-1′, ’03-1234-5678′, ’03-1234-5679’, ‘info@tokyotech.com’, ‘山田太郎’, ‘代表取締役’);
ERROR 1366 (HY000): Incorrect string value: ‘\xE6\x9D\xB1\xE4\xBA\xAC…’ for column ‘name’ at row 1

mysql> show variables like “chara%”;
+————————–+—————————-+
| Variable_name | Value |
+————————–+—————————-+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+————————–+—————————-+
8 rows in set (0.01 sec)

$ sudo vi /etc/mysql/my.cnf

[mysqld]
character-set-server=utf8
skip-character-set-client-handshake
default-storage-engine=INNODB

[mysqldump]
default-character-set=utf8

[mysql]
# default-character-set=utf8

$ sudo /etc/init.d/mysql restart
mysql> status;
————–
mysql Ver 14.14 Distrib 5.7.31, for Linux (x86_64) using EditLine wrapper

Connection id: 2
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ”
Using delimiter: ;
Server version: 5.7.31-0ubuntu0.18.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 24 sec

Threads: 1 Questions: 5 Slow queries: 0 Opens: 105 Flush tables: 1 Open tables: 98 Queries per second avg: 0.208
————–

$ sudo apt-get install language-pack-ja
$ sudo update-locale LANG=ja_JP.UTF-8

だめだ、万事休す
adminから入力できるようにする
/sales/admin.py

from django.contrib import admin
from .models import Master

admin.site.register(Master)

mysql> insert into sales_master (name, office, zipcode, prefecture, address, tel, fax, mail, name_top, position_top, created_at, updated_at) values (”, ”, ‘100-6321’, ”, ‘2-4-1′, ’03-1234-5678′, ’03-1234-5679’, ‘info@tokyotech.com’, ”, ”, ‘2020-08-22 15:00:00’, ‘2020-08-22 15:00:00’);
Query OK, 1 row affected (0.00 sec)

mysql> select * from sales_master;
+—-+——+——–+———-+————+———+————–+————–+——————–+———-+————–+—————————-+—————————-+
| id | name | office | zipcode | prefecture | address | tel | fax | mail | name_top | position_top | created_at | updated_at |
+—-+——+——–+———-+————+———+————–+————–+——————–+———-+————–+—————————-+—————————-+
| 3 | | | 100-6321 | | 2-4-1 | 03-1234-5678 | 03-1234-5679 | info@tokyotech.com | | | 2020-08-22 15:00:00.000000 | 2020-08-22 15:00:00.000000 |
+—-+——+——–+———-+————+———+————–+————–+——————–+———-+————–+—————————-+—————————-+
1 row in set (0.00 sec)

何故だ。。。取り敢えず頭を冷やそう。

[Django3.0]migrationしてテーブル作成する手順

Djangoでmigrationを実行するには、
1.Models.pyにテーブル名とカラムを記載
2.migrationファイルの作成
3.マイグレーションで実行されるSQL確認
4.マイグレーションの実行

その為、まずUIを元にデータ型を整理します。

UI

データ型

データ型を元にmodels.pyを書いていきます。
/sales/models.py

from django.db import models

class Master(models.Model):
	name = models.CharField(max_length=255)
	office = models.CharField(max_length=255, null=True)
	zipcode = models.CharField(max_length=8)
	prefecture = models.CharField(max_length=20)
	address = models.CharField(max_length=255)
	tel = models.CharField(max_length=15)
	fax = models.CharField(max_length=15, null=True)
	mail = models.EmailField(max_length=255)
	name_top = models.CharField(max_length=255)
	position_top = models.CharField(max_length=100, null=True)
	created_at = models.DateTimeField(auto_now_add=True)
	updated_at = models.DateTimeField(auto_now=True)

	def __str__(self):
		return self.name

– max_lengthは基本はvarchar(255)とします
– 郵便番号、TEL、FAXはハイフンなしであればIntegerですが、ハイフンありで入力する場合もあるのでCharFieldにしておきます
– null可のカラムは null=Trueと書いておきます
– __str__の使い方がよくわからないので、とりあえずreturn self.nameとしておきます。

$ python manage.py makemigrations sales

/sales/migrations/0001_initial.py

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Master',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=255)),
                ('office', models.CharField(max_length=255, null=True)),
                ('zipcode', models.CharField(max_length=8)),
                ('prefecture', models.CharField(max_length=20)),
                ('address', models.CharField(max_length=255)),
                ('tel', models.CharField(max_length=15)),
                ('fax', models.CharField(max_length=15, null=True)),
                ('mail', models.EmailField(max_length=255)),
                ('name_top', models.CharField(max_length=255)),
                ('position_top', models.CharField(max_length=100, null=True)),
                ('created_at', models.DateField(auto_now_add=True)),
                ('updated_at', models.DateField(auto_now=True)),
            ],
        ),
    ]

$ python manage.py migrate
mysql> show tables;
+—————————-+
| Tables_in_hanbai |
+—————————-+
| 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 |
| sales_master |
+—————————-+
11 rows in set (0.00 sec)

mysql> describe sales_master;
+————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| office | varchar(255) | YES | | NULL | |
| zipcode | varchar(8) | NO | | NULL | |
| prefecture | varchar(20) | NO | | NULL | |
| address | varchar(255) | NO | | NULL | |
| tel | varchar(15) | NO | | NULL | |
| fax | varchar(15) | YES | | NULL | |
| mail | varchar(255) | NO | | NULL | |
| name_top | varchar(255) | NO | | NULL | |
| position_top | varchar(100) | YES | | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+————–+————–+——+—–+———+—————-+
13 rows in set (0.00 sec)

tableの作成方法まできました。
table nameはアプリケーション名が先頭に付くので、sales_masterになっていることがわかります。ER図のtable名も直しておきます。
続いて、手動でデータをinsertして、表示するところまで行きたい。

[Django3.0]migrationしていこう

auth_userの構造がわかったので、appの方で実装していきます。

mysql> create database hanbai;
Query OK, 1 row affected (0.00 sec)

/hanbai/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myapp',
        'USER': 'root',
        'PASSWORD': '***',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

$ python manage.py makemigrations sales
No changes detected in app ‘sales’
$ python manage.py migrate

mysql> show tables;
+—————————-+
| Tables_in_hanbai |
+—————————-+
| 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 |
+—————————-+
10 rows in set (0.00 sec)

$ python manage.py createsuperuser
Username (leave blank to use ‘vagrant’): admin
Email address: admin@gmail.com
Password:
Password (again):
Superuser created successfully.

$ python manage.py runserver 192.168.33.10:8000

aplicationのデータ構造上、hasManyの親となるテーブルから順番にmigrationしていく。

自社基本情報を入れるmasterテーブルから作っていく。

[Django3.0]groupとpermissionの構造

Tokyoというgroupをadminから追加します。
groupのcrudのpermissionを付けます。

mysql> select * from auth_group;
+—-+——-+
| id | name |
+—-+——-+
| 1 | tokyo |
+—-+——-+
1 row in set (0.00 sec)

mysql> select * from auth_group_permissions;
+—-+———-+—————+
| id | group_id | permission_id |
+—-+———-+—————+
| 1 | 1 | 9 |
| 2 | 1 | 10 |
| 3 | 1 | 11 |
| 4 | 1 | 12 |
+—-+———-+—————+
4 rows in set (0.00 sec)

ユーザに作成したgroupを紐づけます。

この時、このユーザはグループのpermissionと個別のpermissionが付いてます。
mysql> select * from auth_user_groups;
+—-+———+———-+
| id | user_id | group_id |
+—-+———+———-+
| 1 | 2 | 1 |
+—-+———+———-+
1 row in set (0.00 sec)

グループのpermissionと個別のpermissionに上下関係はなく、両方のpermissionが与えられている状態となります。

ER図で作成した通りですね。ER図を書くと理解のスピードがかなり上がります。

auth_userとgroup、permissionの関係性も理解しました。
さて、いよいよマイグレーションを実装していきましょうか。
ログイン機能は後から対応したいと思います。

[Django3.0]auth_permissionの構造

mysql> select * from auth_permission;
+—-+————————-+—————–+——————–+
| id | name | content_type_id | codename |
+—-+————————-+—————–+——————–+
| 1 | Can add log entry | 1 | add_logentry |
| 2 | Can change log entry | 1 | change_logentry |
| 3 | Can delete log entry | 1 | delete_logentry |
| 4 | Can view log entry | 1 | view_logentry |
| 5 | Can add permission | 2 | add_permission |
| 6 | Can change permission | 2 | change_permission |
| 7 | Can delete permission | 2 | delete_permission |
| 8 | Can view permission | 2 | view_permission |
| 9 | Can add group | 3 | add_group |
| 10 | Can change group | 3 | change_group |
| 11 | Can delete group | 3 | delete_group |
| 12 | Can view group | 3 | view_group |
| 13 | Can add user | 4 | add_user |
| 14 | Can change user | 4 | change_user |
| 15 | Can delete user | 4 | delete_user |
| 16 | Can view user | 4 | view_user |
| 17 | Can add content type | 5 | add_contenttype |
| 18 | Can change content type | 5 | change_contenttype |
| 19 | Can delete content type | 5 | delete_contenttype |
| 20 | Can view content type | 5 | view_contenttype |
| 21 | Can add session | 6 | add_session |
| 22 | Can change session | 6 | change_session |
| 23 | Can delete session | 6 | delete_session |
| 24 | Can view session | 6 | view_session |
| 25 | Can add friend | 7 | add_friend |
| 26 | Can change friend | 7 | change_friend |
| 27 | Can delete friend | 7 | delete_friend |
| 28 | Can view friend | 7 | view_friend |
+—-+————————-+—————–+——————–+

mysql> describe auth_user_groups;
+———-+———+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+———+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| group_id | int(11) | NO | MUL | NULL | |
+———-+———+——+—–+———+—————-+
3 rows in set (0.00 sec)

user_id 2のユーザにcan view userとcan add userのpermissionを追加します。

mysql> select * from auth_user_user_permissions;
+—-+———+—————+
| id | user_id | permission_id |
+—-+———+—————+
| 2 | 2 | 13 |
| 1 | 2 | 16 |
+—-+———+—————+
2 rows in set (0.00 sec)

なるほど、一つのpermissionごとに一つのレコードが増えていく構造です。
ER図を書き直します。

auth_permissionは各テーブルのadd, change, delete, viewの権限があり、userやgroupと紐づけられる構造になっています。

テーブル構造は理解したが、念の為groupの挙動も確認します。

[Django3.0]createsuperuserとauth_userテーブルの関係

コマンドでcreatesuperuserをすると、auth_userテーブルはどうなるのか

$ python manage.py createsuperuser
Username (leave blank to use ‘vagrant’): admin
Email address: admin@gmail.com
Password:
Password (again):
Superuser created successfully.

mysql> select * from auth_user;
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+—————–+———-+———–+—————————-+
| id | password | last_login | is_superuser | username | first_name | last_name | email | is_staff | is_active | date_joined |
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+—————–+———-+———–+—————————-+
| 1 | pbkdf2**** | 2020-08-23 00:25:31.740845 | 1 | admin | | | admin@gmail.com | 1 | 1 | 2020-08-23 00:20:16.453853 |
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+—————–+———-+———–+—————————-+
1 row in set (0.00 sec)

id:1
password:
last_login: * adminにログインした日時
is_superuser:1
username: *
first_name: null
last_name: null
email: *
is_staff: 1
is_active: 1
data_joined: * createsuperuserした日時

adminツールからユーザ作成した場合

active: Designates whether this user should be treated as active. Unselect this instead of deleting accounts.
-> activeユーザか否か

is_staff: Designates whether the user can log into this admin site.
-> adminサイトにアクセスできるか否か

is_superuser: Designates that this user has all permissions without explicitly assigning them.
-> adminサイトでのパーミッション

mysql> select * from auth_user;
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+——————+———-+———–+—————————-+
| id | password | last_login | is_superuser | username | first_name | last_name | email | is_staff | is_active | date_joined |
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+——————+———-+———–+—————————-+
| 1 | **** | 2020-08-23 00:25:31.740845 | 1 | admin | | | admin@gmail.com | 1 | 1 | 2020-08-23 00:20:16.453853 |
| 2 | **** | NULL | 0 | normal | | | normal@gmail.com | 0 | 1 | 2020-08-23 00:33:51.000000 |
+—-+——————————————————————————–+—————————-+————–+———-+————+———–+——————+———-+———–+—————————-+

is_staffが0のユーザでアクセスしようとすると、ログインエラーになります。

is_staffを1にするとログインできるが、パーミッションがないので、何も閲覧できない。

auth_userの構造は理解した。続いて、permissionを見ていきます。

[Django3.0]デフォルトでmigrationされるテーブルを見てみよう

auth_userは、id, password, last_login, is_super, username, first_name, last_name, email, is_staff, is_active, date_joinedです。
permissionはidで管理してますね。

mysql> show tables;
+—————————-+
| Tables_in_myapp |
+—————————-+
| 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 |
| myapp_friend |
+—————————-+
11 rows in set (0.00 sec)

mysql> describe auth_user;
+————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| password | varchar(128) | NO | | NULL | |
| last_login | datetime(6) | YES | | NULL | |
| is_superuser | tinyint(1) | NO | | NULL | |
| username | varchar(150) | NO | UNI | NULL | |
| first_name | varchar(30) | NO | | NULL | |
| last_name | varchar(150) | NO | | NULL | |
| email | varchar(254) | NO | | NULL | |
| is_staff | tinyint(1) | NO | | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
| date_joined | datetime(6) | NO | | NULL | |
+————–+————–+——+—–+———+—————-+
11 rows in set (0.00 sec)

mysql> describe auth_group;
+——-+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——-+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(150) | NO | UNI | NULL | |
+——-+————–+——+—–+———+—————-+
2 rows in set (0.00 sec)

mysql> describe auth_group_permissions;
+—————+———+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————+———+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| group_id | int(11) | NO | MUL | NULL | |
| permission_id | int(11) | NO | MUL | NULL | |
+—————+———+——+—–+———+—————-+
3 rows in set (0.00 sec)

mysql> describe auth_permission;
+—————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| content_type_id | int(11) | NO | MUL | NULL | |
| codename | varchar(100) | NO | | NULL | |
+—————–+————–+——+—–+———+—————-+
4 rows in set (0.00 sec)

mysql> describe auth_user_groups;
+———-+———+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+———+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| group_id | int(11) | NO | MUL | NULL | |
+———-+———+——+—–+———+—————-+
3 rows in set (0.00 sec)

mysql> describe auth_user_user_permissions;
+—————+———+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————+———+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| permission_id | int(11) | NO | MUL | NULL | |
+—————+———+——+—–+———+—————-+
3 rows in set (0.00 sec)

mysql> describe django_admin_log;
+—————–+———————-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————–+———————-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| action_time | datetime(6) | NO | | NULL | |
| object_id | longtext | YES | | NULL | |
| object_repr | varchar(200) | NO | | NULL | |
| action_flag | smallint(5) unsigned | NO | | NULL | |
| change_message | longtext | NO | | NULL | |
| content_type_id | int(11) | YES | MUL | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
+—————–+———————-+——+—–+———+—————-+
8 rows in set (0.00 sec)

mysql> describe django_content_type;
+———–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| app_label | varchar(100) | NO | MUL | NULL | |
| model | varchar(100) | NO | | NULL | |
+———–+————–+——+—–+———+—————-+
3 rows in set (0.00 sec)

mysql> describe django_migrations;
+———+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| app | varchar(255) | NO | | NULL | |
| name | varchar(255) | NO | | NULL | |
| applied | datetime(6) | NO | | NULL | |
+———+————–+——+—–+———+—————-+
4 rows in set (0.00 sec)

mysql> describe django_session;
+————–+————-+——+—–+———+——-+
| Field | Type | Null | Key | Default | Extra |
+————–+————-+——+—–+———+——-+
| session_key | varchar(40) | NO | PRI | NULL | |
| session_data | longtext | NO | | NULL | |
| expire_date | datetime(6) | NO | MUL | NULL | |
+————–+————-+——+—–+———+——-+
3 rows in set (0.00 sec)

ER図で関係性を書きます。間違いに気づいたら修正。

ユーザごとのパーミッションとグループのパーミッションがあります。
auth_userなどのマイグレーションファイルがmigrationフォルダにないから、どうやってカラムを修正するのかよくわかりませんが、django_migrationsを見てみます。

mysql> select * from django_migrations;
+—-+————–+——————————————+—————————-+
| id | app | name | applied |
+—-+————–+——————————————+—————————-+
| 1 | contenttypes | 0001_initial | 2020-08-22 13:08:21.814641 |
| 2 | auth | 0001_initial | 2020-08-22 13:08:22.311463 |
| 3 | admin | 0001_initial | 2020-08-22 13:08:23.937696 |
| 4 | admin | 0002_logentry_remove_auto_add | 2020-08-22 13:08:24.302298 |
| 5 | admin | 0003_logentry_add_action_flag_choices | 2020-08-22 13:08:24.312982 |
| 6 | contenttypes | 0002_remove_content_type_name | 2020-08-22 13:08:24.561732 |
| 7 | auth | 0002_alter_permission_name_max_length | 2020-08-22 13:08:24.586026 |
| 8 | auth | 0003_alter_user_email_max_length | 2020-08-22 13:08:24.613153 |
| 9 | auth | 0004_alter_user_username_opts | 2020-08-22 13:08:24.630056 |
| 10 | auth | 0005_alter_user_last_login_null | 2020-08-22 13:08:24.754862 |
| 11 | auth | 0006_require_contenttypes_0002 | 2020-08-22 13:08:24.762768 |
| 12 | auth | 0007_alter_validators_add_error_messages | 2020-08-22 13:08:24.779663 |
| 13 | auth | 0008_alter_user_username_max_length | 2020-08-22 13:08:24.800110 |
| 14 | auth | 0009_alter_user_last_name_max_length | 2020-08-22 13:08:24.828577 |
| 15 | auth | 0010_alter_group_name_max_length | 2020-08-22 13:08:24.856540 |
| 16 | auth | 0011_update_proxy_permissions | 2020-08-22 13:08:24.870114 |
| 17 | sessions | 0001_initial | 2020-08-22 13:08:24.943481 |
| 18 | myapp | 0001_initial | 2020-08-22 13:18:22.321078 |
+—-+————–+——————————————+—————————-+
18 rows in set (0.00 sec)

ここはちょっと本屋行って再確認するかな。

[Django3.0]SQLite3ではなくMySQLを使いたい

// ubuntu bionicに入っているmysqlのバージョンを確認し、dbを作る
$ mysql –version
mysql Ver 14.14 Distrib 5.7.31, for Linux (x86_64) using EditLine wrapper
$ mysql -u root -p
Enter password:
mysql> show databases;
mysql> create database myapp;
Query OK, 1 row affected (0.01 sec)

OK
それではテスト用のDjangoのappを作ります

$ mkdir test
$ cd test
$ django-admin startproject testapp
$ cd testapp
$ python manage.py startapp myapp

/myapp/settings.py の DATABASESがデータベースの設定
defaultではsqlite3になっている

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

ここをmysqlに変更します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myapp',
        'USER': 'root',
        'PASSWORD': '*****',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

myqpp/models.py

from django.db import models

class Friend(models.Model):
	name = models.CharField(max_length=100)
	mail = models.EmailField(max_length=200)
	gender = models.BooleanField()
	age = models.IntegerField(default=0)
	birthday = models.DateField()

	def __str__(self):
		return '<Friend:id=' + str(self.id) + ', ' + self.name + '(' + str(self.age) + ')>'

$ python manage.py makemigrations testapp
Traceback (most recent call last):
File “/usr/local/lib/python3.8/dist-packages/django/db/backends/mysql/base.py”, line 16, in
import MySQLdb as Database
ModuleNotFoundError: No module named ‘MySQLdb’

The above exception was the direct cause of the following exception:
// 省略
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?

$ sudo apt-get install python3.8-dev
$ sudo apt-get install -y libmysqlclient-dev
$ pip install mysqlclient

$ python manage.py makemigrations testapp
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, 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 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

mysql> show tables;
+—————————-+
| Tables_in_myapp |
+—————————-+
| 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 |
| myapp_friend |
+—————————-+
11 rows in set (0.00 sec)

mysql> describe myapp_friend
-> ;
+———-+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(100) | NO | | NULL | |
| mail | varchar(200) | NO | | NULL | |
| gender | tinyint(1) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| birthday | date | NO | | NULL | |
+———-+————–+——+—–+———+—————-+
6 rows in set (0.00 sec)

mysqlにマイグレーションできてることは確認できました。
auth_* のテーブルが結構あるけど、やりたい事としてはusersテーブルで、email, name, password, department, (created), (updated)を管理したい。

Djangoのuser管理がどうなってるか理解する必要があるな。

[Django3.0]レイアウト用テンプレートでインクルード

layout.htmlを作成し、編集箇所を{% block * %}{% endblock %}で囲む。
includeする側は{% extends ‘sales/layout.html’ %}で読み込む。
{% load static %}は、読み込むhtml側に記載しないとErrorになる

sales/templates/sales/layout.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>{% block title %}{% endblock %}</title>
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	{% block description %}{% endblock %}
	<meta name="Description" content="見積管理、在庫管理、販売管理はhanbai">

	{% block header_script %}{% endblock %}
</head>
<body>
	<div class="wrapper">

		<nav class="navbar navbar-expand-md navbar-dark fixed-top" style="background-color:#1e90ff">
			<a class="navbar-brand" href="/top.html">Hanbai</a>
			<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
				<span class="navbar-toggler-icon"></span>
			</button>
			<div class="collapse navbar-collapse" id="navbarCollapse">
				<ul class="navbar-nav mr-auto">
					<li class="nav-item dropdown  active">
						<a class="nav-link dropdown-toggle" href="/estimate" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">見積管理</a>
						<div class="dropdown-menu" aria-labelledby="navbarDropdown">
							<a class="dropdown-item" href="/estimate">見積一覧</a>
							<hr>
							<a class="dropdown-item" href="/estimate/input">見積登録</a>
						</div>
					</li>
					<li class="nav-item dropdown active">
						<a class="nav-link dropdown-toggle" href="/order" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">受注管理</a>
						<div class="dropdown-menu" aria-labelledby="navbarDropdown">
							<a class="dropdown-item" href="/order">受注一覧</a>
							<hr>
							<a class="dropdown-item" href="/order/input">受注登録</a>
						</div>
					</li>
					<li class="nav-item dropdown active">
						<a class="nav-link dropdown-toggle" href="/stock" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">在庫管理</a>
						<div class="dropdown-menu" aria-labelledby="navbarDropdown">
							<a class="dropdown-item" href="/stock">在庫一覧</a>
							<hr>
							<a class="dropdown-item" href="/stock/input">在庫登録</a>
						</div>
					</li>
					<li class="nav-item dropdown active">
						<a class="nav-link dropdown-toggle" href="/client" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">得意先管理</a>
						<div class="dropdown-menu" aria-labelledby="navbarDropdown">
							<a class="dropdown-item" href="/client">得意先一覧</a>
							<hr>
							<a class="dropdown-item" href="/client/input">得意先登録</a>
						</div>
					</li>
					<li class="nav-item dropdown active">
						<a class="nav-link dropdown-toggle" href="/master" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">その他</a>
						<div class="dropdown-menu" aria-labelledby="navbarDropdown">
							<a class="dropdown-item" href="/master">自社基本情報</a>
						</div>
					</li>
				</ul>
				<ul class="navbar-nav">
					<li class="nav-item">
						<a class="nav-link" href="">ログアウト</a>
					</li>
				</ul>
			</div>
		</nav>

		<div class="container">
			{% block content %}{% endblock %}
				
		</div>
		<footer>
			<p>&copy; Hanbai All Right Reserved.</p>
		</footer>
	</div>

	{% block script %}{% endblock %}
	
</body>
</html>

sales/templates/sales/index.html

{% extends 'sales/layout.html' %}
{% load static %}

{% block title %}Top | hanbai - トップページを表示しています{% endblock %}

{% block description %}<meta name="Description" content="見積管理、在庫管理、販売管理はhanbai">{% endblock %}

{% block header_script %}
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

	<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

	<link rel="stylesheet" href="{% static 'sales/css/styles.css' %}">
	<script src="/js/main.js"></script>
{% endblock %}

	
{% block content %}
			<div class="top">
				<nav aria-label="パンくずリスト">
					<ol class="breadcrumb">
	  					<li class="breadcrumb-item active" aria-current="page">ホーム</li>
					</ol>
				</nav>
				// 省略
{% endblock %}

{% block script %}
	// 省略
{% endblock %}

Sugeeeeeeeee 上手く表示されています。

これをログイン以外のページも、block, endblockの中に流し込んでいきます。

{% extends 'sales/layout.html' %}
{% load static %}

{% block title %}{% endblock %}

{% block description %}{% endblock %}

{% block header_script %}
{% endblock %}

	
{% block content %}
{% endblock %}

{% block script %}
{% endblock %}

なかなか面白い。