NuxtJSを触ってみる

公式: NUXT JS
Vue.js に基づいたプログレッシブフレームワーク
Universalモード、SPAモード、Generateモードの3つのモードで柔軟にサイトを設計できる

### 機能
– Vue ファイルで記述できること(*.vue)
– コードを自動的に分割すること
– サーバーサイドレンダリング
– 非同期データをハンドリングするパワフルなルーティング
– 静的ファイルの配信
– ES2015+ のトランスパイレーション
– JS と CSS のバンドル及びミニファイ化
– 要素の管理
– 開発モードにおけるホットリローディング
– プリプロセッサ: Sass, Less, Stylus など
– HTTP/2 push headers ready
– モジュール構造で拡張できること

### 動作
Vue2(Vue本体), Vue Router(Routing), Vuex(Vue版Flux), Vue Server Render(ServerSideレンダリング), Vue Meta(メタ情報管理)

### NuxtJSを始めよう
$ cat /etc/os-release
NAME=”Ubuntu”
VERSION=”18.04.4 LTS (Bionic Beaver)”
// 以下略

// npm, nodejsインストール
$ sudo apt install -y nodejs npm
$ node -v
v8.10.0
$ npm -v
3.5.2

// npmを最新化
$ sudo npm install npm@latest -g

$ sudo npm install -g vue-cli // sudo権限がないとエラーになる

$ vue init nuxt-community/starter-template sample
? Project name sample
? Project description Nuxt.js project
? Author hpscript

$ cd sample
$ ls
README.md components middleware package.json plugins store
assets layouts nuxt.config.js pages static
$ sudo npm install
$ sudo npm run dev

> sample@1.0.0 dev /home/vagrant/local/sample
> nuxt

