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

[Django]ModelFormのバリデーション

– バリデーションはsaveの時に実行されるので、モデルの更新時にはモデル側でバリデーションをかける

/hello/forms.py

class FriendForm(forms.ModelForm):
	class Meta:
		model = Friend
		fields = ['name','mail','gender','age','birthday']

/hello/models.py

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()

– save以外に自分でチェックをかけることも可能
/hello/views.py

def check(request):
	params = {
		'title': 'Hello',
		'message': 'check validation.',
		'form': FriendForm(),
	}
	if(request.method == 'POST'):
		obj = Friend()
		form = FriendForm(request.POST, instance=obj)
		params['form'] = form
		if(form.is_valid()):
			params['message'] = 'OK!'
		else:
			params['message'] = 'no good.'
	return render(request, 'hello/check.html', params)

/hello/templates/hello/check.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<form action="{% url 'check' %}" method="post">
		{% csrf_token %}
		<table class="table">
		{{ form.as_table }}
		<tr><th></th><td>
		<input type="submit" value="click" class="btn btn-primary mt-2">
		</td></tr>
		</table>
	</form>
</body>

– forms.FormとModelFormのバリデーションは異なる

### モデルにバリデーションの組み込み
/hello/models.py

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

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

### モデルで使えるバリデータ
– MinValueValidator/MaxValueValidator, MinLengthValidator/MaxLengthValidator

from django.db import models
from django.core.validators import MinLengthValidator

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

– EmailValidator/URLValidator
– ProhibitNullCharactersValidator
– RegexValidator

from django.core.validators import RegexValidator

class Friend(models.Model):
	name = models.CharField(max_length=100, validators=[RegexValidator(r'^[a-z]*$')])

バリデーション定義によってmodelに組み込んでいくイメージです。

[Django]バリデーションの種類

### CharFieldのバリデーション
– required
– min_length, max_length
– empty_value

class CheckForm(forms.Form):
	empty = forms.CharField(label='Empty', empty_value=True, widget=forms.TextInput(attrs={'class':'form-control'}))
	min = forms.CharField(label='Min', min_length=10, widget=forms.TextInput(attrs={'class':'form-control'}))	
	max = forms.CharField(label='Max', max_length=10, widget=forms.TextInput(attrs={'class':'form-control'}))

### IntegerField/FloatFieldのバリデーション
– required
– min_value, max_value

class CheckForm(forms.Form):
	required = forms.IntegerField(label='Required', widget=forms.NumberInput(attrs={'class':'form-control'}))
	min = forms.IntegerField(label='Min', min_value=100, widget=forms.NumberInput(attrs={'class':'form-control'}))	
	max = forms.IntegerField(label='Max', max_value=1000, widget=forms.NumberInput(attrs={'class':'form-control'}))

### 日時関連のバリデーション
– DateField, TimeField, DateTimeFieldでは日時の各値を表す記号を組み合わせて作成する
– %y, %m, %d, %H, %M, %S

class CheckForm(forms.Form):
	date = forms.DateField(label='Date', input_formats=['%d'], widget=forms.DateInput(attrs={'class':'form-control'}))
	time = forms.TimeField(label='Time', widget=forms.TimeInput(attrs={'class':'form-control'}))
	datetime = forms.DateTimeField(label='DateTime', widget=forms.DateTimeInput(attrs={'class':'form-control'}))

### バリデーションを追加
– Formクラスにメソッドを追加する
– raise ValidationError(“Error message”)
/hello/forms.py

from django import forms

class CheckForm(forms.Form):
	str = forms.CharField(label='String', widget=forms.TextInput(attrs={'class':'form-control'}))

	def clean(self):
		cleaned_data = super().clean()
		str = cleaned_data['str']
		if(str.lower().startswith('no')):
			raise forms.ValidationError('Your input "No!"')

[Django]バリデーション基礎

if(<

>.is_valid()):
# エラー時の処理
else:
# 正常時の処理

