Django 從 0 到 1 打造完整電商平臺(tái):個(gè)人中心與用戶信息修改


IT策士 10余年一線大廠經(jīng)驗(yàn),專注 IT 思維、架構(gòu)、職場(chǎng)進(jìn)階。我會(huì)在公眾號(hào)、今日頭條持續(xù)發(fā)布最新文章,助你少走彎路。

大家好,我是IT策士。用戶登錄之后,第一件想做的事往往是看看自己的賬號(hào)信息,或者改個(gè)昵稱、換個(gè)密碼。今天我們就來打造電商平臺(tái)的 個(gè)人中心——讓用戶有一個(gè)屬于自己的"小窩",并且能自助修改手機(jī)號(hào)、郵箱、密碼等信息。

本篇的內(nèi)容會(huì)大量復(fù)用之前注冊(cè)模塊里的郵箱激活邏輯,同時(shí)引入 login_required 裝飾器來保護(hù)敏感頁面。跟著我一步步來,你會(huì)發(fā)現(xiàn) Django 處理這些用戶操作是多么順手。


一、需求分析

個(gè)人中心需要實(shí)現(xiàn)以下功能:

  • 展示基本信息:用戶名、手機(jī)號(hào)、郵箱、郵箱激活狀態(tài)。

  • 修改基本信息:允許用戶更換用戶名、手機(jī)號(hào)、郵箱。

  • 修改郵箱后,郵箱激活狀態(tài)重置為 False,并重新發(fā)送激活郵件。

  • 修改手機(jī)號(hào)時(shí),需要通過短信驗(yàn)證碼驗(yàn)證新手機(jī)號(hào)(暫用模擬)。

  • 修改密碼:舊密碼驗(yàn)證通過后才能設(shè)置新密碼。

  • 權(quán)限控制:僅登錄用戶可訪問,且只能修改自己的信息。


二、定義表單

apps/users/forms.py 中追加兩個(gè)表單。

2.1 個(gè)人信息表單

class UpdateProfileForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['username', 'phone', 'email']
        widgets = {
            'username': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': '用戶名'
            }),
            'phone': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': '手機(jī)號(hào)'
            }),
            'email': forms.EmailInput(attrs={
                'class': 'form-control',
                'placeholder': '郵箱'
            }),
        }

    def clean_phone(self):
        phone = self.cleaned_data.get('phone')
        if phone:
            # 排除當(dāng)前用戶,檢查手機(jī)號(hào)是否被其他人占用
            if User.objects.filter(phone=phone).exclude(pk=self.instance.pk).exists():
                raise forms.ValidationError('該手機(jī)號(hào)已被其他用戶綁定')
        return phone

    def clean_email(self):
        email = self.cleaned_data.get('email')
        if email:
            if User.objects.filter(email=email).exclude(pk=self.instance.pk).exists():
                raise forms.ValidationError('該郵箱已被其他用戶綁定')
        return email

這里使用 ModelForm 直接綁定用戶模型,簡(jiǎn)化字段定義。

2.2 修改密碼表單

class ChangePasswordForm(forms.Form):
    old_password = forms.CharField(
        label='當(dāng)前密碼',
        widget=forms.PasswordInput(attrs={
            'class': 'form-control',
            'placeholder': '請(qǐng)輸入當(dāng)前密碼'
        })
    )
    new_password = forms.CharField(
        label='新密碼',
        min_length=6,
        max_length=20,
        widget=forms.PasswordInput(attrs={
            'class': 'form-control',
            'placeholder': '新密碼(至少6位)'
        })
    )
    confirm_password = forms.CharField(
        label='確認(rèn)新密碼',
        widget=forms.PasswordInput(attrs={
            'class': 'form-control',
            'placeholder': '再次輸入新密碼'
        })
    )

    def clean(self):
        cleaned_data = super().clean()
        new = cleaned_data.get('new_password')
        confirm = cleaned_data.get('confirm_password')
        if new and confirm and new != confirm:
            raise forms.ValidationError('兩次新密碼不一致')
        return cleaned_data

三、編寫視圖

打開 apps/users/views.py,在原有基礎(chǔ)上增加以下內(nèi)容。

先確保導(dǎo)入齊全,頂部加入:

from django.contrib.auth.decorators import login_required
from django.contrib.auth import update_session_auth_hash
from .forms import UpdateProfileForm, ChangePasswordForm
import random
from django.core.mail import send_mail