FATAL Unexpected token { 13:12:14

} catch {
^

ん? nodeがv8.10.0では古いよう。
最新に上げます。

$ sudo npm install n -g
$ sudo n stable
$ sudo apt purge -y nodejs npm
$ exec $SHELL -l
$ node -v
v12.18.3
$ npm -v
6.14.6
$ sudo npm run dev

vagrantのprivate networkが192.168.33.10なので、
nust.config.jsのサーバの設定も変更する必要があります。

module.exports = {
  // 省略
  server: {
    port: 8000, // デフォルト: 3000
    host: '192.168.33.10' // デフォルト: localhost
  },
  // 省略 
}

/pages/users/index.vue

<template>
	<section class="container">
		<div>
			<h1>user index page</h1>
			<p>count={{count}}</p>
			<button @click="addCount">カウントアップ</button>
		</div>
	</section>
</template>

<script>
export default {
	computed: {
		count() { return this.$store.state.counter.count }
	},
	methods: {
		addCount(e){
			this.$store.commit('counter/add')
		}
	}
}
</script>

/store/counter.js

export const state = () => ({
	count: 0
})

export const mutations = {
	add (state){
		state.count += 1
	}
}

Djangoで販売管理システムを開発していくロードマップ

– 今回はDjangoを学習するのが目的の一つでもあるため、設計書はざっくりと作る
– 販売管理システムは財務会計システムとの連携(ERP化)を見据えて作る
– できればRPA、機械学習、Docker、レジシステム連携、ECサイトのAPI連携、ラズパイ/Arduino連携を盛り込みたい
———–
1.全体
 1-1. 何故、販売管理システムを導入するか?
 1-2. 販売管理システムで出来る事(後から作る)
2. システム要求
 2-1. 機能要求
 2-2. 競合のシステムとの差別化
 2-3. 財務会計との連携(後から作る)
3. 基本設計
 3-1. 業務フロー
 3-2. ユースケース
 3-3. 機能詳細
4. 外部設計
4-1 システム概要図
 4-2 インフラ構成図
 4-3 ハードウェア構成図
 4-4 ミドルウェア構成図
5. UI設計
 5-1. URL一覧
 5-2. 画面遷移図
 5-3. 画面定義(別紙)
 5-4. メール設計
 5-5. バリデーション
6. 詳細設計
 6-1. MVC構成図
 6-2. シーケンス図
 6-3. コントローラ処理
 6-4. バッチ処理
 6-5. その他

設計書はサクッと作ろうと思ったけど、最低限の事はちゃんとやろうと思うとそこそこのボリューム感になってしまうな。

SAPのようなERPをDjangoで作りたい

ERPとは? : Enterprise Resource Planning(統合パッケージ)
-> 調達システム、経理システム、在庫システムなどヒトモノカネ情報の管理を一緒にした

### SAPの主なモジュール
– Financial Accounting(FI): 財務会計、業務データが流れてくる
– Controling(CO): 管理会計、業績管理や間接費の管理
– Sales and Distribution(SD): 販売管理、注文を受けてから商品を出荷、請求書
– Material Management(MM): 在庫購買管理、どこに何を発注したか、値段、在庫

### モジュール一覧
Sales and Distribution(販売管理), Material Management(在庫購買管理), Production Planning and Control(生産計画/管理), Plant Maintenance(プラント保全), Warehouse Management(倉庫管理), Financial Accounting(財務会計), Controlling(管理会計), Human Resources(人事管理), Project System(プロジェクト管理), Quality Management(品質管理), Investment Management(設備投資管理), Real Estate(不動産管理), Document Management System(文書管理), Classification(分類), クロスアプリケーション(Cross Application)

SAP Tutorial

– 各システムのログイン画面
– メニュー:各システム一覧(office, logistics, accounting, human resources….)
 L 各システムの帳票ページに遷移できる
– 各入力メニュー
 L ユーザが入力データのパラメータを編集できる
– IDEがトップにあり、その下に、地域、国、システム、従業員、ポジション、機能などでカテゴライズできる
– Masterデータ(client, company, sales area, personal record)と入力データを切り分ける
– メニューはツリー構造で表示する

SAP Engineer

ざっくりとは、各モジュールごとにアプリケーションを作っていき、データ連携させるってことだな。
ゼロから作るなら、(1)販売管理、(2)在庫購買管理、(3)財務会計 ぐらいから始めたいところか。
問題はどこまでやり込むかだな。

[Django]ユニットテスト

– from django.test import TestCaseでTestCaseを継承
– メソッドはtest_* にしなければならない
/sns/tests.py

from django.test import TestCase

class SnsTests(TestCase):

	def test_check(self):
		x = True
		self.assertTrue(x)
		y = 100
		self.assertGreater(y, 0)
		arr = [10, 20, 30]
		self.assertIn(20, arr)
		nn = None
		self.assertIsNone(nn)

$ python manage.py test sns
Creating test database for alias ‘default’…
System check identified no issues (0 silenced).
.
———————————————————————-
Ran 1 test in 0.001s

OK
Destroying test database for alias ‘default’…

### チェックメソッド
– assertTrue, assertFalse, assertIsl, assertIsNot, assertEqual, assertNoEqual, assertGreater, assertGreaterEqual, assertLess, assertLessEqual, assertIsNone, assertIsNotNone, assertIsIn, assertNotIn

### データベースのチェック

from django.test import TestCase

from django.contrib.auth.models import User
from .models import Message

class SnsTests(TestCase):

	def test_check(self):
		usr = User.object.first()
		self.assertIsNotNone(usr)
		msg = Message.objects.first()
		self.assertIsNotNone(msg)

– テスト用のデータベースを都度作って使用している

from django.test import TestCase

from django.contrib.auth.models import User
from .models import Group, Message

class SnsTests(TestCase):

	@classmethod
	def setUpClass(cls):
		super().setUpClass()
		(usr, grp) = cls.create_user_and_group()
		cls.create_message(usr, grp)

	@classmethod
	def create_user_and_group(cls):
		# Create public user & public group
		User(username="public", password="public", is_staff=False, is_active=True).save()
		pb_usr = User.objects.filter(username='public').first()
		Group(title='public', owner_id=pb_usr.id).save()
		pb_grp = Group.objects.filter(title='public').first()

		# Create test user
		User(username="test", password="test", is_staff=True, is_active=True).save()
		usr = User.objects.filter(username='test').first()

		return (usr, pb_grp)

	@classmethod
	def create_message(cls, usr, grp):
		# Create test massage
		Message(content='this is test message.', owner_id=usr.id, group_id=grp.id).save()
		Message(content='test', owner_id=usr.id, group_id=grp.id).save()
		Message(content="ok", owner_id=usr.id, group_id=grp.id).save()
		Message(content="ng", owner_id=usr.id, group_id=grp.id).save()
		Message(content='finish', owner_id=usr.id, group_id=grp.id).save()

	def test_check(self):
		usr = User.objects.first()
		self.assertIsNotNone(usr)
		msg = Message.objects.first()
		self.assertIsNotNone(msg)

[Django]SNSアプリ2

/sns/forms.py

from django import forms
from.models import Message,Group,Friend,Good
from django.contrib.auth.models import User

# Message form
class MessageForm(forms.ModelForm):
	class Meta:
		model = Message
		fields = ['owner', 'group', 'content']

# Group form
class GroupForm(forms.ModelForm):
	class Meta:
		model = Group
		fields = ['owner', 'title']

# Friend form
class FriendForm(forms.ModelForm):
	class Meta:
		model = Friend
		fields = ['owner', 'user', 'group']

# Good form
class GoodForm(forms.ModelForm):
	class Meta:
		model = Good
		fields = ['owner', 'message']

# Group checkbox form
class GroupCheckForm(forms.Form):
	def __init__(self, user, *args, **kwargs):
		super(GroupCheckForm, self).__init__(*args, **kwargs)
		public = User.objects.filter(username='public').first()
		self.fields['groups'] = forms.MultipleChoiceField(
			choices = [(item.title, item.title) for item in Group.objects.filter(owner__in=[user,public])],
		widget = forms.CheckboxSelectMultiple(),
		)

# Group select menu
class GroupSelectForm(forms.Form):
	def __init__(self, user, *args, **kwargs):
		super(GroupSelectForm, self).__init__(*args, **kwargs)
		self.fields['groups'] = forms.ChoiceField(
				choices=[('-','-')] + [(item.title, item.title) for item in Group.objects.filter(owner=user)],widget=forms.Select(attrs={'class':'form-control'}),
			)

# Friend checkbox form
class FriendsForm(forms.Form):
	def __init__(self, user, friends=[], vals=[], *args, **kwargs):
		super(FriendsForm, self).__init__(*args, **kwargs)
		self.fields['friends'] = forms.MultipleChoiceField(
				choices = [(item.user, item.user) for item in friends],
				widget = forms.CheckboxSelectMultiple(),
				initial = vals
			)

# Group making form
class CreateGroupForm(forms.Form):
	group_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class':'form-control'}))

# 投稿フォーム
class PostForm(forms.Form):
	content = forms.CharField(max_length=500, widget=forms.Textarea(attrs={'class':'form-control', 'rows':2}))

	def __init__(self, user, *args, **kwargs):
		super(PostForm, self).__init__(*args, **kwargs)
		public = User.objects.filter(username='public').first()
		self.fields['groups'] = forms.ChoiceField(
				choices=[('-','-')] + [(item.title, item.title) for item in Group.objects. filter(owner__in=[user,public])],
				widget=forms.Select(attrs={'class':'form-control'}), 
		)

/sns/urls.py

from django.urls import path
from . import views

urlpatterns = [
	path('', views.index, name='index'),
	path('<int:page>', views.index, name='index'),
	path('groups', views.groups, name='groups'),
	path('add', views.add, name='add'),
	path('creategroup', views.creategroup, name='creategroup'),
	path('post', views.post, name='post'),
	path('share/<int:share_id>', views.share, name='share'),
	path('good/<int:good_id>', views.good, name='good'),
]

/sns/urls.py

from django.urls import path
from . import views

urlpatterns = [
	path('', views.index, name='index'),
	path('<int:page>', views.index, name='index'),
	path('groups', views.groups, name='groups'),
	path('add', views.add, name='add'),
	path('creategroup', views.creategroup, name='creategroup'),
	path('post', views.post, name='post'),
	path('share/<int:share_id>', views.share, name='share'),
	path('good/<int:good_id>', views.good, name='good'),
]

/django_app/urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', include('hello.urls')),
    path('sns/', include('sns,urls')),
]

