Django 2:如何使用邮箱验证和CBVs实现用户注册?
Django 2.0 原生实现邮箱验证式用户注册流程
我太懂那种因为政策限制没法用第三方模块的处境了,自己查Django文档拼出一套注册流程确实繁琐得要命。下面就手把手给你实现这个「邮箱验证+设置密码」的注册流程,全程用Django原生的类视图(CBV),完全适配2.0版本——毕竟这时候官方的registration模块还没出来呢。
先明确我们的目标流程:
- 用户在注册页填写
first_name、last_name和email(邮箱直接当用户名用) - 提交后收到带唯一令牌链接的验证邮件
- 点击链接跳转到设置密码页面,完成后自动登录到仪表板
1. 模型(Models)
首先得加个专门存储激活令牌的模型,用来绑定用户和验证链接里的唯一令牌,还要记录创建时间用来判断令牌是否过期:
# your_app/models.py from django.db import models from django.contrib.auth.models import User from django.utils.crypto import get_random_string from django.utils import timezone class AccountActivationToken(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) token = models.CharField(max_length=64, unique=True) created_at = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): # 自动生成唯一令牌 if not self.token: self.token = get_random_string(64) super().save(*args, **kwargs) @property def is_expired(self): # 令牌24小时内有效,可自行调整时长 return (timezone.now() - self.created_at).total_seconds() > 86400
2. 表单(Forms)
需要两个表单:一个是注册用的表单(只收集姓名和邮箱),另一个是设置密码的表单(直接用Django原生的即可):
# your_app/forms.py from django import forms from django.contrib.auth.models import User from django.contrib.auth.forms import SetPasswordForm class RegistrationForm(forms.ModelForm): class Meta: model = User fields = ['first_name', 'last_name', 'email'] def clean_email(self): # 确保邮箱唯一,因为要作为用户名使用 email = self.cleaned_data.get('email') if User.objects.filter(username=email).exists(): raise forms.ValidationError("这个邮箱已经注册过了") return email class UserSetPasswordForm(SetPasswordForm): # 直接继承Django原生的密码设置表单,无需额外修改 pass
3. 视图(Views)
全部用CBV实现,分三个核心视图:注册处理、令牌验证、密码设置:
# your_app/views.py from django.shortcuts import redirect, get_object_or_404, render from django.views.generic import FormView, View from django.contrib.auth import login, get_user_model from django.core.mail import send_mail from django.conf import settings from django.urls import reverse_lazy from .models import AccountActivationToken from .forms import RegistrationForm, UserSetPasswordForm User = get_user_model() class RegisterView(FormView): template_name = 'registration/register.html' form_class = RegistrationForm success_url = reverse_lazy('registration_success') def form_valid(self, form): # 创建用户,先设为未激活状态 email = form.cleaned_data['email'] user = form.save(commit=False) user.username = email # 邮箱作为用户名 user.is_active = False user.save() # 生成激活令牌 activation_token = AccountActivationToken.objects.create(user=user) # 发送验证邮件 activation_url = self.request.build_absolute_uri( reverse_lazy('activate_account', kwargs={'token': activation_token.token}) ) subject = "请验证你的邮箱以完成注册" message = f"点击下面的链接设置你的密码:\n{activation_url}" send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [email]) return super().form_valid(form) class ActivateAccountView(View): def get(self, request, token): # 验证令牌是否有效 token_obj = get_object_or_404(AccountActivationToken, token=token) if token_obj.is_expired: # 令牌过期,跳转到提示页(可扩展为重新发送令牌逻辑) return redirect('registration_expired') # 把用户ID存在session里,供后续设置密码使用 request.session['activation_user_id'] = token_obj.user.id return redirect('set_password') class SetPasswordView(FormView): template_name = 'registration/set_password.html' form_class = UserSetPasswordForm success_url = reverse_lazy('dashboard') def get_form_kwargs(self): # 将session中的用户传递给表单 kwargs = super().get_form_kwargs() user_id = self.request.session.get('activation_user_id') if user_id: kwargs['user'] = User.objects.get(id=user_id) return kwargs def form_valid(self, form): # 设置密码、激活用户并自动登录 user = form.save() user.is_active = True user.save() login(self.request, user) # 清理session中的临时数据 del self.request.session['activation_user_id'] return super().form_valid(form) # 辅助提示视图 class RegistrationSuccessView(View): def get(self, request): return render(request, 'registration/success.html') class RegistrationExpiredView(View): def get(self, request): return render(request, 'registration/expired.html')
4. URL配置(URLs)
把上面的视图映射到路由:
# your_app/urls.py from django.urls import path from .views import ( RegisterView, ActivateAccountView, SetPasswordView, RegistrationSuccessView, RegistrationExpiredView ) urlpatterns = [ path('register/', RegisterView.as_view(), name='register'), path('register/success/', RegistrationSuccessView.as_view(), name='registration_success'), path('activate/<str:token>/', ActivateAccountView.as_view(), name='activate_account'), path('activate/expired/', RegistrationExpiredView.as_view(), name='registration_expired'), path('set-password/', SetPasswordView.as_view(), name='set_password'), # 假设仪表板路由已定义 # path('dashboard/', DashboardView.as_view(), name='dashboard'), ]
5. 邮件配置(Settings.py)
测试阶段可以用控制台输出邮件内容,生产环境替换为真实SMTP配置:
# settings.py # 测试用控制台输出邮件 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' DEFAULT_FROM_EMAIL = 'your-email@example.com' # 生产环境SMTP示例配置 # EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # EMAIL_HOST = 'smtp.example.com' # EMAIL_PORT = 587 # EMAIL_USE_TLS = True # EMAIL_HOST_USER = 'your-email@example.com' # EMAIL_HOST_PASSWORD = 'your-email-password'
6. 基础模板示例
需要几个简单的模板来展示页面:
注册页面(registration/register.html)
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">注册</button> </form>
设置密码页面(registration/set_password.html)
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">设置密码并登录</button> </form>
注册成功提示页(registration/success.html)
<h1>注册成功!</h1> <p>请查收你的邮箱,点击链接完成密码设置。</p>
令牌过期提示页(registration/expired.html)
<h1>链接已过期</h1> <p>请重新发起注册请求。</p>
最后注意事项
- 记得运行
python manage.py makemigrations和python manage.py migrate创建令牌数据表 - 生产环境建议缩短令牌过期时长(比如1小时)
- 可扩展邮件内容为HTML格式,使用
send_mail的html_message参数 - 仪表板视图建议添加
LoginRequiredMixin确保仅登录用户可访问
内容的提问来源于stack exchange,提问作者Cajuu'