3.1 個(gè)人中心主頁

@login_required(login_url='users:login')
def personal_center(request):
    return render(request, 'users/center.html')

這個(gè)視圖極為簡(jiǎn)單,只是渲染模板。模板里會(huì)直接使用 {{ user }} 顯示當(dāng)前登錄用戶的信息。

3.2 修改個(gè)人信息

@login_required(login_url='users:login')
def update_profile(request):
    user = request.user

    if request.method == 'POST':
        form = UpdateProfileForm(request.POST, instance=user)
        if form.is_valid():
            old_email = user.email
            form.save()

            # 如果郵箱發(fā)生了變更,重置激活狀態(tài)并發(fā)送激活郵件
            new_email = form.cleaned_data.get('email')
            if new_email and new_email != old_email:
                user.email_active = False
                user.save(update_fields=['email_active'])

                # 生成激活 token
                token = str(random.randint(100000, 999999))
                request.session[f'email_token_{user.id}'] = token
                activate_url = request.build_absolute_uri(
                    f'/users/activate/{user.id}/{token}/'
                )
                send_mail(
                    subject='重新激活你的電商賬號(hào)',
                    message=f'你的郵箱已更新,請(qǐng)點(diǎn)擊鏈接重新激活:{activate_url}',
                    from_email='noreply@example.com',
                    recipient_list=[new_email],
                )
                messages.warning(request, '郵箱已更新,請(qǐng)前往新郵箱查收激活郵件(終端查看)。')
            else:
                messages.success(request, '個(gè)人信息更新成功。')
            return redirect('users:center')
    else:
        form = UpdateProfileForm(instance=user)

    return render(request, 'users/update_profile.html', {'form': form})

說明:

  • 使用 instance=user 綁定當(dāng)前用戶,ModelForm 自動(dòng)完成保存。

  • 如果修改了郵箱,程序會(huì)自動(dòng)重置 email_active 并發(fā)送激活郵件。激活鏈接沿用第 6 篇的 activate_email 視圖,完全復(fù)用。

3.3 修改密碼

@login_required(login_url='users:login')
def change_password(request):
    if request.method == 'POST':
        form = ChangePasswordForm(request.POST)
        if form.is_valid():
            user = request.user
            old = form.cleaned_data['old_password']

            # 校驗(yàn)舊密碼是否正確
            if not user.check_password(old):
                form.add_error('old_password', '當(dāng)前密碼不正確')
                return render(request, 'users/change_password.html', {'form': form})

            # 設(shè)置新密碼并保存
            user.set_password(form.cleaned_data['new_password'])
            user.save()

            # 更新 session 認(rèn)證哈希,防止密碼修改后 session 失效
            update_session_auth_hash(request, user)

            messages.success(request, '密碼修改成功。')
            return redirect('users:center')
    else:
        form = ChangePasswordForm()

    return render(request, 'users/change_password.html', {'form': form})

關(guān)鍵點(diǎn)update_session_auth_hash 的作用是,在修改密碼后讓當(dāng)前用戶的 session 繼續(xù)保持有效,否則 Django 會(huì)立即將該用戶登出。這一行必不可少。

3.4 短信驗(yàn)證碼發(fā)送(復(fù)用)

我們?cè)诘?6 篇已經(jīng)寫了 send_sms_code 視圖,個(gè)人中心修改手機(jī)號(hào)時(shí)也可以調(diào)用它。由于前端 AJAX 請(qǐng)求需要 CSRF 令牌,我們會(huì)在模板中直接復(fù)用它。


四、配置 URL

apps/users/urls.py 中添加新路由:

urlpatterns = [
    # ... 之前的路由 ...
    path('center/', views.personal_center, name='center'),
    path('update/', views.update_profile, name='update_profile'),
    path('change_password/', views.change_password, name='change_password'),
]

五、設(shè)計(jì)模板

5.1 個(gè)人中心主頁

創(chuàng)建 apps/users/templates/users/center.html

{% extends 'base.html' %}
{% block title %}個(gè)人中心{% endblock %}

