页面模板
新建\myblog\templates\user\register.html
{% extends 'base.html' %}
{% load static %}
{% block title %}密码重置{% endblock %}
{% block main %}
<!--主内容-start--->
<div class="container">
<form class="form-signin bg-white" method="post" action="{% url 'sys:reset_password' %}">
<div class="text-center mb-4">
<img class="mb-2" src="{% static 'images/favicon.png' %}" alt="" width="100" height="100">
<h1 class="h3 mb-3 font-weight-normal">密码重置</h1>
<div class="rollContainer">
<p class="rollBlock">请按照密码格式要求重新设置你的密码。</p>
</div>
<div class="login_error">
{% for k,v in reset_form.errors.items %}
{{ v }}
{% endfor %}
</div>
</div>
<div class="form-label-group">
<input type="password" id="inputPassword" name="password"
value="{% if reset.password.value %}{{ reset_form.password.value }}{% endif %}"
class="form-control {% if reset_form.errors.password %}is-invalid{% endif %}" placeholder="新密码"
aria-describedby="passwordHelpBlock" required autofocus>
<label for="inputPassword">新密码</label>
<small id="passwordHelpBlock" class="form-text text-muted">
密码必须以字母开头,长度在5~18之间,只能包括字母,数字以及.@+-_?&%$这些字符
</small>
</div>
<input type="hidden" name="email"
value="{% if reset_form.email.value %}{{ reset_form.email.value }}{% else %}{{ email }}{% endif %}">
<button class="btn btn-lg btn-success btn-block" type="submit">重置密码</button>
{% csrf_token %}
</form>
<div class="clearfix " style="width: 100%;max-width: 420px;padding: 15px;margin: auto;">
<p class="float-left"><a class="text-muted " href="/">←回到首页</a></p>
<p class="float-right">
<a class="text-muted " href="{% url 'sys:login' %}">登录</a>
|
<a class="text-muted " href="{% url 'sys:register' %}">注册</a>
</p>
</div>
</div>
{% endblock %}
页面效果:
密码重置邮件
编辑\myblog\system\tools.py
内容如下,RegisterActiveEmailSender继承之前封装的EmailSender,重写了get_template_context函数,实现了注册激活需要的参数逻辑。
from django.urls import reverse
from django.utils.crypto import get_random_string
from system.models import EmailRecord, User
from django.core.mail import send_mail
from django.template.loader import render_to_string
from myblog.email_settings import EMAIL_FROM
from myblog.settings import SITE_URL, SITE_NAME
import logging
logger = logging.getLogger(__name__)
...
"""
密码重置邮件发送器
"""
class PasswordResetEmailSender(EmailSender):
template_name = "user/email-password-reset.html"
def get_template_context(self):
context = super(PasswordResetEmailSender, self).get_template_context()
self.record.url = self.site_url + reverse(viewname='sys:reset_password') + f"?code={self.record.code}"
context.update({"url": self.record.url})
return context
密码重置邮件任务
新增\myblog\system\tasks.py
添加以下内容,需要注意这里文件名必须为tasks.py,便于后面celery的自动任务发现机制。可以看到下面发送邮件是区分了两种环境的。
如果是DEBUG=False(生产环境)的情况下则是交给celery异步发送邮件,同时在之前prod_settings.py里面已经配置过 EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
。
而在DEBUG=True(开发环境)则是同步发送且因为在dev_settings.py里面定义了
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
,邮件直接输出在控制台,便于开发调试。
from celery import shared_task
from django.conf import settings
from system.models import EmailType
from system.tools import RegisterActiveEmailSender, PasswordResetEmailSender
import logging
logger = logging.getLogger("django")
@shared_task
def _send_email(email_type=None, username=None, *args, **kwargs):
if email_type == EmailType.ACTIVE:
RegisterActiveEmailSender(EmailType.ACTIVE, username).send(*args, **kwargs)
elif email_type == EmailType.RESET:
PasswordResetEmailSender(EmailType.RESET, username).send(*args, **kwargs)
def send_email(email_type=None, username=None, *args, **kwargs):
if settings.DEBUG:
_send_email(email_type, username, *args, **kwargs)
else:
_send_email.delay(email_type, username, *args, **kwargs)
密码重置表单
编写\myblog\system\forms.py
添加以下内容,需要注意这里面使用到了django-simple-captcha依赖,如下面的
from captcha.fields import CaptchaField
,这个在之前初始化工程的时候已经安装了开发环境下需要的依赖了。
from django import forms
from django.core import validators
from system.models import User
import logging
logger = logging.getLogger('django')
class ResetPasswordForm(forms.Form):
email = forms.EmailField(required=True, max_length=30, error_messages={'require': "请输入邮箱地址"})
password = forms.CharField(required=True, error_messages={'require': "请输入密码"}, min_length=5, max_length=18,
validators=[
validators.RegexValidator(regex=r'[\w.@+-_?&%$)!]{6,18}$',
message="请输入合法的密码")])
def clean_email(self):
email = self.cleaned_data.get('email')
if email is not None:
exists = User.objects.filter(email=email).exists() # 之所以用filter是因为如果用get若不存在则会报错
if not exists:
logger.info("【{email}】邮箱不存在!".format(email=email))
raise forms.ValidationError("邮箱不存在!")
return email
密码重置视图
编写\myblog\system\views.py
添加以下内容
import logging
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.generic.base import View
from system import forms
from system.models import EmailRecord, User
logger = logging.getLogger('django')
class ResetPasswordView(View):
def get(self, request):
code = request.GET.get("code", None)
try:
record = EmailRecord.objects.get(code=code)
return render(request, "user/reset.html", {"email": record.receiver.email})
except EmailRecord.DoesNotExist:
logger.info("【{code}】激活验证码不存在!".format(code=code))
return redirect("/")
def post(self, request):
reset_form = forms.ResetPasswordForm(request.POST)
if reset_form.is_valid():
email = reset_form.cleaned_data['email']
password = reset_form.cleaned_data['password']
user = User.objects.get(email=email)
user.set_password(password)
user.save()
return redirect(reverse("sys:login"))
return render(request, 'user/reset.html', {'reset_form': reset_form})
路由绑定
编辑\myblog\system\urls.py
添加以下内容
from django.urls import path
from system import views
urlpatterns = [
path('user/reset_password', views.ResetPasswordView.as_view(), name='reset_password'),
]