You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Django 2:如何使用邮箱验证和CBVs实现用户注册?

Django 2.0 原生实现邮箱验证式用户注册流程

我太懂那种因为政策限制没法用第三方模块的处境了,自己查Django文档拼出一套注册流程确实繁琐得要命。下面就手把手给你实现这个「邮箱验证+设置密码」的注册流程,全程用Django原生的类视图(CBV),完全适配2.0版本——毕竟这时候官方的registration模块还没出来呢。

先明确我们的目标流程:

  • 用户在注册页填写first_namelast_nameemail(邮箱直接当用户名用)
  • 提交后收到带唯一令牌链接的验证邮件
  • 点击链接跳转到设置密码页面,完成后自动登录到仪表板

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 makemigrationspython manage.py migrate创建令牌数据表
  • 生产环境建议缩短令牌过期时长(比如1小时)
  • 可扩展邮件内容为HTML格式,使用send_mailhtml_message参数
  • 仪表板视图建议添加LoginRequiredMixin确保仅登录用户可访问

内容的提问来源于stack exchange,提问作者Cajuu'

火山引擎 最新活动