You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django2+后台更新邮箱时如何向email_candidate发送验证邮件

项目背景
  • 项目基于Django 2+开发,为医患管理系统,当前已正常运行的业务逻辑如下:
    1. 医生为患者注册账号后,患者将收到附带验证链接的确认邮件,点击链接完成验证后才可正常使用平台
    2. 患者在个人中心设置页修改邮箱时,系统不会直接更新User模型的email字段,仅先更新email_candidate(候选邮箱)字段,验证邮件将发送至该候选邮箱地址;待患者点击邮件内验证链接后,系统才会将email字段正式替换为候选邮箱值,该设计可避免用户输错新邮箱导致账号无法登录,此部分患者端逻辑运行正常。
待解决问题

在Patient Support Admin(患者支持管理后台)中,客服人员可协助患者更新邮箱,但后台配置的resend_email_verification(重发邮箱验证)动作仅会向用户当前已绑定的正式email地址发送验证邮件,不会选取email_candidate字段存储的候选邮箱地址发送,需要复用患者端已实现的update_candidate_email序列化方法,实现后台向候选邮箱发送验证邮件的功能。

相关代码

models.py - User模型

class User(AbstractUser):
    objects = UserManager()
    
    is_physician = models.BooleanField(default=False)
    is_patient = models.BooleanField(default=False)
    
    title = models.CharField(choices=TITLE_CHOICES,
                             max_length=10,
                             blank=True,
                             null=True)
    first_name = models.CharField(max_length=254, blank=True, null=True, )
    last_name = models.CharField(max_length=254, blank=True, null=True, )
    birthdate = models.DateField(null=True, blank=True)
    home_phone = PhoneNumberField(blank=True, null=True)
    mobile_phone = PhoneNumberField(blank=True, null=True)
    gender = models.CharField(max_length=1,
                              choices=GENDER_CHOICES,
                              blank=True,
                              null=True)
    
    language = models.CharField(max_length=4,
                                choices=settings.LANGUAGES,
                                default='en',
                                blank=True,
                                null=True)
    
    email = models.EmailField(unique=True, db_index=True,
                              error_messages={
                                  'unique': "Email already exists."
                              }
                              )
    email_candidate = models.EmailField(
        max_length=254,
        blank=True,
        null=True,
        default=None)
    
    username = models.CharField(
        max_length=30,
        unique=True,
        blank=True,
        null=True,
        validators=[
            validators.RegexValidator(
                r'^\w+$',
                'Enter a valid username. This value may contain only '
                'letters, numbers and _ character.',
                'invalid'
            ),
        ],
        error_messages={
            'unique': "Username already exists."
        }
    )
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    ordering = ('created',)
    class Meta(object):
        verbose_name = 'User'
        verbose_name_plural = 'Users'
        # abstract = False
        permissions = u_perm.user_permissions
    @property
    def name(self):
        return '{} {}'.format(self.last_name, self.first_name, )

models.py - Patient模型(与User为OneToOne关联)

