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_mail3.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 runserver7.1 訪問個(gè)人中心
以任一用戶登錄,例如
13800138000。點(diǎn)擊導(dǎo)航欄的用戶名 → "個(gè)人中心"。
瀏覽器顯示用戶名、手機(jī)號(hào)、郵箱及激活狀態(tài)、注冊(cè)時(shí)間。
7.2 修改用戶名
在個(gè)人中心頁面點(diǎn)擊側(cè)邊欄"修改資料"(或直接訪問
/users/update/)。將用戶名改為新名字,點(diǎn)擊"保存修改"。
頁面重定向回個(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 36547.3 修改郵箱并重新激活
在修改資料頁,將郵箱改為
newemail@example.com。提交后,頁面提示"郵箱已更新,請(qǐng)前往新郵箱查收激活郵件(終端查看)。"
終端控制臺(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/復(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 07.4 修改密碼
在個(gè)人中心側(cè)邊欄點(diǎn)擊"修改密碼"。
輸入當(dāng)前密碼、新密碼(如
newpass123)、確認(rèn)新密碼,提交。提示"密碼修改成功。",并保持在登錄狀態(tài)(不會(huì)掉線)。
退出后重新登錄,舊密碼失效,新密碼生效。
驗(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)載。