### バリデーションを使ってみる
/hello/templates/hello/check.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<form action="{% url 'check' %}" method="post">
		{% csrf_token %}
		{{ form.as_table }}
		<input type="submit" value="click" class="btn btn-primary mt-2">
	</form>
</body>

/hello/urls.py

	path('check', views.check, name='check')

/hello/forms.py

class CheckForm(forms.Form):
	str = forms.CharField(label='Name', widget=forms.TextInput(attrs={'class':'form-control'}))	

/hello/views.py

from .forms import CheckForm

def check(request):
	params = {
		'title': 'Hello',
		'message': 'check validation.',
		'form': CheckForm(),
	}
	if(request.method == 'POST'):
		form = CheckForm(request.POST)
		param['form'] = form
		if(form.is_valid()):
			params['message'] = 'OK!'
		else:
			params['message'] = 'no good.'
	return render(request, 'hello/check.html', params)

スペルミスでmethod=”post”がmetho=”post”になってた。道理で動かない訳だ。

[Django]SQLを直接実行

Managerクラスに用意されている「raw」メソッドを使う

/hello/views.py

def find(request):
	if(request.method == 'POST'):
		msg = request.POST['find']
		form = FindForm(request.POST)
		sql = 'select * from hello_friend'
		if(msg != ''):
			sql += ' where ' + msg
		data = Friend.objects.raw(sql)
		msg = sql
	else:
		msg = 'search words...'
		form = FindForm()
		data =Friend.objects.all()
	params = {
		'title': 'Hello',
		'message': msg,
		'form': form,
		'data': data,
	}
	return render(request, 'hello/find.html', params)

テーブル名はhello_friend
マイグレーションの際に「アプリケーション名_モデル名」で作成される

まあ、SQLの方が慣れているといえば慣れてます。

[Django]レコードの並び替え

Managerクラスの「order_by」メソッドで行う

/hello/views.py

def index(request):
	data = Friend.objects.all().order_by('age')
	params = {
		'title': 'Hello',
		'message': '',
		'data': data,
	}		
	return render(request, 'hello/index.html', params)

/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>
</body>

逆の場合はorder_by().reverse()とする

data = Friend.objects.all().order_by('age').reverse()

### 指定した範囲のレコードを取り出す
/hello/views.py

def find(request):
	if(request.method == 'POST'):
		msg = 'Search Result:'
		form = FindForm(request.POST)
		find = request.POST['find']
		list = find.split()
		data = Friend.objects.all()[int(list[0]):int(list[1])]
	else:
		msg = 'search words...'
		form = FindForm()
		data =Friend.objects.all()
	params = {
		'title': 'Hello',
		'message': msg,
		'form': form,
		'data': data,
	}
	return render(request, 'hello/find.html', params)

/hello/templates/hello/find.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<form action="{% url 'find' %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<tr>
			<th></th><td><input type="submit" value="click" class="btn btn-primary mt-2"></td>
		</tr>
	</form>
	<table class="table">
		<tr>
			<th>id</th>
			<th>name</th>
			<th>age</th>
			<th>mail</th>
			<th>birthday</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>
</body>

### レコードの集計
aggregateメソッドで集計を行わせる
– count: レコード数
– sum: 合計
– avg: 平均
– min: 最小値
– max: 最大値

/hello/views.py

from django.db.models import Count, Sum, Avg, Min, Max

def index(request):
	data = Friend.objects.all()
	re1 = Friend.objects.aggregate(Count('age'))
	re2 = Friend.objects.aggregate(Sum('age'))
	re3 = Friend.objects.aggregate(Avg('age'))
	re4 = Friend.objects.aggregate(Min('age'))
	re5 = Friend.objects.aggregate(Max('age'))
	msg = 'count:' + str(re1['age__count']) + '<br>Sum:' + str(re2['age__sum']) + '<br>Average:' + str(re3['age__avg']) + '<br>Min:' + str(re4['age__min']) + '<br>Max:' + str(re5['age__max'])
	params = {
		'title': 'Hello',
		'message': '',
		'data': data,
	}		
	return render(request, 'hello/index.html', params)