class Patient(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    physician = models.ForeignKey(Physician,
                                  related_name='patients',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL)
    address = models.TextField(blank=True, null=True)
    city = models.CharField(max_length=85, null=True, blank=True)
    country = CountryField(null=True, blank=True)
    postal_code = models.CharField(max_length=32, null=True, blank=True)
    reminder_hour_default = models.TimeField(
        'Send Reminder Time',
        default=settings.REMINDER_HOUR_DEFAULT)
    is_eligible = models.BooleanField(default=True)
    def __str__(self):
        return self.user.email
   @receiver(post_save, sender=Patient)
   @receiver(post_save, sender=User)
   def update_status(sender, **kwargs):
    def update_patient(instance):
        # from pending/disabled to active
        current_status = instance.user.status
            instance.save()
            instance.user.save()
            return
    def update_user(instance):
        pass

    instance = kwargs.get('instance')
    method_name = get_inner_method(sender)
    if instance and method_name:
        method_name(instance)

models.py - PatientSupport代理模型

class PatientSupport(Patient):
    class Meta:
        proxy = True
        # Add verbose name
        verbose_name = 'Patient Support'
        verbose_name_plural = 'Patients Support'

serializers.py - UserSerializer(包含患者端已正常运行的update_candidate_email方法)

class UserSerializer(serializers.ModelSerializer):
    permission_list = serializers.SerializerMethodField()
    permissions = serializers.SerializerMethodField()
    user_messages = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = (
            'pk',
            'title',
            'first_name',
            'last_name',
            'birthdate',
            'email',
            'email_candidate',
            'home_phone',
            'mobile_phone',
            'gender',
            'language',
            'username',
            'password',
            'is_patient',
        )
        extra_kwargs = {
            'email': {'required': False,
                      'validators': [],
                      #'read_only': True,
                      },
            'email_candidate': {'required': False,
                                'validators': [],
                                #'read_only': True,
                                },
            'home_phone': {'required': False,
                           'validators': [], },
            'mobile_phone': {'required': False,
                             'validators': [], },
            'is_patient': {'required': False},
            'username': {'read_only': True, },
        }

    

    def update_candidate_email(self, instance, validated_data, email_candidate):
        # if email candidate is None (and changed) -> search for tokens and delete token sent
        if instance.email_candidate and email_candidate == None:
            instance.email_candidate = email_candidate
            instance.save()

            ResetPasswordToken.objects.filter(user=instance).delete()
            
        # if email candidate is same to current email -> error - email in use !
        if instance.email_candidate == instance.email:
            raise serializers.ValidationError(
                    {'email':
                     "email candidate cannot be same to current mail"}
                )

        # if email candidate is not None and already not in use-> save it (email candidate) and send new email replace_email_mail
        if email_candidate and email_candidate != instance.email_candidate:

            if User.objects.filter(email=email_candidate):
                raise serializers.ValidationError(
                    {'email': "email in use"}
                )
            ResetPasswordToken.objects.filter(user=instance).delete()

            instance.email_candidate = email_candidate
            instance.save()

            site_domain = Site.objects.filter(name__icontains='admin').first()
            # create pass token send mail
            token = ResetPasswordToken.objects.create(
                            user=instance,)

            tiny_link = shortener.create(instance,
                                         'https://{}/api/users/verifyMail/?lang={}&token={}&region={}&patient={}'.format(
                                             site_domain.domain,
                                             instance.language if instance.language else "en",
                                             token.key,
                                             settings.REGION_PREFIX,
                                             instance.is_patient
                                             )
                                         )
            from messaging.notify import Notify
            activate(instance.language)

            msg_name = 'EMAIL_VERIFICATION'
            if not instance.is_patient:
                msg_name = 'EMAIL_VERIFICATION_STAFF'

            notify = Notify()
            notify.send_notification(
                msg_name=msg_name,
                users_pk=[instance.pk, ],
                context={
                    'tiny_link': 'https://{}/s/{}/?lang={}&region={}'.format(
                        site_domain.domain,
                        tiny_link,
                        instance.language if instance.language else "en",
                        settings.REGION_PREFIX),
                    'email_candidate': email_candidate
                })
        return

admin.py - PatientSupportAdmin(需实现向候选邮箱发验证邮件的功能)

class PatientSupportAdmin(admin.ModelAdmin):
    list_display = ['pk', 'patient', 'patient_email', 'email_candidate' ]

    list_display_links = ('pk', 'patient')

   
    def get_queryset(self, request):
        qs = super(PatientSupportAdmin, self).get_queryset(request)
        return qs.filter(support_consent=True)

    fieldsets = (
        ('User', {
            'fields': (
                'patient',
                'get_gender',
                'birthdate',
                'home_phone', 'mobile_phone', 'language',
                'email', 'username',
                'date_modified',
                'date_joined',
                'status',
                'user',
            )}),

        ('Patient', {'fields': ('physician',
                                'country',
                                'id_number',
                                )}),

    def patient(self, obj):
        return obj.user.get_full_name()

    def patient_email(self, obj):
        return obj.user.email

    def email_candidate(self, obj):
        return obj.user.email_candidate

    def birthdate(self, obj):
        return obj.user.birthdate or ''

    def home_phone(self, obj):
        return obj.user.home_phone

    def mobile_phone(self, obj):
        return obj.user.mobile_phone

    def email(self, obj):
        return obj.user.email

    def username(self, obj):
        return obj.user.username or ''

    actions = [resend_email_verification,
               ]

admin.py - 现有resend_email_verification方法(仅向正式绑定邮箱发验证邮件)

def resend_email_verifcation(modeladmin, request, queryset):
    for user in queryset:
        if 'users/patient' in request.path:
           
            user = user.user
          
        if user.is_patient and not user.patient.is_eligible:
            continue
        site_domain = Site.objects.filter(name__icontains='admin').first()
        token = ResetPasswordToken.objects.create(
            user=user, )
        reset_password_token_created.send(
            sender=modeladmin.__class__, reset_password_token=token)

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 05:03:19