/sns/views.py

from django.shortcuts import render
from django.shortcuts import redirect
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.paginator import Paginator
from django.db.models import Q
from django.contrib.auth.decorators import login_required

from .models import Message,Friend,Group,Good
from .forms import GroupCheckForm, GroupSelectForm, FriendsForm, CreateGroupForm, PostForm

# index view
@login_required(login_url='/admin/login/')
def index(request, page=1):
	# publicのuserを取得
	(public_user, public_group) = get_public()

	# post送信時の処理
	if request.method == 'POST':

		# Groupsのチェックを更新した時の処理
		# フォームを用意
		checkform = GroupCheckForm(request.user, request.POST)
		# チェックされたGroup名をリストにまとめる
		glist = []
		for item in request.POST.getlist('groups'):
			glist.append(item)
		# Messageの取得
		messages = get_your_group_message(request.user, glist, page)

	# GETアクセス時の処理
	else:
		# フォームの用意
		checkform = GroupCheckForm(request.user)
		# Groupのリスト取得
		gps = Group.objects.filter(owner=request.user)
		glist = [public_group.title]
		for item in gps:
			glist.append(item.title)
		# メッセージの取得
		messages = get_your_group_message(request.user, glist, page)

	# 共通処理
	params = {
		'login_user': request.user,
		'content': messages,
		'check_form': checkform,
	}
	return render(request, 'sns/index.html', params)

@login_required(login_url='/admin/login/')
def groups(request):
	# 自分が登録したFriendを取得
	friends = Friend.objects.filter(owner=request.user)

	# POST送信時の処理
	if request.method == 'POST':

		# Groupsメニュー選択肢の処理
		if request.POST['mode'] == '__groups_form__':
			# 選択したGroup名を取得
			sel_group = request.POST['groups']
			# Groupを取得
			gp = Group.objects.filter(owner=request.user).filter(title=sel_group).first()
			# Groupに含まれるFriend取得
			fds = Friend.objects.filter(owner=request.user).filter(group=gp)
			print(Friend.objects.filter(owner=request.user))
			# FriendのUserをリストにまとめる
			vlist = []
			for item in fds:
				vlist.append(item.user.username)
			# フォームの用意
			groupsform = GroupSelectForm(request.user,request.POST)
			friendsform = FriendsForm(request.user, friends=friends, vals=vlist)

		# Friendsのチェック更新時の処理
		if request.POST['mode'] == '__friends_form__':
			# 選択したGroupの取得
			sel_group = request.POST['group']
			group_obj = Group.objects.filter(title=sel_group).first()
			print(group_obj)
			# チェックしたFriendsを取得
			sel_fds = request.POST.getlist('friends')
			# FriendsのUserを取得
			sel_users = User.objects.filter(username__in=sel_fds)
			# Userのリストに含まれるユーザが登録したFriendを取得
			fds = Friend.objects.filter(owner=request.user).filter(user__in=sel_users)
			# 全てのFriendにGroupを設定し保存する
			vlist = []
			for item in fds:
				item.group = group_obj
				item.save()
				vlist.append(item.user.username)
			# メッセージを設定
			messages.success(request, ' チェックされたFriendを' + sel_group + 'に登録しました')
			# フォームの用意
			groupsform = GroupSelectForm(request.user, {'groups':sel_group})
			friendsform = FriendsForm(request.user, friends=friends, vals=vlist)

	# GETアクセス時の処理
	else:
		# フォームの用意
		groupsform = GroupSelectForm(request.user)
		friendsform = FriendsForm(request.user, friends=friends, vals=[])
		sel_group = '-'

	# 共通処理
	createform = CreateGroupForm()
	params = {
		'login_user': request_user,
		'groups_form': groupsform,
		'friends_form': friendsform,
		'create_form': createform,
		'group': sel_group,
	}
	return render(request, 'sns/groups.html', params)