avgの小数点以下がおかしなことになってますが、filterを使えば大抵の事は出来るとのこと。

[Django]filterによる検索

モデルにはobjects属性があり、その中にManagerというクラスのインスタンスが入っている
このManagerにfilter機能がある

/hello/urls.py

urlpatterns = [ 
	path('', views.index, name='index'),
	path('create', views.create, name='create'),
	path('edit/<int:num>', views.edit, name='edit'),
	path('delete/<int:num>', views.delete, name='delete'),
	path('list', FriendList.as_view()),
	path('detail/<int:pk>', FriendDetail.as_view()),
	path('find', view.find, name='find'), # 追加
]

/hello/forms.py

class FindForm(forms.Form):
	find = forms.CharField(label='Find', required=False, widget=forms.TextInput(attrs={'class':'form-control'}))

/hello/templates/hello/find.html

<body class="container">
	<h1 class="display-4 text-primary">{{title}}</h1>
	<p>{{message|safe}}</p>
	<form action="{% url 'find' %}" method="post">
		{% csrf_token %}
		{{ form.as_p }}
		<tr>
			<th></th><td><input type="submit" value="click" class="btn btn-primary mt-2"></td>
		</tr>
	</form>
	<table class="table">
		<tr>
			<th>id</th>
			<th>name</th>
			<th>mail</th>
		</tr>
	{% for item in data %}
		<tr>
			<td>{{item.id}}</td>
			<td>{{item.name}}({{item.age}})</td>
			<td>{{item.mail}}</td>
		</tr>
	{% endfor %}
	</table>
</body>

/hello/views.py

def find(request):
	if(request.method == 'POST'):
		form = FindForm(request.POST)
		find = request.POST['find']
		data = Friend.objects.filter(name=find)
		msg = 'Result: ' + str(data.count())
	else:
		msg = 'search words...'
		form = FindForm()
		data =Friend.objects.all()
	params = {
		'title': 'Hello',
		'message': msg,
		'form': form,
		'data': data,
	}
	return render(request, 'hello/find.html', params)

Friend.objects.filter(name=find)で指定している。

### 曖昧検索
– 値を含む検索: __contains=value
– 値で始まるもの: __startswith=value
– 値で終わるもの: __endswith=value

if(request.method == 'POST'):
		form = FindForm(request.POST)
		find = request.POST['find']
		data = Friend.objects.filter(name__contains=find)
		msg = 'Result: ' + str(data.count())

– 大文字小文字を区別しない: __iexact=value
– 曖昧検索: __icontains=value, __istartswith=value, __iendswith=value

### 数値の比較
– 等しい: =value
– 大きい: __gt=value
– 以上: __gte=value
– 小さい: __lt=value
– 以下: __lte=value

if(request.method == 'POST'):
		form = FindForm(request.POST)
		find = request.POST['find']
		data = Friend.objects.filter(age__lte=int(find))
		msg = 'Result: ' + str(data.count())

●●以上●●以下
split()は改行やスペースで分割する

	if(request.method == 'POST'):
		form = FindForm(request.POST)
		find = request.POST['find']
		val = find.split()
		data = Friend.objects.filter(age__gte=val[0], age__lte=val[1])
		msg = 'Result: ' + str(data.count())

### AとBどちらも検索
変数 = <<モデル>>.object.filter(Q(条件) | Q(条件))

from django.db.models import Q
// 
data = Friend.objects.filter(Q(name__contains=find)|Q(mail__contains=find))

### リストを使って検索

	if(request.method == 'POST'):
		form = FindForm(request.POST)
		find = request.POST['find']
		list = find.split()
		data = Friend.objects.filter(name__in=list)
		msg = 'Result: ' + str(data.count())

検索条件はアプリケーションの要件や機能によって変わってきますね。