[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 %}

なかなか面白い。

[Django3.0]urls.pyとviews.py

フロントに沿って、urls.py、views.py、*.htmlを作ってきます。
editはパラメータの書き方が異なるので、モデルを作った後に作ります。

/sales/urls.py

urlpatterns = [
	path('login/', views.login, name='login'),
	path('', views.index, name='index'),
	path('client/', views.client, name='client'),
	path('client/input', views.client_input, name='client_input'),
	path('client/detail', views.client_detail, name='client_detail'),
	path('master/edit', views.master, name='master'),
	path('estimate/', views.estimate, name='estimate'),
	path('estimate/input', views.estimate_input, name='estimate_input'),
	path('order/', views.order, name='order'),
	path('order/input', views.order_input, name='order_input'),
	path('stock', views.stock, name='stock'),
	path('stock/input', views.stock_input, name='stock_input'),
]

/sales/views.py

from django.shortcuts import render
from django.http import HttpResponse

def login(request):
	return render(request, 'sales/login.html')

def index(request):
	return render(request, 'sales/index.html')

def client(request):
	return render(request, 'sales/client.html')

def client_input(request):
	return render(request, 'sales/client_input.html')

def client_detail(request):
	return render(request, 'sales/client_detail.html')

def master(request):
	return render(request, 'sales/master.html')

def estimate(request):
	return render(request, 'sales/estimate.html')

def estimate_input(request):
	return render(request, 'sales/estimate_input.html')

def order(request):
	return render(request, 'sales/order.html')

def order_input(request):
	return render(request, 'sales/order_input.html')

def stock(request):
	return render(request, 'sales/stock.html')

def stock_input(request):
	return render(request, 'sales/stock_input.html')

ヘッダとログインページ以外のナビゲーションは共通なので、続いて、共通箇所をインクルードにして一括管理したい。

[Django]templatesを一気に作る

### 画面遷移図

### URL一覧

まずtemplateのhtmlを流し込む前に、urls.pyとviews.pyの一覧を作ってからhtmlを流し込めば良いのか?
いや、urls.pyに記載してもviews.pyに記載がないとAttributeErrorになるので、urls.py -> views.py -> *.html をループさせた方が作業効率は良さそうである。

/sales/urls.py

urlpatterns = [
	path('login/', views.login, name='login'),
	path('', views.index, name='index'),
]

/sales/views.py

def index(request):
	return render(request, 'sales/index.html')

/sales/templates/sales/index.html

// 省略

load staticを毎ページ修正するのは面倒ではある。

[Django]staticフォルダにstyle.cssを置こう

まずsales配下にstaticフォルダ、その配下にsalesフォルダ、更にその下にcssフォルダを作成します。

/sales/static/sales/css/style.css
// 省略 ※webpackでsassから作ったcssを置く

/templates/sales/login.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>ログイン | hanbai - メールアドレス、パスワードを入力してください</title>
	// 省略

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

imgフォルダも作ります。

<img id="profile-img" class="profile-img-card" src="{% static 'sales/img/avatar.png' %}">

Wow!! なるほど、Djangoオモロイ!
取り敢えずこれはモデルの前に先に全部のurlspatternとtemplate嵌め込みをやれば良いのかな。順番がよくわからないが、フロント部分から手を付けた方が効率的そう。

djangoでテンプレートにはめ込もう

/hanbai/settings.py

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

/sales/templates/sales/login.html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>ログイン | hanbai - メールアドレス、パスワードを入力してください</title>
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<meta name="Description" content="見積管理、在庫管理、販売管理はhanbai">

	<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="css/style.css">
	<script src="/js/main.js"></script>
</head>
<body>
	<div class="wrapper">

		<nav class="navbar navbar-expand-md navbar-dark fixed-top" style="background-color:#1e90ff">
			<a class="navbar-brand disabled" href="">Hanbai</a>
		</nav>

		<div class="container">
			<div class="login">
				<div class="row">
					<div class="card card-container">
						<h1 class="text-center login-title">Hanbai</h1>
						<img id="profile-img" class="profile-img-card" src="/img/avatar.png">
						<div class="account-wall">
							<form class="form-signin">
								<input type="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
								<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
								<button class="btn btn-lg btn-primary btn-block btn-signin" type="submit">
								ログイン</button>
								・ログインパスワードをお忘れの方は
								<a href="#">こちら</a><span class="clearfix"></span>
								・アカウントをお持ちでない方は
								<a href="#">こちら</a><span class="clearfix"></span>
							</form>
						</div>
					</div>
				</div>
			</div>
		</div>
		<footer>
			<p>&copy; Hanbai All Right Reserved.</p>
		</footer>
	</div>
</body>
</html>

/sales/urls.py

from django.urls import path
from . import views

urlpatterns = [
	path('/login', views.login, name='login'),
]

/sales/views.py

from django.shortcuts import render
from django.http import HttpResponse

def login(request):
	return render(request, 'sales/login.html')

cssと画像が効いていませんが、基本型はできました。