# Friendの追加処理
@login_required(login_url='/admin/login/')
def add(request):
	# 追加するUserを取得
	add_name = request.GET['name']
	add_user = User.objects.filter(username=add_name).first()
	# Userが本人だった場合の処理
	if add_user == request.user:
		messages.info(request, "自分自身をFriendに追加することはできません。")
		return redirect(to='/sns')
	# publicの取得
	(public_user, public_group) = get_public()
	# add_userのFriendの数を調べる
	frd_num = Friend.objects.filter(owner=request.user).filter(user=add_user).count()
	# ゼロより大きければ既に登録済み
	if frd_num > 0:
		messages.info(request, add_user.username + ' は既に追加されています。')
		return redirect(to='/sns')

	# ここからFriendの登録処理
	frd = Friend()
	frd.owner = request.user
	frd.user = add_user
	frd.group = public_group
	frd.save()
	# メッセージを設定
	messages.success(request, add_user.username + ' を追加しました! groupページに移動して、追加したFriendをメンバーに設定してください。')
	return redirect(to='/sns')

# グループの作成処理
@login_required()
def creategroup(request):
	# Groupを作りUserとtitleを設定して保存する
	gp = Group()
	gp.owner = request.user
	gp.title = request.user.username + 'の' + request.POST['group_name']
	gp.save()
	messages.info(request, '新しいグループを作成しました。')
	return redirect(to='/sns/groups')

# メッセージのポスト処理
@login_required(login_url='/admin/login/')
def post(request):
	# POST送信の処理
	if request.method == 'POST':
		# 送信内容の取得
		gr_name = request.POST['groups']
		content = request.POST['content']
		# Groupの取得
		group = Group.objects.filter(owner=request.user).filter(title=gr_name).first()
		if group == None:
			(pub_user, group) = get_public()
		# Messageを作成し設定して保存
		msg = Message()
		msg.owner = request.user
		msg.group = group
		msg.content = content
		msg.save()
		# メッセージを設定
		messages.success(request, '新しいメッセージを投稿しました!')
		return redirect(to='sns')

	# GETアクセス時の処理
	else:
		form = PostForm(request.user)

	# 共通処理
	params = {
		'login_user': request.user,
		'form': form, 
	}
	return render(request, 'sns/post.html', params)

# 投稿をシェアする
@login_required(login_url='/admin/login/')
def share(request, share_id):
	# シェアするMessageの取得
	share = Message.objects.get(id=share_id)
	print(share)
	# POST送信時の処理
	if request.method == 'POST':
		# 送信内容を取得
		gr_name = request.POST['groups']
		content = request.POST['content']
		# Groupの取得
		group = Group.objects.filter(owner=request.user).filter(title=gr_name).first()
		if group == None:
			(pub_user, group) = get_public()
		# メッセージを作成し、設定をして保存
		msg = Message()
		msg.owner = request.user
		msg.group = group
		msg.content = content
		msg.share_id = share.id
		msg.save()
		share_msg = msg.get_share()
		share_msg.share_content += 1
		share_msg.save()
		# メッセージを設定
		messages.sucess(request, 'メッセージをシェアしました!')
		return redirect(to='/sns')

	# 共通処理
	form = PostForm(request.user)
	params = {
		'login_user': request.user,
		'form': form,
		'share': share,
	}
	return render(request, 'sns/share.html', params)

# goodボタンの処理
@login_required(login_url='/admin/login')
def good(request, good_id):
	# goodするMessageを取得
	good_msg = Message.objects.get(id=good_id)
	# 自分がメッセージにGoodした数を調べる
	is_good = Good.objects.filter(owner=request.user).filter(message=good_msg).count()
	# ゼロより大きければ既にgood済み
	if is_good > 0:
		messages.success(request, '既にメッセージにはGoodしています。')
		return redirect(to='/sns')

	# Messageのgood_countを1増やす
	good_msg.good_count += 1
	good_msg.save()
	# Goodを作成し、設定して保存
	good = Good()
	good.owner = request.user
	good.message = good_msg
	good.save()
	# メッセージを設定
	messages.success(request, 'メッセージにGoodしました!')
	return redirect(to='/sns')