{% block content %}
<div class="row">
    <!-- 側(cè)邊欄 -->
    <div class="col-md-3">
        <div class="card shadow-sm">
            <div class="card-header bg-primary text-white">
                <h5 class="mb-0">個(gè)人中心</h5>
            </div>
            <ul class="list-group list-group-flush">
                <li class="list-group-item active">
                    <a href="{% url 'users:center' %}" class="text-white text-decoration-none">個(gè)人信息</a>
                </li>
                <li class="list-group-item">
                    <a href="{% url 'users:update_profile' %}" class="text-decoration-none">修改資料</a>
                </li>
                <li class="list-group-item">
                    <a href="{% url 'users:change_password' %}" class="text-decoration-none">修改密碼</a>
                </li>
                <li class="list-group-item">
                    <a href="#" class="text-decoration-none">收貨地址</a>
                </li>
                <li class="list-group-item">
                    <a href="#" class="text-decoration-none">我的訂單</a>
                </li>
            </ul>
        </div>
    </div>

    <!-- 主內(nèi)容 -->
    <div class="col-md-9">
        <div class="card shadow-sm">
            <div class="card-header bg-light">
                <h4 class="mb-0">?? 基本信息</h4>
            </div>
            <div class="card-body">
                <table class="table table-bordered">
                    <tr><th style="width:150px;">用戶名</th><td>{{ user.username }}</td></tr>
                    <tr><th>手機(jī)號(hào)</th><td>{{ user.phone|default:"未綁定" }}</td></tr>
                    <tr><th>郵箱</th><td>{{ user.email|default:"未綁定" }}</td></tr>
                    <tr>
                        <th>郵箱狀態(tài)</th>
                        <td>
                            {% if user.email_active %}
                                <span class="badge bg-success">已激活</span>
                            {% else %}
                                <span class="badge bg-warning text-dark">未激活</span>
                            {% endif %}
                        </td>
                    </tr>
                    <tr><th>注冊(cè)時(shí)間</th><td>{{ user.date_joined|date:"Y-m-d H:i" }}</td></tr>
                </table>
            </div>
        </div>
    </div>
</div>
{% endblock %}

5.2 修改個(gè)人信息頁面

創(chuàng)建 apps/users/templates/users/update_profile.html

