From 17b8048e0a92d034039d997a8ff2d447c776173a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B1=9F=E6=A5=A0?= <13910380238@163.com>
Date: Thu, 9 Sep 2021 11:41:47 +0800
Subject: [PATCH 1/4] =?UTF-8?q?fix(account):=E8=A7=A3=E5=86=B3=E7=99=BB?=
=?UTF-8?q?=E5=BD=95=E9=A1=B5=20checkbox=20=E9=80=89=E4=B8=AD=E6=97=A0?=
=?UTF-8?q?=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
accounts/views.py | 3 +++
blog/static/assets/img/checkmark.png | Bin 0 -> 221 bytes
templates/account/login.html | 2 +-
3 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 blog/static/assets/img/checkmark.png
diff --git a/accounts/views.py b/accounts/views.py
index 8114d67..5f7499a 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -89,6 +89,7 @@ class LoginView(FormView):
template_name = 'account/login.html'
success_url = '/'
redirect_field_name = REDIRECT_FIELD_NAME
+ login_ttl = 2626560 # 一个月的时间
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@@ -115,6 +116,8 @@ class LoginView(FormView):
logger.info(self.redirect_field_name)
auth.login(self.request, form.get_user())
+ if self.request.POST.get("remember"):
+ self.request.session.set_expiry(self.login_ttl)
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
else:
diff --git a/blog/static/assets/img/checkmark.png b/blog/static/assets/img/checkmark.png
new file mode 100644
index 0000000000000000000000000000000000000000..4bd0eb350087ae1c5dedf8dfce9d24272d6db7a8
GIT binary patch
literal 221
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fjKx9jP7LeL$-D$|rhB?LhIn`<
zrz{XmSTb=Z&*38p6En+u{{Q{|+S|v#(9kHqZt~J1u~=QV01lshZx0+fvtho3b7I!N
zzo`X(CakVvh~#fO=oFl9W?*1+b#juy;s5o$?>AkQcH>Qx;N@~rlJFJ%ut7mhgTe~DWM4f;?+@i
literal 0
HcmV?d00001
diff --git a/templates/account/login.html b/templates/account/login.html
index f758b7a..0a10a60 100644
--- a/templates/account/login.html
+++ b/templates/account/login.html
@@ -25,7 +25,7 @@
{% load oauth_tags %}
From 3e88b74d2dbd734049eb3d9f79c3b63b2575d1c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B1=9F=E6=A5=A0?= <13910380238@163.com>
Date: Tue, 14 Sep 2021 16:52:05 +0800
Subject: [PATCH 2/4] =?UTF-8?q?feat(account):=E5=AE=9E=E7=8E=B0=E5=BF=98?=
=?UTF-8?q?=E8=AE=B0=E5=AF=86=E7=A0=81=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
DjangoBlog/blog_signals.py | 17 ++--
accounts/email.py | 51 ++++++++++
accounts/forms.py | 85 ++++++++++++++++-
accounts/tests.py | 116 +++++++++++++++++++++--
accounts/urls.py | 13 ++-
accounts/views.py | 53 +++++++++--
blog/static/account/css/account.css | 9 ++
blog/static/account/js/account.js | 47 +++++++++
templates/account/forget_password.html | 29 ++++++
templates/account/login.html | 2 +
templates/share_layout/base_account.html | 3 +
11 files changed, 397 insertions(+), 28 deletions(-)
create mode 100644 accounts/email.py
create mode 100644 blog/static/account/css/account.css
create mode 100644 blog/static/account/js/account.js
create mode 100644 templates/account/forget_password.html
diff --git a/DjangoBlog/blog_signals.py b/DjangoBlog/blog_signals.py
index 22260c9..d0cbedd 100644
--- a/DjangoBlog/blog_signals.py
+++ b/DjangoBlog/blog_signals.py
@@ -12,24 +12,23 @@
@file: blog_signals.py
@time: 2017/8/12 上午10:18
"""
-import django
+import _thread
+import logging
+
import django.dispatch
from django.dispatch import receiver
from django.conf import settings
from django.contrib.admin.models import LogEntry
-from DjangoBlog.utils import get_current_site
from django.core.mail import EmailMultiAlternatives
from django.db.models.signals import post_save
-from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
+from django.contrib.auth.signals import user_logged_in, user_logged_out
-from DjangoBlog.utils import cache, send_email, expire_view_cache, delete_sidebar_cache, delete_view_cache
-from DjangoBlog.spider_notify import SpiderNotify
from oauth.models import OAuthUser
-from blog.models import Article, Category, Tag, Links, SideBar, BlogSettings
from comments.models import Comment
from comments.utils import send_comment_email
-import _thread
-import logging
+from DjangoBlog.utils import get_current_site
+from DjangoBlog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache
+from DjangoBlog.spider_notify import SpiderNotify
logger = logging.getLogger(__name__)
@@ -61,7 +60,7 @@ def send_email_signal_handler(sender, **kwargs):
result = msg.send()
log.send_result = result > 0
except Exception as e:
- logger.error(e)
+ logger.error(f"失败邮箱号: {emailto}, {e}")
log.send_result = False
log.save()
diff --git a/accounts/email.py b/accounts/email.py
new file mode 100644
index 0000000..6b56241
--- /dev/null
+++ b/accounts/email.py
@@ -0,0 +1,51 @@
+import typing
+import random
+import string
+from datetime import timedelta
+
+from django.core.cache import cache
+from DjangoBlog.utils import send_email
+
+_code_ttl = timedelta(minutes=5)
+
+
+def generate_code() -> str:
+ """生成随机数验证码"""
+ return ''.join(random.sample(string.digits, 6))
+
+
+def send(to_mail: str, code: str, subject: str = "邮件验证码"):
+ """发送重设密码验证码
+ Args:
+ to_mail: 接受邮箱
+ subject: 邮件主题
+ code: 验证码
+ """
+ html_content = f"您正在重设密码,验证码为:{code}, 5分钟内有效,请妥善保管"
+ send_email([to_mail], subject, html_content)
+
+
+def verify(email: str, code: str) -> typing.Optional[str]:
+ """验证code是否有效
+ Args:
+ email: 请求邮箱
+ code: 验证码
+ Return:
+ 如果有错误就返回错误str
+ Node:
+ 这里的错误处理不太合理,应该采用raise抛出
+ 否测调用方也需要对error进行处理
+ """
+ cache_code = get_code(email)
+ if cache_code != code:
+ return "验证码错误"
+
+
+def set_code(email: str, code: str):
+ """设置code"""
+ cache.set(email, code, _code_ttl.seconds)
+
+
+def get_code(email: str) -> typing.Optional[str]:
+ """获取code"""
+ return cache.get(email)
diff --git a/accounts/forms.py b/accounts/forms.py
index 1f60cd4..5808946 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -12,11 +12,14 @@
@file: forms.py
@time: 2016/11/20 下午3:16
"""
-from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
+from django import forms
from django.forms import widgets
-from django.conf import settings
-from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
+from django.contrib.auth import get_user_model, password_validation
+from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
+
+from . import email
+from .models import BlogUser
class LoginForm(AuthenticationForm):
@@ -50,3 +53,79 @@ class RegisterForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ("username", "email")
+
+
+class ForgetPasswordForm(forms.Form):
+ new_password1 = forms.CharField(
+ label="新密码",
+ widget=forms.PasswordInput(
+ attrs={
+ "class": "form-control",
+ 'placeholder': "密码"
+ }
+ ),
+ )
+
+ new_password2 = forms.CharField(
+ label="确认密码",
+ widget=forms.PasswordInput(
+ attrs={
+ "class": "form-control",
+ 'placeholder': "确认密码"
+ }
+ ),
+ )
+
+ email = forms.EmailField(
+ label='邮箱',
+ widget=forms.TextInput(
+ attrs={
+ 'class': 'form-control',
+ 'placeholder': "邮箱"
+ }
+ ),
+ )
+
+ code = forms.CharField(
+ label='验证码',
+ widget=forms.TextInput(
+ attrs={
+ 'class': 'form-control',
+ 'placeholder': "验证码"
+ }
+ ),
+ )
+
+ def clean_new_password2(self):
+ password1 = self.data.get("new_password1")
+ password2 = self.data.get("new_password2")
+ if password1 and password2 and password1 != password2:
+ raise ValidationError("两次密码不一致")
+ password_validation.validate_password(password2)
+
+ return password2
+
+ def clean_email(self):
+ user_email = self.cleaned_data.get("email")
+ if not BlogUser.objects.filter(
+ email=user_email
+ ).exists():
+ # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
+ raise ValidationError("未找到邮箱对应的用户")
+ return user_email
+
+ def clean_code(self):
+ code = self.cleaned_data.get("code")
+ error = email.verify(
+ email=self.cleaned_data.get("email"),
+ code=code,
+ )
+ if error:
+ raise ValidationError(error)
+ return code
+
+
+class ForgetPasswordCodeForm(forms.Form):
+ email = forms.EmailField(
+ label="邮箱号"
+ )
diff --git a/accounts/tests.py b/accounts/tests.py
index f3613ef..5fe2ef1 100644
--- a/accounts/tests.py
+++ b/accounts/tests.py
@@ -1,12 +1,12 @@
-from django.test import Client, RequestFactory, TestCase
-from blog.models import Article, Category, Tag
-from django.contrib.auth import get_user_model
-from DjangoBlog.utils import delete_view_cache, delete_sidebar_cache
-from accounts.models import BlogUser
from django.urls import reverse
-from DjangoBlog.utils import *
from django.conf import settings
from django.utils import timezone
+from django.test import Client, RequestFactory, TestCase
+
+from accounts.models import BlogUser
+from DjangoBlog.utils import *
+from blog.models import Article, Category
+from . import email
# Create your tests here.
@@ -15,6 +15,12 @@ class AccountTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
+ self.blog_user = BlogUser.objects.create_user(
+ username="test",
+ email="admin@admin.com",
+ password="12345678"
+ )
+ self.new_test = "xxx123--="
def test_validate_account(self):
site = get_current_site().domain
@@ -111,3 +117,101 @@ class AccountTest(TestCase):
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
+
+ def test_verify_email_code(self):
+ to_email = "admin@admin.com"
+ code = email.generate_code()
+ email.set_code(to_email, code)
+ email.send(to_email, code)
+
+ err = email.verify("admin@admin.com", code)
+ self.assertEqual(err, None)
+
+ err = email.verify("admin@123.com", code)
+ self.assertEqual(type(err), str)
+
+ def test_forget_password_email_code_success(self):
+ resp = self.client.post(
+ path=reverse("account:forget_password_code"),
+ data=dict(email="admin@admin.com")
+ )
+
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.content.decode("utf-8"), "ok")
+
+ def test_forget_password_email_code_fail(self):
+ resp = self.client.post(
+ path=reverse("account:forget_password_code"),
+ data=dict()
+ )
+ self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
+
+ resp = self.client.post(
+ path=reverse("account:forget_password_code"),
+ data=dict(email="admin@com")
+ )
+ self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
+
+ def test_forget_password_email_success(self):
+ code = email.generate_code()
+ email.set_code(self.blog_user.email, code)
+ data = dict(
+ new_password1=self.new_test,
+ new_password2=self.new_test,
+ email=self.blog_user.email,
+ code=code,
+ )
+ resp = self.client.post(
+ path=reverse("account:forget_password"),
+ data=data
+ )
+ self.assertEqual(resp.status_code, 302)
+
+ # 验证用户密码是否修改成功
+ blog_user = BlogUser.objects.filter(
+ email=self.blog_user.email,
+ ).first() # type: BlogUser
+ self.assertNotEqual(blog_user, None)
+ self.assertEqual(blog_user.check_password(data["new_password1"]), True)
+
+ def test_forget_password_email_not_user(self):
+ data = dict(
+ new_password1=self.new_test,
+ new_password2=self.new_test,
+ email="123@123.com",
+ code="123456",
+ )
+ resp = self.client.post(
+ path=reverse("account:forget_password"),
+ data=data
+ )
+
+ self.assertEqual(resp.status_code, 200)
+ self.assertFormError(
+ response=resp,
+ form="form",
+ field="email",
+ errors="未找到邮箱对应的用户"
+ )
+
+ def test_forget_password_email_code_error(self):
+ code = email.generate_code()
+ email.set_code(self.blog_user.email, code)
+ data = dict(
+ new_password1=self.new_test,
+ new_password2=self.new_test,
+ email=self.blog_user.email,
+ code="111111",
+ )
+ resp = self.client.post(
+ path=reverse("account:forget_password"),
+ data=data
+ )
+
+ self.assertEqual(resp.status_code, 200)
+ self.assertFormError(
+ response=resp,
+ form="form",
+ field="code",
+ errors="验证码错误"
+ )
diff --git a/accounts/urls.py b/accounts/urls.py
index a10ff00..e84123b 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -14,10 +14,10 @@
"""
from django.conf.urls import url
-from django.contrib.auth import views as auth_view
from django.urls import path
-from . import views
+
from .forms import LoginForm
+from . import views
app_name = "accounts"
@@ -33,4 +33,11 @@ urlpatterns = [url(r'^login/$',
name='logout'),
path(r'account/result.html',
views.account_result,
- name='result')]
+ name='result'),
+ url(r'^forget_password/$',
+ views.ForgetPasswordView.as_view(),
+ name='forget_password'),
+ url(r'^forget_password_code/$',
+ views.ForgetPasswordEmailCode.as_view(),
+ name='forget_password_code'),
+ ]
diff --git a/accounts/views.py b/accounts/views.py
index 5f7499a..1314449 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,24 +1,31 @@
-from django.shortcuts import render
import logging
-from .forms import RegisterForm, LoginForm
-from django.contrib.auth import authenticate, login, logout
-# from django.views.generic.edit import FormView
+import threading
+
+from django.http.request import HttpRequest
+from django.http.response import HttpResponse
+from django.shortcuts import render
+from django.contrib.auth import logout
from django.views.generic import FormView, RedirectView
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.urls import reverse
-from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
+from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.views.decorators.csrf import csrf_protect
from django.contrib import auth
from django.views.decorators.cache import never_cache
-from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.http import is_safe_url
-from DjangoBlog.utils import send_email, get_sha256, get_current_site
from django.conf import settings
+from django.views import View
+from django.contrib.auth.hashers import make_password
+
+from DjangoBlog.utils import send_email, get_sha256, get_current_site
+from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
+from . import email
+from .models import BlogUser
logger = logging.getLogger(__name__)
@@ -166,3 +173,35 @@ def account_result(request):
})
else:
return HttpResponseRedirect('/')
+
+
+class ForgetPasswordView(FormView):
+ form_class = ForgetPasswordForm
+ template_name = 'account/forget_password.html'
+
+ def form_valid(self, form):
+ if form.is_valid():
+ blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
+ blog_user.password = make_password(form.cleaned_data["new_password2"])
+ blog_user.save()
+ return HttpResponseRedirect('/login/')
+ else:
+ return self.render_to_response({'form': form})
+
+
+class ForgetPasswordEmailCode(View):
+
+ def post(self, request: HttpRequest):
+ form = ForgetPasswordCodeForm(request.POST)
+ if not form.is_valid():
+ return HttpResponse("错误的邮箱")
+ to_email = form.cleaned_data["email"]
+
+ code = email.generate_code()
+ email.set_code(to_email, code)
+
+ # 异步执行
+ t = threading.Thread(target=email.send, args=(to_email, code))
+ t.start()
+
+ return HttpResponse("ok")
diff --git a/blog/static/account/css/account.css b/blog/static/account/css/account.css
new file mode 100644
index 0000000..7d4cec7
--- /dev/null
+++ b/blog/static/account/css/account.css
@@ -0,0 +1,9 @@
+.button {
+ border: none;
+ padding: 4px 80px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 4px 2px;
+}
\ No newline at end of file
diff --git a/blog/static/account/js/account.js b/blog/static/account/js/account.js
new file mode 100644
index 0000000..f1a8771
--- /dev/null
+++ b/blog/static/account/js/account.js
@@ -0,0 +1,47 @@
+let wait = 60;
+
+function time(o) {
+ if (wait == 0) {
+ o.removeAttribute("disabled");
+ o.value = "获取验证码";
+ wait = 60
+ return false
+ } else {
+ o.setAttribute("disabled", true);
+ o.value = "重新发送(" + wait + ")";
+ wait--;
+ setTimeout(function () {
+ time(o)
+ },
+ 1000)
+ }
+}
+
+document.getElementById("btn").onclick = function () {
+ let id_email = $("#id_email")
+ let token = $("*[name='csrfmiddlewaretoken']").val()
+ let ts = this
+ let myErr = $("#myErr")
+ $.ajax(
+ {
+ url: "/forget_password_code/",
+ type: "POST",
+ data: {
+ "email": id_email.val(),
+ "csrfmiddlewaretoken": token
+ },
+ success: function (result) {
+ if (result != "ok") {
+ myErr.remove()
+ id_email.after("")
+ return
+ }
+ myErr.remove()
+ time(ts)
+ },
+ error: function (e) {
+ alert("发送失败,请重试")
+ }
+ }
+ );
+}
diff --git a/templates/account/forget_password.html b/templates/account/forget_password.html
new file mode 100644
index 0000000..1016c14
--- /dev/null
+++ b/templates/account/forget_password.html
@@ -0,0 +1,29 @@
+{% extends 'share_layout/base_account.html' %}
+{% load static %}
+{% block content %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/account/login.html b/templates/account/login.html
index 0a10a60..1773896 100644
--- a/templates/account/login.html
+++ b/templates/account/login.html
@@ -37,6 +37,8 @@
Create Account
|
Home Page
+ |
+ 忘记密码
diff --git a/templates/share_layout/base_account.html b/templates/share_layout/base_account.html
index d7160b5..c00d842 100644
--- a/templates/share_layout/base_account.html
+++ b/templates/share_layout/base_account.html
@@ -11,6 +11,7 @@
{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}
+
{% load compress %}
{% compress css %}
@@ -41,4 +42,6 @@