1. データベース設計
データベース設計のルール:
1. 基本テーブルを作成:ユーザー、サイト、記事、タグ、カテゴリ、記事とタグの関係テーブル、いいね싫어요
2. 各テーブルの基本フィールドを定義
3. 外部キー(1対1、1対多、多対多)を追加
4. 関係テーブルの作成
注意事項:外部キーを作成する際には、`to='テーブル名'`を使い、`null=True`を忘れずに設定。すべての外部キーに付けない場合もある。
2. settingsの構成
settingsの構成が必要:
1. データベース設定
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbssite',
'USER': 'admin',
'PASSWORD': 'admin123',
'HOST': 'localhost',
'PORT': 3306,
'CHARSET': 'utf8mb4'
}
}
2. 静的リソースの設定
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'staticfiles')
]
3. ユーザーモデルの設定
models.pyファイルのユーザークラスはAbstractUserを継承する。
AUTH_USER_MODEL = 'app01.UserInfo'
4. メディアリソースの設定 + URLルートの設定
メディアルートを定義し、ユーザーがデフォルトのプロフィール画像にアクセスできるようにする。
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
urls.pyファイルに画像のルートを設定:
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
3. 関数開発
3.1 登録機能
後端開発論理:
構成要素:register関数 + register.html + myform.pyファイル
後端開発論理:
# myform.py ファイル
1. myform.pyファイルを作成し、ユーザー登録用のフォームを作成
class MyRegForm(forms.Form):
username = forms.CharField(label='ユーザー名')
password = forms.CharField(label='パスワード', widget=forms.PasswordInput)
password2 = forms.CharField(label='パスワード確認', widget=forms.PasswordInput)
email = forms.EmailField(label='メールアドレス')
def clean_username(self):
username = self.cleaned_data.get('username')
if models.UserInfo.objects.filter(username=username).exists():
raise forms.ValidationError('このユーザー名は既に存在します')
return username
def clean_password2(self):
password = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if password != password2:
raise forms.ValidationError('パスワードが一致しません')
return password2
# views.py ファイル
def register(request):
form_obj = myform.MyRegForm()
if request.method == 'POST':
form = myform.MyRegForm(request.POST, request.FILES)
if form.is_valid():
cleaned_data = form.cleaned_data
cleaned_data.pop('password2')
avatar = cleaned_data.get('avatar')
if avatar:
cleaned_data['avatar'] = avatar
user = models.UserInfo.objects.create_user(**cleaned_data)
back_dic = {
'msg': '登録成功しました',
'url': '/login/'
}
return JsonResponse(back_dic)
else:
back_dic = {
'code': 4001,
'msg': form.errors
}
return JsonResponse(back_dic)
return render(request, 'register.html', locals())
前端開発論理:
画像アップロードと登録ボタンの機能
<script>
$(document).ready(function() {
// アバター画像プレビュー
$('#avatarSelect').on('change', function() {
const reader = new FileReader();
const file = $(this)[0].files[0];
reader.readAsDataURL(file);
reader.onload = function() {
$('#previewImg').attr('src', reader.result);
};
});
// 登録ボタンクリック
$('#submitBtn').click(function() {
const formData = new FormData();
$.each($('#registerForm').serializeArray(), function(i, obj) {
formData.append(obj.name, obj.value);
});
formData.append('avatar', $('#avatarSelect')[0].files[0]);
$.ajax({
url: '/register/',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
if (response.code === 1000) {
window.location.href = response.url;
} else {
$('#errorDiv').text(response.msg);
}
}
});
});
});
</script>
3.2 ログイン機能
後端開発論理:
urls.pyファイル:
url(r'^login/$', views.login_view, name='login'),
views.pyファイル:
def login_view(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
if code.lower() == request.session.get('code').lower():
user = auth.authenticate(username=username, password=password)
if user:
auth.login(request, user)
return JsonResponse({
'code': 1000,
'msg': 'ログイン成功',
'url': '/home/'
})
else:
return JsonResponse({
'code': 4000,
'msg': 'ユーザー名またはパスワードが間違っています'
})
else:
return JsonResponse({
'code': 4001,
'msg': '認証コードが間違っています'
})
return render(request, 'login.html')
前端開発論理:
<img src="/get_code/" id="verifyImg" alt="認証コード" width="360" height="35">
<div class="input-group">
<input type="text" class="form-control" name="username" placeholder="ユーザー名">
</div>
<div class="input-group">
<input type="password" class="form-control" name="password" placeholder="パスワード">
</div>
<div class="input-group">
<input type="text" class="form-control" name="code" placeholder="認証コード">
</div>
<button type="submit" class="btn btn-primary">ログイン</button>
<script>
$(document).ready(function() {
// 認証コード画像をクリックしたら更新
$('#verifyImg').click(function() {
const oldPath = $(this).attr('src');
$(this).attr('src', oldPath + '?' + Math.random());
});
// ログインボタンクリック
$('button[type="submit"]').click(function() {
const formData = new FormData();
formData.append('username', $('input[name="username"]').val());
formData.append('password', $('input[name="password"]').val());
formData.append('code', $('input[name="code"]').val());
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
$.ajax({
url: '/login/',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
if (response.code === 1000) {
window.location.href = response.url;
} else {
$('#errorDiv').text(response.msg);
}
}
});
});
});
</script>
3.3 画像認証コードの生成
# views.py ファイル
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
def generate_verification_code(request):
# 背景色のランダム生成
def random_color():
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
# 認証コードの生成
def create_code():
code = ''
for _ in range(5):
random_num = str(random.randint(0, 9))
random_upper = chr(random.randint(65, 90))
random_lower = chr(random.randint(97, 122))
code += random.choice([random_num, random_upper, random_lower])
return code
# 認証コードの画像生成
img = Image.new('RGB', (360, 35), random_color())
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('static/fonts/arial.ttf', 30)
code = create_code()
request.session['code'] = code
for i, char in enumerate(code):
draw.text((i*60 + 60, 0), char, random_color(), font)
byte_io = BytesIO()
img.save(byte_io, 'PNG')
return HttpResponse(byte_io.getvalue())
3.4 画像認証コードの自動更新
<img src="/generate_verification_code/" id="verifyCode" alt="認証コード" width="360" height="35">
<script>
$(document).ready(function() {
// 認証コード画像クリック時の自動更新
$('#verifyCode').click(function() {
const currentUrl = $(this).attr('src');
$(this).attr('src', currentUrl + '?' + new Date().getTime());
});
});
</script>
3.5 ホームページの構築
後端開発論理:
def home(request):
articles = models.Article.objects.all().order_by('-create_time')
return render(request, 'home.html', {'articles': articles})
前端開発論理:
<div class="container">
<div class="row">
<div class="col-xs-12">
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="#">BBSサイト</a>
</div>
<ul class="nav navbar-nav">
<li><a href="#">ホーム</a></li>
<li><a href="#">カテゴリ</a></li>
<li><a href="#">タグ</a></li>
<li><a href="#">検索</a></li>
</ul>
</nav>
</div>
</div>
<div class="row">
<div class="col-md-2">
<div class="panel panel-default">
<div class="panel-heading">ユーザー情報</div>
<div class="panel-body">
{% if request.user.is_authenticated %}
<p>ようこそ、{{ request.user.username }}さん!</p>
<a href="/set_password/" class="btn btn-primary">パスワード変更</a>
<a href="/logout/" class="btn btn-danger">ログアウト</a>
{% else %}
<a href="/login/" class="btn btn-success">ログイン</a>
<a href="/register/" class="btn btn-info">ユーザー登録</a>
{% endif %}
</div>
</div>
</div>
<div class="col-md-8">
<div class="article-list">
{% for article in articles %}
<div class="article-item">
<h3>{{ article.title }}</h3>
<p>作成日:{{ article.create_time }}</p>
<p>カテゴリ:{{ article.category }}</p>
<p>タグ:{{ article.tags.all|join:", " }}</p>
<p>内容:{{ article.content|truncatewords:30 }}</p>
<div class="text-right">
<a href="#" class="btn btn-default">詳細</a>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="col-md-2">
<div class="panel panel-default">
<div class="panel-heading">サイト情報</div>
<div class="panel-body">
<p>サイト名:{{ site.name }}</p>
<p>管理人:{{ site.admin }}</p>
<p>記事数:{{ site.article_count }}</p>
</div>
</div>
</div>
</div>
</div>
3.6 ログアウト機能
後端開発論理:
from django.contrib.auth.decorators import login_required
from django.contrib.auth import logout
from django.shortcuts import redirect
@login_required
def logout_view(request):
logout(request)
return redirect('/home/')
前端開発論理:
{% if request.user.is_authenticated %}
<div class="user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{ request.user.username }}<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="/set_password/">パスワード変更</a></li>
<li><a href="/logout/">ログアウト</a></li>
</ul>
</div>
{% else %}
<div class="login-menu">
<a href="/login/" class="btn btn-primary">ログイン</a>
<a href="/register/" class="btn btn-info">ユーザー登録</a>
</div>
{% endif %}
3.7 パスワード変更機能
後端開発論理:
@login_required
def change_password(request):
if request.method == 'POST':
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
if not request.user.check_password(old_password):
return JsonResponse({'code': 4000, 'msg': '旧パスワードが間違っています'})
if new_password != confirm_password:
return JsonResponse({'code': 4001, 'msg': '新しいパスワードと確認パスワードが一致しません'})
request.user.set_password(new_password)
request.user.save()
auth.logout(request)
return JsonResponse({'code': 1000, 'msg': 'パスワード変更成功', 'url': '/login/'})
return render(request, 'password_change.html')
前端開発論理:
旧パスワード
新パスワード
パスワード確認
パスワード変更
$(document).ready(function() {
$('#passwordChangeForm').submit(function(e) {
e.preventDefault();
const formData = new FormData(this);
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
$.ajax({
url: '/set_password/',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
if (response.code === 1000) {
window.location.href = response.url;
} else {
$('#errorDiv').text(response.msg);
}
}
});
});
});