IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我也会在其它平台持续发布最新文章,助你少走弯路。
大家好,我是IT策士。用户登录之后,第一件想做的事往往是看看自己的账号信息,或者改个昵称、换个密码。今天我们就来打造电商平台的 个人中心------让用户有一个属于自己的"小窝",并且能自助修改手机号、邮箱、密码等信息。
本篇的内容会大量复用之前注册模块里的邮箱激活逻辑,同时引入 login_required 装饰器来保护敏感页面。跟着我一步步来,你会发现 Django 处理这些用户操作是多么顺手。
一、需求分析
个人中心需要实现以下功能:
-
展示基本信息:用户名、手机号、邮箱、邮箱激活状态。
-
修改基本信息:允许用户更换用户名、手机号、邮箱。
-
修改邮箱后 ,邮箱激活状态重置为
False,并重新发送激活邮件。 -
修改手机号时,需要通过短信验证码验证新手机号(暂用模拟)。
-
修改密码:旧密码验证通过后才能设置新密码。
-
权限控制:仅登录用户可访问,且只能修改自己的信息。
二、定义表单
在 apps/users/forms.py 中追加两个表单。
2.1 个人信息表单
bash
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': '手机号'
}),
'email': forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '邮箱'
}),
}
def clean_phone(self):
phone = self.cleaned_data.get('phone')
if phone:
# 排除当前用户,检查手机号是否被其他人占用
if User.objects.filter(phone=phone).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError('该手机号已被其他用户绑定')
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直接绑定用户模型,简化字段定义。
2.2 修改密码表单
bash
class ChangePasswordForm(forms.Form):
old_password = forms.CharField(
label='当前密码',
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': '请输入当前密码'
})
)
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='确认新密码',
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,在原有基础上增加以下内容。
先确保导入齐全,顶部加入:
bash
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 个人中心主页
bash
@login_required(login_url='users:login')
def personal_center(request):
return render(request, 'users/center.html')
这个视图极为简单,只是渲染模板。模板里会直接使用
{``{ user }}显示当前登录用户的信息。
3.2 修改个人信息
bash
@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()
# 如果邮箱发生了变更,重置激活状态并发送激活邮件
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='重新激活你的电商账号',
message=f'你的邮箱已更新,请点击链接重新激活:{activate_url}',
from_email='noreply@example.com',
recipient_list=[new_email],
)
messages.warning(request, '邮箱已更新,请前往新邮箱查收激活邮件(终端查看)。')
else:
messages.success(request, '个人信息更新成功。')
return redirect('users:center')
else:
form = UpdateProfileForm(instance=user)
return render(request, 'users/update_profile.html', {'form': form})
说明:
-
使用
instance=user绑定当前用户,ModelForm自动完成保存。 -
如果修改了邮箱,程序会自动重置
email_active并发送激活邮件。激活链接沿用第 6 篇的activate_email视图,完全复用。
3.3 修改密码
bash
@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']
# 校验旧密码是否正确
if not user.check_password(old):
form.add_error('old_password', '当前密码不正确')
return render(request, 'users/change_password.html', {'form': form})
# 设置新密码并保存
user.set_password(form.cleaned_data['new_password'])
user.save()
# 更新 session 认证哈希,防止密码修改后 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})
关键点 :
update_session_auth_hash的作用是,在修改密码后让当前用户的 session 继续保持有效,否则 Django 会立即将该用户登出。这一行必不可少。
3.4 短信验证码发送(复用)
我们在第 6 篇已经写了 send_sms_code 视图,个人中心修改手机号时也可以调用它。由于前端 AJAX 请求需要 CSRF 令牌,我们会在模板中直接复用它。
四、配置 URL
在 apps/users/urls.py 中添加新路由:
bash
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'),
]
五、设计模板
5.1 个人中心主页
创建 apps/users/templates/users/center.html:
bash
{% extends 'base.html' %}
{% block title %}个人中心{% endblock %}
{% block content %}
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-3">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">个人中心</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">个人信息</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>
<!-- 主内容 -->
<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>手机号</th><td>{{ user.phone|default:"未绑定" }}</td></tr>
<tr><th>邮箱</th><td>{{ user.email|default:"未绑定" }}</td></tr>
<tr>
<th>邮箱状态</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>注册时间</th><td>{{ user.date_joined|date:"Y-m-d H:i" }}</td></tr>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
5.2 修改个人信息页面
创建 apps/users/templates/users/update_profile.html:
bash
{% 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">用户名</label>
{{ form.username }}
{{ form.username.errors }}
</div>
<div class="mb-3">
<label class="form-label">手机号</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 修改密码页面
创建 apps/users/templates/users/change_password.html:
bash
{% 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 %}
六、更新导航栏
确保 templates/base.html 的导航栏中,"个人中心"链接指向 {% url 'users:center' %}:
bash
<li><a class="dropdown-item" href="{% url 'users:center' %}">个人中心</a></li>
七、完整流程测试
启动服务器:
bash
python manage.py runserver
7.1 访问个人中心
-
以任一用户登录,例如
13800138000。 -
点击导航栏的用户名 → "个人中心"。
-
浏览器显示用户名、手机号、邮箱及激活状态、注册时间。
7.2 修改用户名
-
在个人中心页面点击侧边栏"修改资料"(或直接访问
/users/update/)。 -
将用户名改为新名字,点击"保存修改"。
-
页面重定向回个人中心,提示"个人信息更新成功。",用户名已更新。
终端输出:
bash
[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 修改邮箱并重新激活
-
在修改资料页,将邮箱改为
newemail@example.com。 -
提交后,页面提示"邮箱已更新,请前往新邮箱查收激活邮件(终端查看)。"
-
终端控制台打印激活邮件:
bash
Content-Type: text/plain; charset="utf-8"
Subject: 重新激活你的电商账号
From: noreply@example.com
To: newemail@example.com
你的邮箱已更新,请点击链接重新激活:http://127.0.0.1:8000/users/activate/2/835472/
- 复制链接在浏览器打开,提示"邮箱激活成功!",个人中心中邮箱状态变为"已激活"。
控制台记录:
bash
[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 修改密码
-
在个人中心侧边栏点击"修改密码"。
-
输入当前密码、新密码(如
newpass123)、确认新密码,提交。 -
提示"密码修改成功。",并保持在登录状态(不会掉线)。
-
退出后重新登录,旧密码失效,新密码生效。
验证旧密码错误的情况:
- 故意输错当前密码,提交后页面显示"当前密码不正确"。
终端输出:
bash
[21/May/2026 14:40:33] "POST /users/change_password/ HTTP/1.1" 200 2987
八、总结与下集预告
今天我们完成了用户体系的核心闭环:
-
搭建了个人中心展示页面,带侧边栏导航;
-
实现了个人信息修改,修改邮箱自动触发重新激活;
-
实现了安全的密码修改流程,确保旧密码校验和 session 保持;
-
所有视图都加上
login_required保护,未登录用户自动跳转登录页。
有了牢固的用户身份与个人资料管理基础,明天我们要进入一个更贴近电商场景的功能------收货地址管理。用户下单前必须选择地址,所以第 9 篇我们将实现地址的增删改查,并支持设置默认地址。
想了解更多还可以去其它平台搜索「IT策士」,一起升级 IT 思维 !
本文为《Django 从 0 到 1 打造完整电商平台》系列第 8 篇,作者:IT策士,未经授权禁止转载。