如何在Django 1.7.6中以编程方式触发密码重置电子邮件?

5

我遇到了一个问题,需要将超过200个新用户加载到我的django应用程序中,并立即向他们发送密码重置电子邮件。这只需在后台由我完成一次,并安静地运行。在网上搜索只给我带来了一个或多或少正确的答案:在django中触发密码重置电子邮件而不使用浏览器?。唯一的问题是这篇文章大约四年前写的,当我尝试解决方案时,它并没有起作用...

3个回答

14

我提到的链接中提到的两个最有价值的点:

  1. 在最新版本的Django中,我们需要调用form.is_valid()
  2. 发送电子邮件是在save()时完成的。

以下是我查询所需用户并向每个用户发送密码重置链接的方法:

def find_users_and_send_email():
    from django.http import HttpRequest
    from django.contrib.auth.forms import PasswordResetForm
    from django.contrib.auth.models import User
    import logging
    logger = logging.getLogger(__name__)
    
    users = User.objects.filter(date_joined__gt = '2015-04-16')
    for user in users:
        try:
            if user.email:
                logger.info("Sending email for to this email:", user.email)
                form = PasswordResetForm({'email': user.email})
                
                assert form.is_valid()
                request = HttpRequest()
                request.META['SERVER_NAME'] = 'help.mydomain.com'
                request.META['SERVER_PORT'] = '443'
                form.save(
                    request= request,
                    use_https=True,
                    from_email="username@gmail.com", 
                        email_template_name='registration/password_reset_email.html')

        except Exception as e:
            logger.warning(str(e))
            continue

    return 'done'
  1. 通常情况下,PasswordResetForm需要前端发起“请求”,但我没有。所以我简单地创建了一个。
  2. 当我按照链接中的示例操作时,它失败了... 它在请求中找不到服务器名称。(这是有道理的,因为我突然实例化了我的请求)
  3. 当我修复了服务器名称后,它要求输入服务器端口号。由于我使用了https,所以我的端口号是443,否则使用默认端口号。
  4. 如果您使用https,请不要忘记在保存表单时指定 use_https=True

1
你可以在form.save()中设置domain_override,而不是创建一个请求,以便在重置电子邮件中显示您想要显示为域的内容(这将用于链接和电子邮件的默认主题)。 - Fasand
请注意,如果电子邮件发送给一个不存在/无效的用户,.save()将会静默跳过发送消息并不会引发错误。我的解决方案是预先调用next(form.get_users(users_email_address))来检查返回的生成器是否包含匹配项。顺便说一句,Fasand使用domain_override的想法对我很有帮助!不错。 - Tristan

2

我发现将@chabislav的答案封装在一个自定义管理命令中很有用,这样可以根据需要重复使用。请注意,我删除了用户对象过滤器,因为我知道我想向所有用户发送密码重置说明。

# myproject/myapp/management/commands/send_password_reset_emails.py
from django.core.management.base import BaseCommand, CommandError
# I use a custom user model, so importing that rather than the standard django.contrib.auth.models
from users.models import User
from django.http import HttpRequest
from django.contrib.auth.forms import PasswordResetForm


class Command(BaseCommand):
    help = 'Emails password reset instructions to all users'

    def handle(self, *args, **kwargs):
        users = User.objects.all()
        for user in users:
            try:
                if user.email:
                    print("Sending email for to this email:", user.email)
                    form = PasswordResetForm({'email': user.email})
                    assert form.is_valid()
                    request = HttpRequest()
                    request.META['SERVER_NAME'] = 'www.example.com'
                    request.META['SERVER_PORT'] = 443
                    form.save(
                        request= request,
                        use_https=True,
                        from_email="admin@mysite.com", 
                        email_template_name='registration/password_reset_email.html'
                        )
            except Exception as e:
                print(e)
                continue

        return 'done'

然后,您可以轻松运行以下命令:python manage.py send_password_reset_emails

请注意,我收到了一个错误消息(550, b'5.7.1 Relaying denied'),这最终是由于我的本地主机电子邮件服务器设置方式有问题,但一旦解决了这个问题,它就非常有效。将其放在管理命令中也很好,因为它不会堵塞您定期使用的代码(视图)。


0
如果您正在使用django 3.x或4.x,并且正在使用django-allauth,那么以下内容适用于您:
from allauth.account.forms import ResetPasswordForm # note the different form import

form = ResetPasswordForm({"email": "some@mail.io"})

assert form.is_valid() # required because we need clean() executed
form.save(
    request=None,
    from_email="noreply@example.io",
)

请注意,这将从allauth配置中获取所有值,使用django.contrib.sites

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接