# 指定されたグループおよび検索文字によるMessageの取得
def get_your_group_message(owner, glist, page):
	page_num = 10 # ページあたりの表示数
	# publicの取得
	(public_user, public_group) = get_public()
	# チェックされたGroupの取得
	groups = Group.objects.filter(Q(owner=owner)|Q(owner=public_user)).filter(title__in_groups)
	# FriendのUserをリストにまとめる
	me_users = []
	for f in me_friends:
		me_users.append(f.user)
	# UserリストのUserが作ったGroupの取得
	his_groups = Group.objects.filter(owner__in=me_users)
	his_friends = Friend.objects.filter(user=owner).filter(group__in=his_groups)
	me_groups = []
	for hf in his_friends:
		me_groups.append(hf.group)
	# groupがgroupに含まれるか、me_groupsに含まれるMessageの取得
	messages = Message.objects.filter(Q(group__in=groups)|Q(group__in=me_groups))
	# ページネーションで指定ページを取得
	page_item = Paginator(messages, page_num)
	return page_item.get_page(page)

# publicなUserとGroupを取得する
def get_public():
	public_user = User.objects.filter(username='public').first()
	public_group = Group.objects.filter(owner=public_user).first()
	return (public_user, public_group)

– @login_required(login_url=’/admin/login’)でログインしないとアクセスできなくなる
– from django.contrib.auth.decorators import Userが必須

### テンプレートの作成
/sns/templates/sns/layout.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>{% block title %}{% endblock %}</title>
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossoorigin="anonymous">
</head>
<body>
	<nav class="navbar navbar-expand navbar-light bg-light">
	<ul class="navbar-nav mr-auto">
		<li class="nav-item">
			<a class="nav-link" href="{% url 'index' %}">top</a>
		</li>
		<li class="nav-item">
			<a class="nav-link" href="{% url 'post' %}">post</a>
		</li>
		<li class="nav-item">
			<a class="nav-link" href="{% url 'groups' %}">group</a>
		</li>
	</ul>
	<span>logined: <span class="h6">"{{login_user}}".</span></span>
	</nav>

	<div>{% block header %}{% endblock %}</div>
	<div class="content">{% block content %}{% endblock %}</div>

	<hr>
	<div class="my-3">
		<span class="font-weight-bold">
			<a href="/admin/logout?next=/sns/">[ logout ]</a>
		</span>
	</div>	
</body>
</html>

– {% block ○○ %}{% endblock %}にコンテンツを用意する

/sns/templates/sns/index.html

{% extends 'sns/layout.html' %}

{% block title %}Index{% endblock %}

{% block header %}
<script>
function sendGroupForm(page){
	document.group_form.action += page;
	document.group_form.submit();
}
</script>
<h1 class="display-4 text-primary">SNS</h1>
<p>※グループのチェックをONにしてupdateすると、そのグループに登録されている利用者のメッセージだけが表示されます。</p>
{% if messages %}
<ul class="messages">
	{% for message in messages %}
	<li {% if message.tags %} class="{{ message.tags }}" {% endif %}></li>
	{% endfor %}
</ul>
{% endif %}
{% endblock %}

{% block content %}
<hr>
<div>
	<form action="{% url 'index' %}" method="post" name="group_form">
		{% csrf_token %}
		{{ check_form }}
		<div>
			<button class="btn btn-primary">update</button>
		</div>
	</form>
</div>
<table class="table mt-3">
	<tr><th>Messages</th></tr>
	{% for item in contents %}
	<tr><td>
		<p class="my-0">
			{% if item.group.title == 'public' %}
			<span class="bg-info text-light px-1">Public</span>
			{% endif %}
		{{item.content}}</p>
		<p class="">({{item.pub_date}})</p>
		{% if item.share_id > 0 %}
		<ul><li class="text-black-50">"{{item.get_share}}"</li></ul>
		{% endif %}
		<span class="float-left text-info"> share="{{item.share_count}} good={{item.good_count}}</span>
		<span class="float-right">
			"{{item.owner}}"(<a href="{% url 'add' %}?name=({item.owner})">add friend</a>)
			<a href="{% url 'share' item.id %}"><button class="py-0">share</button></a>
			<a href="{% url 'good' item.id %}"><button class="py-0">good!</button></a>
		</span>
	</td></tr>
	{% endfor %}
</table>

<ul class="pagination justify-content-center">
	{% if contents.has_previous %}
		<li class="page-item">
			<a class="page-link" href="javascript:sendGroupForm(1);">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="javascript:sendGroupForm({{contents.previous_page_number}});">
			&laquo; prev</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link">
			&laquo; prev</a>
		</li>
		{% endif %}
		<li class="page-item">
			<a class="page-link">
			{{contents.number}}/{{contents.paginator.num_pages}}</a>
		</li>
		{% if data.has_next %}
		<li class="page-item">
			<a class="page-link" href="javascript:sendGroupForm({{contents.next_page_number}});">
			next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="javascript:sendGroupForm({{contents.paginator.num_pages}})">
			last &raquo;</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link">last &raquo;</a>
		</li>
		{% endif %}
	</ul>
{% endblock %}

/sns/templates/sns/post.html

{% extends 'sns/layout.html' %}

