You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Djoser框架下邮箱修改验证问题及重置流程功能扩展需求

Djoser框架下邮箱修改验证问题及重置流程功能扩展需求

兄弟,我完全懂你现在的困扰——在Djoser框架里修改用户邮箱时,没法完成有效的验证闭环,而且你想要一套更严谨的邮箱重置+激活机制:用户先提交注册邮箱申请重置,收到带uid和token的链接后,提交新邮箱,此时账号被设为未激活状态,必须通过新邮箱收到的激活链接验证后才能恢复使用。下面我帮你一步步实现这个需求:

一、先梳理现有代码的问题

你当前的配置里开启了USERNAME_CHANGED_EMAIL_CONFIRMATION,但Djoser默认的邮箱修改流程只是发送确认邮件,不会禁用账号,也不会强制新邮箱激活。所以我们需要自定义两个核心端点reset_email/(申请重置邮箱)和reset_email_confirm/(确认新邮箱并触发激活)。

二、扩展序列化器(serializers.py)

先写两个序列化器,分别处理重置请求和确认请求的参数校验:

from rest_framework import serializers
from djoser.serializers import UidAndTokenSerializer
from .models import User
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_decode
from django.utils.encoding import force_str

# 处理重置邮箱的请求(校验邮箱是否存在)
class ResetEmailSerializer(serializers.Serializer):
    email = serializers.EmailField()

    def validate_email(self, value):
        try:
            # 排除软删除的用户
            User.objects.get(email=value, soft_deleted=False)
        except User.DoesNotExist:
            raise serializers.ValidationError("该邮箱未注册或已被软删除")
        return value

# 处理重置确认的请求(校验uid、token,以及新邮箱是否可用)
class ResetEmailConfirmSerializer(UidAndTokenSerializer):
    new_email = serializers.EmailField()

    def validate(self, attrs):
        # 调用Djoser自带的方法验证uid和token
        self.user = self.get_user(attrs["uid"])
        if not default_token_generator.check_token(self.user, attrs["token"]):
            raise serializers.ValidationError("无效的验证链接,请检查链接是否过期或正确")
        
        # 检查新邮箱是否已被其他用户使用
        if User.objects.filter(email=attrs["new_email"]).exists():
            raise serializers.ValidationError("该邮箱已被注册,请更换其他邮箱")
        return attrs

三、自定义视图逻辑(views.py)

接下来写两个视图,处理这两个端点的业务逻辑:

from rest_framework import generics, status
from rest_framework.response import Response
from djoser.utils import send_activation_email
from .serializers import ResetEmailSerializer, ResetEmailConfirmSerializer
from .models import User
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.core.mail import send_mail
from django.conf import settings

# 处理重置邮箱申请的视图
class ResetEmailView(generics.GenericAPIView):
    serializer_class = ResetEmailSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = User.objects.get(email=serializer.validated_data["email"])
        
        # 生成uid和token(和Djoser激活/重置密码的逻辑一致)
        uid = urlsafe_base64_encode(force_bytes(user.pk))
        token = default_token_generator.make_token(user)
        
        # 构建前端的重置确认页面链接(替换成你的实际前端地址)
        reset_confirm_url = f"{settings.FRONTEND_URL}/auth/reset-email-confirm/{uid}/{token}"
        
        # 发送重置邮箱通知邮件
        send_mail(
            subject="【你的平台】重置邮箱地址申请",
            message=f"你申请重置邮箱地址,请点击下方链接完成操作:\n{reset_confirm_url}\n如果不是你操作,请忽略此邮件",
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[user.email],
            fail_silently=False,
        )
        return Response({"detail": "重置邮箱链接已发送至你的注册邮箱,请查收"}, status=status.HTTP_200_OK)

# 处理重置邮箱确认的视图
class ResetEmailConfirmView(generics.GenericAPIView):
    serializer_class = ResetEmailConfirmSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.user
        new_email = serializer.validated_data["new_email"]
        
        # 核心逻辑:更新用户邮箱,将账号设为未激活,然后发送新邮箱激活链接
        user.email = new_email
        user.is_active = False
        user.save()
        
        # 调用Djoser自带的发送激活邮件方法(复用现有激活逻辑)
        send_activation_email(user, request)
        
        return Response({"detail": "新邮箱已提交,激活链接已发送至你的新邮箱,请验证后登录"}, status=status.HTTP_200_OK)

四、添加路由(urls.py)

把自定义的端点加到路由里:

from django.urls import path, include
from .views import ResetEmailView, ResetEmailConfirmView

urlpatterns = [
    # 保留Djoser默认的路由
    path('auth/', include('djoser.urls')),
    # 自定义的重置邮箱端点
    path('auth/users/reset_email/', ResetEmailView.as_view(), name='reset_email'),
    path('auth/users/reset_email_confirm/', ResetEmailConfirmView.as_view(), name='reset_email_confirm'),
]

五、配置与注意事项

  1. 确保Djoser配置正确:你现有settings.py里的SEND_ACTIVATION_EMAILACTIVATION_URL已经配置好了,不需要额外修改,激活邮件会自动使用你设置的ACTIVATION_URL
  2. 邮件模板优化:如果觉得默认邮件内容太简单,可以自定义Djoser的邮件模板,在templates/djoser/email/activation.html(或txt格式)里修改内容。
  3. Token有效期:Djoser默认的token有效期是3天,如果需要调整,可以自定义default_token_generator
  4. 软删除处理:我们在序列化器里已经排除了软删除的用户,符合你的模型设计。

这样一套流程下来,就完全实现了你想要的功能:用户申请重置邮箱→收验证链接→提交新邮箱→账号被禁用→新邮箱收激活链接→激活后账号恢复可用。

备注:内容来源于stack exchange,提问作者Azhar Uddin Sheikh

火山引擎 最新活动