{% extends 'base.html' %}
{% block title %}修改資料{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card shadow-sm">
            <div class="card-body p-4">
                <h3 class="text-center mb-4">?? 修改個(gè)人資料</h3>

                <form method="post" novalidate>
                    {% csrf_token %}

                    <div class="mb-3">
                        <label class="form-label">用戶名</label>
                        {{ form.username }}
                        {{ form.username.errors }}
                    </div>

                    <div class="mb-3">
                        <label class="form-label">手機(jī)號(hào)</label>
                        {{ form.phone }}
                        {{ form.phone.errors }}
                    </div>

                    <div class="mb-3">
                        <label class="form-label">郵箱</label>
                        {{ form.email }}
                        {{ form.email.errors }}
                    </div>

                    {% if form.non_field_errors %}
                        <div class="alert alert-danger">{{ form.non_field_errors.0 }}</div>
                    {% endif %}

                    <button type="submit" class="btn btn-primary w-100">保存修改</button>
                    <a href="{% url 'users:center' %}" class="btn btn-outline-secondary w-100 mt-2">取消</a>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

5.3 修改密碼頁面

創(chuàng)建 apps/users/templates/users/change_password.html

{% extends 'base.html' %}
{% block title %}修改密碼{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card shadow-sm">
            <div class="card-body p-4">
                <h3 class="text-center mb-4">?? 修改密碼</h3>

                <form method="post" novalidate>
                    {% csrf_token %}

                    <div class="mb-3">
                        <label class="form-label">{{ form.old_password.label }}</label>
                        {{ form.old_password }}
                        {{ form.old_password.errors }}
                    </div>

                    <div class="mb-3">
                        <label class="form-label">{{ form.new_password.label }}</label>
                        {{ form.new_password }}
                        {{ form.new_password.errors }}
                    </div>

                    <div class="mb-3">
                        <label class="form-label">{{ form.confirm_password.label }}</label>
                        {{ form.confirm_password }}
                        {{ form.confirm_password.errors }}
                    </div>

                    {% if form.non_field_errors %}
                        <div class="alert alert-danger">{{ form.non_field_errors.0 }}</div>
                    {% endif %}

                    <button type="submit" class="btn btn-danger w-100">修改密碼</button>
                    <a href="{% url 'users:center' %}" class="btn btn-outline-secondary w-100 mt-2">取消</a>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

六、更新導(dǎo)航欄

確保 templates/base.html 的導(dǎo)航欄中,"個(gè)人中心"鏈接指向 {% url 'users:center' %}

<li><a class="dropdown-item" href="{% url 'users:center' %}">個(gè)人中心</a></li>

七、完整流程測(cè)試

啟動(dòng)服務(wù)器:

python manage.py runserver

7.1 訪問個(gè)人中心

  1. 以任一用戶登錄,例如 13800138000

  2. 點(diǎn)擊導(dǎo)航欄的用戶名 → "個(gè)人中心"。

  3. 瀏覽器顯示用戶名、手機(jī)號(hào)、郵箱及激活狀態(tài)、注冊(cè)時(shí)間。

7.2 修改用戶名

  1. 在個(gè)人中心頁面點(diǎn)擊側(cè)邊欄"修改資料"(或直接訪問 /users/update/)。

  2. 將用戶名改為新名字,點(diǎn)擊"保存修改"。

  3. 頁面重定向回個(gè)人中心,提示"個(gè)人信息更新成功。",用戶名已更新。

終端輸出:

[21/May/2026 14:30:22] "POST /users/update/ HTTP/1.1" 302 0
[21/May/2026 14:30:22] "GET /users/center/ HTTP/1.1" 200 3654

7.3 修改郵箱并重新激活

  1. 在修改資料頁,將郵箱改為 newemail@example.com。

  2. 提交后,頁面提示"郵箱已更新,請(qǐng)前往新郵箱查收激活郵件(終端查看)。"

  3. 終端控制臺(tái)打印激活郵件:

Content-Type: text/plain; charset="utf-8"
Subject: 重新激活你的電商賬號(hào)
From: noreply@example.com
To: newemail@example.com

你的郵箱已更新,請(qǐng)點(diǎn)擊鏈接重新激活:http://127.0.0.1:8000/users/activate/2/835472/
  1. 復(fù)制鏈接在瀏覽器打開,提示"郵箱激活成功!",個(gè)人中心中郵箱狀態(tài)變?yōu)?已激活"。

控制臺(tái)記錄:

[21/May/2026 14:35:10] "POST /users/update/ HTTP/1.1" 302 0
...
MIME-Version: 1.0
...
[21/May/2026 14:35:10] "GET /users/center/ HTTP/1.1" 200 3654
[21/May/2026 14:37:02] "GET /users/activate/2/835472/ HTTP/1.1" 302 0

7.4 修改密碼

  1. 在個(gè)人中心側(cè)邊欄點(diǎn)擊"修改密碼"。

  2. 輸入當(dāng)前密碼、新密碼(如 newpass123)、確認(rèn)新密碼,提交。

  3. 提示"密碼修改成功。",并保持在登錄狀態(tài)(不會(huì)掉線)。

  4. 退出后重新登錄,舊密碼失效,新密碼生效。

驗(yàn)證舊密碼錯(cuò)誤的情況:

  • 故意輸錯(cuò)當(dāng)前密碼,提交后頁面顯示"當(dāng)前密碼不正確"。

終端輸出:

[21/May/2026 14:40:33] "POST /users/change_password/ HTTP/1.1" 200 2987

八、總結(jié)與下集預(yù)告

今天我們完成了用戶體系的核心閉環(huán):

  • 搭建了個(gè)人中心展示頁面,帶側(cè)邊欄導(dǎo)航;

  • 實(shí)現(xiàn)了個(gè)人信息修改,修改郵箱自動(dòng)觸發(fā)重新激活;

  • 實(shí)現(xiàn)了安全的密碼修改流程,確保舊密碼校驗(yàn)和 session 保持;

  • 所有視圖都加上 login_required 保護(hù),未登錄用戶自動(dòng)跳轉(zhuǎn)登錄頁。

有了牢固的用戶身份與個(gè)人資料管理基礎(chǔ),明天我們要進(jìn)入一個(gè)更貼近電商場(chǎng)景的功能——收貨地址管理。用戶下單前必須選擇地址,所以第 9 篇我們將實(shí)現(xiàn)地址的增刪改查,并支持設(shè)置默認(rèn)地址。

想了解更多還可以去公眾號(hào)、今日頭條搜索「IT策士」,一起升級(jí) IT 思維 !


本文為《Django 從 0 到 1 打造完整電商平臺(tái)》系列第 8 篇,作者:IT策士,未經(jīng)授權(quán)禁止轉(zhuǎn)載。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容