{% block title %}Post{% endblock %}

{% block header %}
<h1 class="display-4 text-primary">Post</h1>
<p class="caption">※投稿先のグループを選択し、メッセージを投稿します。</p>
{% if messages %}
<ul class="list-group">
	{% for message in messages %}
	<li {% if message.tags %} class="list-group-item {{ message.tags }}" {% endif %}>{{ message }}</li>
	{% endfor %}
</ul>
{% endif %}
{% endblock %}

{% block content %}
<form action="{% url 'post' %}" method="post">
	{% csrf_token %}
	{{form.as_p}}
	<button class="btn btn-primary">Post!</button>
</form>
{% endblock %}

/sns/templates/sns/share.html

{% extends 'sns/layout.html' %}

{% block title %}Share{% endblock %}

{% block header %}
<h1 class="display-4 text-primary">Share</h1>
<p class="caption">※投稿先のグループを選択し、下のメッセージをシェアします。</p>
{% if messages %}
<ul class="messages">
	{% for message in messages %}
	<li{% if message tags %} class="{{ message.tags }}" {% endif %}>{{ message }}</li>
	{% endfor %}
</ul>
{% endif %}
{% endblock %}

{% block content %}
<p class="bg-light p-3">
"{{share.content}}({{share.owner}})"</p>
<form action="{% url 'share' share.id %}" method="post">
	{% csrf_token %}
	{{form.as_p}}
	<button class="btn btn-primary mt-2">Share!</button>
</form>
{% endblock %}

/sns/templates/sns/groups.html

{% extends 'sns/layout.html' %}

{% block title %}Groups{% endblock %}

{% block header %}
<h1 class="display-4 text-primary">Group</h1>
<p class="caption">※グループを選択してselect memberすると、
	そのグループに登録されている利用者がONになります。
利用者のチェックをONにしてset memberすると、ONにしてある利用者がグループに追加されます。</p>
{% if messages %}
<ul class="messages">
	{% for message in messages %}
	<li{% if message.tags %} class="{{ message.tags }}" {% endif %}>{{message}}</li>
	{% endfor %}
</li>
{% endif %}
{% endblock %}

{% block content %}
<form action="{% url 'groups' %}" method="post">
	{% csrf_token %}
	<input type="hidden" name="mode" value="__groups_form__">
	{{groups_form}}
	<button class="btn btn-primary mt-1">select members</button>
</form>
<hr>
<form action="{% url 'groups' %}" method="post">
	{% csrf_token %}
	<input type="hidden" name="mode" value="__friends_form__">
	<input type="hidden" name="group" value="{{group}}">
	{{friends_form}}
	<button class="btn btn-primary mt-0">set members</button>
</form>
<hr>
<p>※新しいGroupの登録</p>
<form action="{% url 'creategroup' %}" method="post">
	{% csrf_token %}
	{{create_form}}
	<button class="btn btn-primary mt-1">create new group</button>
</form>
{% endblock %}

[Django]SNSアプリ1

### アプリケーションの作成
$ python manage.py startapp sns

– settingsにアプリケーション名を追加
/django_app/settings.py

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

### アプリケーションの設計
– 機能の洗い出し
– データベース設計
– 各ページの設計

### モデルの作成
– テーブルはUser, Message, Group, Friend, Good
– Message: owner(投稿者), group, content, share_id, good_count, share_count, pub_date
– Group: owner, title
– Friend: owner, user, group
– Good: owner, message
/sns/models.py

from django.db import models
from django.contrib.auth.models import User

# Message
class Message(models.Model):
	owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='message_owner')
	group = models.ForeignKey('Group', on_delete=models.CASCADE)
	content = models.TextField(max_length=1000)
	share_id = models.IntegerField(default=-1)
	good_count = models.IntegerField(default=0)
	pub_date = models.DateTimeField(auto_now_add=True)

	def __str__(self):
		return str(self.content) + ' (' + str(self.owner) + ')'

	class Meta:
		ordering = ('-pub_date',)

# Group
class Group(models.Model):
	owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='group_owner')
	title = models.CharField(max_length=100)

	def __str__(self):
		return '<' + self.title + '(' + str(self.owner) + ')>'

# Friend
class Friend(models.Model):
	owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friend_owner')
	user = models.ForeignKey(User, on_delete=models.CASCADE)
	group = models.ForeignKey(Group, on_delete=models.CASCADE)

	def __str__(self):
		return str(self.user) + ' (group:"' + str(self.group) + '")'

# Good
class Good(models.Model):
	owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='good_owner')
	message = models.ForeignKey(Message, on_delete=models.CASCADE)

	def __str__(self):
		return 'good for "' + str(self.message) + '" (by' + str(self.owner) + ')'

– -pub_dateはpub_dateの大きい順

### マイグレーション
$ python manage.py makemigrations sns
$ python manage.py migrate

### admin.py
/sns/admin.py

from django.contrib import admin
from .models import Message, Friend, Group, Good

admin.site.register(Message)
admin.site.register(Friend)
admin.site.register(Group)
admin.site.register(Good)

$ python manage.py runserver 192.168.33.10:8000

