BBSプロジェクト開発の論理的整理

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);
}
}
});
});
});

タグ: Django BBS ブログ フォーラム 認証

7月21日 21:19 投稿