– AddUserからpublicユーザの作成
– ユーザ作成
– publicグループを用意

[Django]メッセージの投稿

### Messageモデル
/hello/models.py

class Message(models.Model):
	friend = models.ForeignKey(Friend, on_delete=models.CASCADE)
	title = models.CharField(max_length=100)
	content = models.CharField(max_length=300)
	pub_date = models.DateTimeField(auto_now_add=True)

	def __str__(self):
		return '<Message:id=' + str(self.id) + ', ' + self.title + '(' + str(self.pub_date) + ')>'

	class Meta:
		ordering = ('pub_date',)

– on_delete=models.CASCADEは削除を指定するという意味
– auto_now_add=Trueは自動的に値を設定
– Metaはクラスの基本的設定を記述

### マイグレーション実行
$ python manage.py makemigrations helloMigrations for ‘hello’:
hello/migrations/0002_auto_20200712_2230.py
– Alter field age on friend
– Alter field name on friend
– Create model Message
$ python manage.py migrate

### admin.py
/hello/admin.py

from django.contrib import admin
from .models import Friend, Message

admin.site.register(Friend)
admin.site.register(Message)

http://192.168.33.10:8000/admin/

### Messageページ作成
/hello/urls.py

	path('message/', views.message, name='message'),
	path('message/<int:page>', views.message, name='message'),

/hello/forms.py

from.models import Friend, Message

class MessageForm(forms.ModelForm):
	class Meta:
		model = Message
		fields = ['title', 'content', 'friend']
		widgets = {
			'title': forms.TextInput(attrs={'class':'formcontrol form-control-sm'}),
			'content': forms.Textarea(attrs={'class':'form-control form-control-sm', 'rows':2}),
			'friend': forms.Select(attrs={'class':'form-control form-control-sm'}),
		}

/hello/views.py

from .models import Friend, Message
from .forms import FriendForm, MessageForm

def message(request, page=1):
	if(request.method == 'POST'):
		obj = Message()
		form = MessageForm(request.POST, instance=obj)
		form.save()
	data = Message.objects.all().reverse()
	paginator = Paginator(data, 5)
	params = {
		'title': 'Message',
		'form': MessageForm(),
		'data': paginator.get_page(page),
	}
	return render(request, 'hello/message.html', params)

/hello/templates/hello/message.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<form action="{% url 'message' %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<input type="submit" value="send" class="btn btn-primary">
		<div class="mt-5"></div>
		<table class="table">
			<tr>
				<th class="py-1">title</th>
				<th class="py-1">name</th>
				<th class="py-1">datetime</th>
			</tr>
		{% for item in data %}
			<tr>
				<td class="py-2">{{item.title}}</td>
				<td class="py-2">{{item.friend.name}}</td>
				<td class="py-2">{{item.pub_date}}</td>
			</tr>
		{% endfor %}
		</table>

	<ul class="pagination justify-content-center">
		{% if data.has_previous %}
		<li class="page-item">
			<a class="page-link" href="{% url 'message' %}">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="{% url 'message' %}{{data.previous_page_number}}">
			&laquo; prev</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link">
			&laquo; prev</a>
		</li>
		{% endif %}
		<li class="page-item">
			<a class="page-link">
			{{data.number}}/{{data.paginator.num_pages}}</a>
		</li>
		{% if data.has_next %}
		<li class="page-item">
			<a class="page-link" href="{% url 'message' %}{{data.next_page_number}}">
			next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="{% url 'message' %}{{data.paginator.num_pages}}">
			last &raquo;</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link">last &raquo;</a>
		</li>
		{% endif %}
	</ul>
</body>

/hello/templates/hello/index.html

<table class="table">
			<tr>
				<th>id</th>
				<th>name</th>
				<th>age</th>
				<th>mail</th>
				<th>birthday</th>
				<th>Messages</th>
			</tr>
		{% for item in data %}
			<tr>
				<td>{{item.id}}</td>
				<td>{{item.name}}</td>
				<td>{{item.age}}</td>
				<td>{{item.mail}}</td>
				<td>{{item.birthday}}</td>
				<td><ul>
					{% for ob in item.message_set.all %}
						<li>{{ob.title}}</li>
					{% endfor %}
				</ul>
				</td>
			</tr>
		{% endfor %}
		</table>

[Django]テーブルのリレーション

### OneToManyの関連付け
主モデル
class A(models.Model):

従モデル
class B(models.Model)
項目 = models.ForeignKey(model name)
※ForeignKeyは外部クラスのキー

### OneToOneの関連付け
主モデル
class A(models.Model):

従モデル
class B(models.Model)
項目 = models.OneToOneField(model name)

### ManyToManyの関連付け
主モデル
class A(models.Model):

従モデル
class B(models.Model)
項目 = models.ManyToManyField(model name)

なるほど、MySQLのForeignKeyでindexを貼るって、レコードのリレーションの事だったのか。点と点が繋がるタイミングは感動する。

[Django]ページネーション

Djangoでは「Paginator」というクラスが用意されている
– 変数 = Paginator(コレクション、レコード数)
– 指定ページのレコードを取り出すには「get_page」メソッドを使う

### Paginatorを使用する
/hello/urls.py

urlpatterns = [ 
	path('', views.index, name='index'),
	path('<int:num>', views.index, name='index'),
	//
]

※ path(”, views.index, name=’index’)は削除しない

/hello/views.py

from django.core.paginator import Paginator

def index(request, num=1):
	data = Friend.objects.all()
	page = Paginator(data, 3)
	params = {
		'title': 'Hello',
		'message': '',
		'data': page.get_page(num),
	}		
	return render(request, 'hello/index.html', params)

Paginator(data, 3)でQuerySetオブジェクトからPaginatorインスタンスを作成する

/hello/templates/hello/index.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<table class="table">
		<tr>
			<th>id</th>
			<th>name</th>
			<th>age</th>
			<th>mail</th>
			<th>birth</th>
		</tr>
	{% for item in data %}
		<tr>
			<td>{{item.id}}</td>
			<td>{{item.name}}</td>
			<td>{{item.age}}</td>
			<td>{{item.mail}}</td>
			<td>{{item.birthday}}</td>
		</tr>
	{% endfor %}
	</table>
	<ul class="pagination">
		{% if data.has_previous %}
		<li class="page-item">
			<a class="page-link" href="{% url 'index' %}">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="{% url 'index' %}{{data.previous_page_number}}">
			&laquo; prev</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">
			&laquo; first</a>
		</li>
		<li class="page-item">
			<a class="page-link">
			&laquo; prev</a>
		</li>
		{% endif %}
		<li class="page-item">
			<a class="page-link">
			{{data.number}}/{{data.paginator.num_pages}}</a>
		</li>
		{% if data.has_next %}
		<li class="page-item">
			<a class="page-link" href="{% url 'index' %}{{data.next_page_number}}">
			next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link" href="{% url 'index' %}{{data.paginator.num_pages}}">
			last &raquo;</a>
		</li>
		{% else %}
		<li class="page-item">
			<a class="page-link">next &raquo;</a>
		</li>
		<li class="page-item">
			<a class="page-link">last &raquo;</a>
		</li>
		{% endif %}
	</ul>
</body>

これは凄い

[Django]バリデータ関数の作成とエラーメッセージ

### バリデータ作成
– reがpythonの正規表現モジュール
/hello/models.py

import re
from django.db import models
from django.core.validators import ValidationError

def number_only(value):
	if(re.match(r'^[0-9]*$', value) == None):
		raise ValidationError(
				'%(value)s is not Number!', params={'value': value},
			)

class Friend(models.Model):
	name = models.CharField(max_length=100, validators=[number_only])
	mail = models.EmailField(max_length=200)
	gender = models.BooleanField()
	age = models.IntegerField()
	birthday = models.DateField()

### エラーメッセージ作成
– フォーム全体のエラーメッセージを取り出すにはform.errorsを使う
– フォームの項目からエラーを取り出すことができる
/hello/templates/hello/check.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<ol class="list-group">
		{% for item in form %}
		<li class="list-group-item py-2">{{ item.name }}{{ item.value }}:{{ item.errors.as_text }}</li>
		{% endfor %}
	</ol>
	<table class="table mt-4">
		<form action="{% url 'check' %}" method="post">
			{% csrf_token %}
			<tr>
				<th>名前</th>
				<td>{{ form.name }}</td>
			</tr>
			<tr>
				<th>メール</th>
				<td>{{ form.mail }}</td>
			</tr>
			<tr>
				<th>性別</th>
				<td>{{ form.gender }}</td>
			</tr>
			<tr>
				<th>年齢</th>
				<td>{{ form.age }}</td>
			</tr>
			<tr>
				<th>誕生日</th>
				<td>{{ form.birthday }}</td>
			</tr>
			<tr><th></th><td>
			<input type="submit" value="click" class="btn btn-primary">
			</td></tr>
		</form>
	</table>
</body>

### ModelFormのカスタマイズ
/hello/forms.py

class FriendForm(forms.ModelForm):
	class Meta:
		model = Friend
		fields = ['name','mail','gender','age','birthday']
		widgets = {
			'name': forms.TextInput(attrs={'class':'form-control'}),
			'mail': forms.EmailInput(attrs={'class':'form-control'}),
			'age': forms.NumberInput(attrs={'class':'form-control'}),
			'birthday': forms.DateInput(attrs={'class':'form-control'}),
		}

/hello/templates/hello/check.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<ol class="list-group mb-4">
		{% for item in form %}
		<li class="list-group-item py-2">{{ item.name }}{{ item.value }}:{{ item.errors.as_text }}</li>
		{% endfor %}
	</ol>
		<form action="{% url 'check' %}" method="post">
			{% csrf_token %}
			<div class="form-group">名前{{ form.name }}</div>
			<div class="form-group">メール{{ form.mail }}</div>
			<div class="form-group">性別{{ form.gender }}</div>
			<div class="form-group">年齢{{ form.age }}</div>
			<div class="form-group">誕生日{{ form.birthday }}</div>
			<div class="form-group">
			<input type="submit" value="click" class="btn btn-primary">
			</div>
</body>