From b33966701b2fce7a71b072885fa7b37e01a5a583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=85=A7?= <1096877868@qq.com> Date: Sat, 8 Nov 2025 19:42:55 +0800 Subject: [PATCH] =?UTF-8?q?zh=5FcommentsAPP=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.idea/.gitignore | 5 + .../inspectionProfiles/profiles_settings.xml | 6 + src/.idea/misc.xml | 7 + src/.idea/modules.xml | 8 + src/.idea/src.iml | 12 + src/.idea/vcs.xml | 6 + src/accounts/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 149 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 3170 bytes src/accounts/__pycache__/apps.cpython-312.pyc | Bin 0 -> 398 bytes .../__pycache__/forms.cpython-312.pyc | Bin 0 -> 5822 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 2218 bytes src/accounts/__pycache__/urls.cpython-312.pyc | Bin 0 -> 1290 bytes .../user_login_backend.cpython-312.pyc | Bin 0 -> 1509 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 2193 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 10718 bytes src/accounts/admin.py | 66 +++++ src/accounts/apps.py | 18 ++ src/accounts/forms.py | 141 ++++++++++ src/accounts/migrations/0001_initial.py | 50 ++++ ...s_remove_bloguser_created_time_and_more.py | 46 ++++ src/accounts/migrations/__init__.py | 2 + .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 4182 bytes ...user_created_time_and_more.cpython-312.pyc | Bin 0 -> 1898 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 160 bytes src/accounts/models.py | 50 ++++ src/accounts/templatetags/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 162 bytes src/accounts/tests.py | 245 +++++++++++++++++ src/accounts/urls.py | 42 +++ src/accounts/user_login_backend.py | 59 +++++ src/accounts/utils.py | 76 ++++++ src/accounts/views.py | 249 ++++++++++++++++++ 33 files changed, 1092 insertions(+) create mode 100644 src/.idea/.gitignore create mode 100644 src/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 src/.idea/misc.xml create mode 100644 src/.idea/modules.xml create mode 100644 src/.idea/src.iml create mode 100644 src/.idea/vcs.xml create mode 100644 src/accounts/__init__.py create mode 100644 src/accounts/__pycache__/__init__.cpython-312.pyc create mode 100644 src/accounts/__pycache__/admin.cpython-312.pyc create mode 100644 src/accounts/__pycache__/apps.cpython-312.pyc create mode 100644 src/accounts/__pycache__/forms.cpython-312.pyc create mode 100644 src/accounts/__pycache__/models.cpython-312.pyc create mode 100644 src/accounts/__pycache__/urls.cpython-312.pyc create mode 100644 src/accounts/__pycache__/user_login_backend.cpython-312.pyc create mode 100644 src/accounts/__pycache__/utils.cpython-312.pyc create mode 100644 src/accounts/__pycache__/views.cpython-312.pyc create mode 100644 src/accounts/admin.py create mode 100644 src/accounts/apps.py create mode 100644 src/accounts/forms.py create mode 100644 src/accounts/migrations/0001_initial.py create mode 100644 src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py create mode 100644 src/accounts/migrations/__init__.py create mode 100644 src/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-312.pyc create mode 100644 src/accounts/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 src/accounts/models.py create mode 100644 src/accounts/templatetags/__init__.py create mode 100644 src/accounts/templatetags/__pycache__/__init__.cpython-312.pyc create mode 100644 src/accounts/tests.py create mode 100644 src/accounts/urls.py create mode 100644 src/accounts/user_login_backend.py create mode 100644 src/accounts/utils.py create mode 100644 src/accounts/views.py diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/src/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/src/.idea/inspectionProfiles/profiles_settings.xml b/src/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/src/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml new file mode 100644 index 0000000..3ffd832 --- /dev/null +++ b/src/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 0000000..f669a0e --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml new file mode 100644 index 0000000..fdbd330 --- /dev/null +++ b/src/.idea/src.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/accounts/__init__.py b/src/accounts/__init__.py new file mode 100644 index 0000000..50a55dc --- /dev/null +++ b/src/accounts/__init__.py @@ -0,0 +1,2 @@ +#zh: +#coding:utf-8 \ No newline at end of file diff --git a/src/accounts/__pycache__/__init__.cpython-312.pyc b/src/accounts/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5230c99c97559306b395e3cab8ffa2e3f44e8b6 GIT binary patch literal 149 zcmX@j%ge<81m`AQ%K*`jK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^%f!VhCM7E|FFik| zxF|Ws1gjEsy$%s>_Z|5hZ( literal 0 HcmV?d00001 diff --git a/src/accounts/__pycache__/admin.cpython-312.pyc b/src/accounts/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b3851b80daef4469b8de8e20202b94f0ea25699 GIT binary patch literal 3170 zcmb7GU2GiH6~1@=cJ?ppm~FgvNZ67%$s}M(2q~bD0C7Sr8iUkArNPx`*qJ!v%+9)Z zW*gR8B!eummT38rAmOQL;UOqI@W>;NsFf;j9o5SfmXO*P-jdoVC{I1-&d&OWRRtr> znRCxQ_uM(>eCOQz_k2D>;Q7aU|GFrp2>CY-l1J?_Ykvji3UP=-eNv?+rN~RZRFzFx z@UpK|RZ|tb0=#Bwf>-@iRX5YXYfj3~RI_H55{W!Z9DRW}X_wBFlFc^0H{_T%^FlKN znps!vNp}co4y1Wk?nw)c9Y*0>Txd|PUZ}BZ2u%9L&}C0MRWE?S4ji1mXb02oS(pb& z9wmWYbjT?M9LNYGnELT;p3BThpKMYtyJ%iT^96z2N)}#&1G+Tw+p(n36;>K#FR4w7^qE!tTHt&Sls!+ zsvUwCQ+B0Ns|QgyWg~3HW*c0=e_#W!x&Uv8X$&9vn zub7W8UgB|jxJsN|z2lvwZ0E^5FPVZoE?{E$JnZ_)524VH-jecty@X0Ooyqg+wS7&!XHUe}pVJ z6l%PS6UdY_)5}bvNf0Z2vgF7Zi)1Y&kmVsqX-XFEDju-b2TwP;v#S(iaR>_50X7rId|>})Ss;#yd%vx;jd?0y)sQIsil=#D&X zSrs3egk^>JOOU;Jkv?_^Msxkk8+X|a)8ROf@8`*X3dP0Ci_L3~wwlWYqos88+=@Qj z)`zd|Ul}>l9yxOTx%S9oH}%KA%I@rpjJC$#dajcjcza@T0+_-*t*l6jL4RZ9*&vu? z2m*#(Ck9^{`acuhp8XtzJpV$st4##6^*9^?f1;k*K3s=?&+}d1Dz5Ka)+dxb0MZci z_*Cp;7FChp8@>eOdu$T-y`x?!yj%E-eLeG0_46|;C!TMgc>d zOLgVk%k6V7-yE2lpSYt)>blknc;0BhAK z%eoYfW#p}%`Mqa z0&D1^F1)` zc;T#XH~8S@k>z{ArI71zp>dfPOmBzOlejMu3};vd0+7n?nTOuLZ~ojZDODVt|JAJl zGV;KZva3@Vdi`7+Ib1Sn3~(*JWW-%(I3A2uYC*)jSH|pmba6~HU541;edp1Gkm2zV zmq4j&q#k*GI2JKG2z_yiGPt*sxSeU@&J)HLp!9jX0O93L#6_fExU|jJyZVXv9(9tP z2TPdh@M$2oWJ>8ODXo%+R>`qd^2jPV{+&kXo^@@A7Oy_~=Tje^S|`A+A4}20SNEbF5XNVcoW{#VE!5sdNa0~EBAf>7t~UmjB^#rXf9_t2D}{RivAJguA4RYf z30PU#3DM>%ceBY9Gry17Z+7O@b%!A1?dkpIO4ctW`WST397tFK1BNMN7$AZH7r=}K zFf+y%Q@;z0x#bPMmGH3Mn3Uytkzd~?k#6gZooKctEFp#f83YEyz?>VNS#M6FsHpOp z(B9gnot}DDZ4;8yEGDEL5|S0HN)?X?xvyy2bu3k%p;7;=j{M`0-O)TLLcycZZ}BWG zlEaLOSAl7ACz=yN@;PX-6naVXCJ$0;XT-~pYj-vLVD`{n) f=p|42mXzvru_M!uiHM)D|GS2`_jvgSvb3&$k!D|| literal 0 HcmV?d00001 diff --git a/src/accounts/__pycache__/forms.cpython-312.pyc b/src/accounts/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7ce54cbba4b8fdafeca686650e6d7b0dde4e95e GIT binary patch literal 5822 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 WYcFsvR=Du;2dRsxuNfFp75@in6|6b{ literal 0 HcmV?d00001 diff --git a/src/accounts/__pycache__/models.cpython-312.pyc b/src/accounts/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b3441f43ae0c89ffa351f49ba611825c90dcc0b GIT binary patch literal 2218 zcmaJ?O>7%g5Pth-uN|+~ZQLd)b>oV^xM>|i`4Oc83RNhehC>U7RixG0@5OPt_J+6X zwssRKhaB7!6&%{0DB?nt960vaV4zpJP<~E^(3O1<-h9Dp>+G0gAq=1)fxuO_Kz{_@| z5;dX$uh?oOX2b(NVh>ajMnWJW`Iyk?3ZW_!)}-(ohE|W|;*U_s34Y>S!S%RR^uBNz z2RTx4D6?JQqMTi2+-1N@Rp%PomYCz-*_jXKCFkj-L^b5`|Cx9qWE_=S-6Qi=$e z5+jB{i6M%FN%LyIVaUV1LWMQ3#Y06Zt;@ZTBY{^k)b-QAOX+$sbv?2shI%9Rzdq6D z`osgD|B6oS`aBce0FA8>BSGUpH97z^833OE;I&>ndOexbef2}zDIwO4ShZZdlC4@5 z_J~5rmvf>YuUOYjn^jBRoUasYt9qrO6*;rKvQy1^%mzGJpe%f%V^e1ITUS^>kFA;v$*vfEL(xNLg5yy1`!}IeEHqumG{2 zv1;C&!%fOOt8DuN9TD?@=pt9HS-j-tM2=td)f!}qs#U?4U1ra^_~Go+*Uz7sq4QR? zDiwNMdT@F%e@EdtgKMA%y`3NF4#@HUVtV{) z$qSDqqMzI&g8r${l>X2UHsc|z@P^*OAn@#+0P^ojqC!b9Bp7{}iro=66s8yvDjCtb zoKv1^y%;Y--#4UFW$Me=+`d?*(>c{wO%wavH2t_~Leti4h|JA(4Ujv5)2~K2>Hm<%o{2 z;(EfseeFDia2d8wsEweux0Xy?OMbA6Fm2dH;Y}o5LP0lh93_4n2}Z#4NU-OFTsZzK z24nyjcLfNfqK`JOw)D|Wee`?b-uMs6`==k!ZT++53+?pSt*KUeY%@K!df>@Pjr<2&C(k`Ry*>2#hMaC|ndX-*Ewia*?ppWstz)m< zzw}Vv)-Enj?aGoeu^ShZBfAk<8T>mTE16vaBn%K^o6aTuBjL8n7oDod%Y{5{y?nTv zIChmE>ln~N2O$RN>vNpjupp_>rT|+q zU&D6<;}@d0AJM1k*{}oO<7dGgH=2vB0=rKTcF5@+a&(7`?vR&v$Xh$))RXwIkZCUd JMc@%+{SS4D9)bV> literal 0 HcmV?d00001 diff --git a/src/accounts/__pycache__/urls.cpython-312.pyc b/src/accounts/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc14eae1cb401f941d8ab69a868b8e9e7fa719a2 GIT binary patch literal 1290 zcmb7C-%ry}6mIvUD-76Rg$t&U5C||Mo0BC(L(m}dM2zvlhh<@D+PW3nb?NOSOe6Xq zpfCIlW<2=FU*L<0S+m47O-MAx2NEA3@Whkn-fjVg(Rd$vzjN+)zI(pY`?Z~$;17XTboLVC~$Z1N@s8;EzL7Y`9i;GkJ$1N^ETvb8s+`8|_0^#>vZz`=s0n_2@%vA;fxTEg z=vPpkIOvRARPgbibn0!IGI&XYRfa%g^s0uExWo%60`RU~<^A=p;Vz94&=CJq1D$IW zxm;{(G&y!iB$BKbsz`PT_K2kx2s|WHhdBH~Q#Ubyo;9W40BJ>(X%YQq`lqgtZfLfTSsRku?m+t5AE4{Af}a7j_M!3f5i zrlmk(tSePR-K2tSDk?CAE+)-CO2)mYvLTg=W(v$FN$=A}xd zKn?m+dkQo3A`&i;VS0T}+Foh8ca3_Zc%bd7;2ZIVR;iE2Z5xz>F6SN^lBPd^&b=t~ z1%D1^SjHci6X=6EyR~vR?{}TYmYiRq29{xlbB08wo$vg~JTaB3Y@P9ohN)C_b;+6p z0rCj&FZdhlJ0qIB2GY z(hf>DQN|Ug2w|EKgobd#6~UuYaf7@wKly+6q%TUnAjsnttU$}LGRAShKrYD!w4JRgb=Jr0)5?wZ`??lzs- zGfBBZEm6`3HApr3mIo0f66%wJl+rgrkc5aimlgz@#=b>Qq2$T$+uM_)p8eSU&2M(T z`DW((ezQOH_4N?2A3ywgHX{-88+O{JgrxaAkTqfxo0dt1<|#$a%B&*gB}y21mDtiU zv1LcvfEZ|bc}dYD50UEB@1Jozqb3|~R9xFB! z+xs`Zfak%=H+!q^Jy`kp;kC{E&9C=A{ci8pM}cF`lH2=$7SG=cZ}WQqI6tkkw+L5k zR%b7OrHwE; zXLaQQSt7&T){Fgj<_W@~LKk7i0$G$Uk_DWM-i8yF~7 zjp>$Lvhz!R!mN3-PSq<~rsrJFJV6E7PEXvP5CB(6qql!GbNx(X@YpAdTLYQv=YJhc zuV=OfMpn)rFeRR9Xk#}fcC_JLZDd;;+119j@p!*KGM;pnb ztGx#jIdbaJef8Lm`rNKMx~-1>NoYLviV`hyo*seMsf-2m(KE7}&#h3;~}ay({TxoVM2>%n@e*u95>L#Zz~+p~rH6^YoTBwxf-2 zCB`4!{r@EQLyT*~C&}l@Ng~~*06NR%K&UapK%Es=rZ|(F(uZF^(d)kN>)-Fe_Vl2PFH-brSh!hRy|&v z4%FDXXU}vQ$%Yaa9Hr1&4R_FriHSXojkh*G!H1xWzXg#50F6<4k7VzW7ygP2(A4_q J-vpXq;y*Q7Y)t?F literal 0 HcmV?d00001 diff --git a/src/accounts/__pycache__/utils.cpython-312.pyc b/src/accounts/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c99048166fa321ad5380723bafe6398f7a80f8b GIT binary patch literal 2193 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 typing.Optional[str]: + """ + 验证邮箱验证码是否正确 + + Args: + email: 邮箱地址 + code: 用户输入的验证码 + + Returns: + str: 如果验证失败返回错误信息,验证成功返回None + + Note: + 这里的错误处理不太合理,应该采用raise抛出异常 + 否则调用方也需要对error进行处理 + """ + cache_code = get_code(email) # 从缓存中获取该邮箱对应的验证码 + if cache_code != code: # 比较缓存中的验证码和用户输入的验证码 + return gettext("Verification code error") # 验证码错误,返回错误信息 + # 验证成功返回None + + +def set_code(email: str, code: str): + """ + 将验证码存储到缓存中 + + Args: + email: 邮箱地址(作为缓存的key) + code: 验证码(作为缓存的value) + """ + # 使用邮箱作为key,验证码作为value,设置过期时间为5分钟 + cache.set(email, code, _code_ttl.seconds) + + +def get_code(email: str) -> typing.Optional[str]: + """ + 从缓存中获取验证码 + + Args: + email: 邮箱地址(缓存的key) + + Returns: + str: 如果存在返回验证码,不存在返回None + """ + return cache.get(email) # 从缓存中获取指定邮箱的验证码 \ No newline at end of file diff --git a/src/accounts/views.py b/src/accounts/views.py new file mode 100644 index 0000000..bad2752 --- /dev/null +++ b/src/accounts/views.py @@ -0,0 +1,249 @@ +#zh: +#coding:utf-8 +import logging +from django.utils.translation import gettext_lazy as _ +from django.conf import settings +from django.contrib import auth +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth import get_user_model +from django.contrib.auth import logout +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.hashers import make_password +from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.shortcuts import get_object_or_404 +from django.shortcuts import render +from django.urls import reverse +from django.utils.decorators import method_decorator +from django.utils.http import url_has_allowed_host_and_scheme +from django.views import View +from django.views.decorators.cache import never_cache +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.debug import sensitive_post_parameters +from django.views.generic import FormView, RedirectView + +from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache +from . import utils +from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm +from .models import BlogUser + +# 获取当前模块的日志记录器 +logger = logging.getLogger(__name__) + + +# 在此处创建视图 + +class RegisterView(FormView): + """用户注册视图""" + form_class = RegisterForm # 使用自定义注册表单 + template_name = 'account/registration_form.html' # 注册模板路径 + + @method_decorator(csrf_protect) # CSRF保护装饰器 + def dispatch(self, *args, **kwargs): + """请求分发方法""" + return super(RegisterView, self).dispatch(*args, **kwargs) + + def form_valid(self, form): + """表单验证通过后的处理""" + if form.is_valid(): + # 保存用户但不提交到数据库 + user = form.save(False) + user.is_active = False # 设置用户为未激活状态 + user.source = 'Register' # 记录用户来源 + user.save(True) # 保存用户到数据库 + + # 获取当前站点信息 + site = get_current_site().domain + # 生成邮箱验证签名 + sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + + # 调试模式下使用本地地址 + if settings.DEBUG: + site = '127.0.0.1:8000' + + # 构建验证URL + path = reverse('account:result') + url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( + site=site, path=path, id=user.id, sign=sign) + + # 构建邮件内容 + content = """ +

请点击下面链接验证您的邮箱

+ + {url} + + 再次感谢您! +
+ 如果上面链接无法打开,请将此链接复制至浏览器。 + {url} + """.format(url=url) + # 发送验证邮件 + send_email( + emailto=[user.email], + title='验证您的电子邮箱', + content=content) + + # 重定向到结果页面 + url = reverse('accounts:result') + '?type=register&id=' + str(user.id) + return HttpResponseRedirect(url) + else: + # 表单无效,重新渲染表单页 + return self.render_to_response({'form': form}) + + +class LogoutView(RedirectView): + """用户登出视图""" + url = '/login/' # 登出后重定向的URL + + @method_decorator(never_cache) # 禁止缓存装饰器 + def dispatch(self, request, *args, **kwargs): + """请求分发方法""" + return super(LogoutView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + """处理GET请求""" + logout(request) # 执行登出操作 + delete_sidebar_cache() # 删除侧边栏缓存 + return super(LogoutView, self).get(request, *args, **kwargs) + + +class LoginView(FormView): + """用户登录视图""" + form_class = LoginForm # 使用自定义登录表单 + template_name = 'account/login.html' # 登录模板路径 + success_url = '/' # 登录成功默认重定向URL + redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名 + login_ttl = 2626560 # 会话有效期:一个月(以秒为单位) + + # 方法装饰器:保护敏感数据、CSRF防护、禁止缓存 + @method_decorator(sensitive_post_parameters('password')) + @method_decorator(csrf_protect) + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + """请求分发方法""" + return super(LoginView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + """获取模板上下文数据""" + # 获取重定向URL + redirect_to = self.request.GET.get(self.redirect_field_name) + if redirect_to is None: + redirect_to = '/' # 默认重定向到首页 + kwargs['redirect_to'] = redirect_to + + return super(LoginView, self).get_context_data(**kwargs) + + def form_valid(self, form): + """表单验证通过后的处理""" + # 使用Django内置的认证表单 + form = AuthenticationForm(data=self.request.POST, request=self.request) + + if form.is_valid(): + # 清除侧边栏缓存 + delete_sidebar_cache() + 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) + else: + # 表单无效,重新渲染表单页 + return self.render_to_response({'form': form}) + + def get_success_url(self): + """获取登录成功后的重定向URL""" + redirect_to = self.request.POST.get(self.redirect_field_name) + # 验证URL安全性 + if not url_has_allowed_host_and_scheme( + url=redirect_to, allowed_hosts=[self.request.get_host()]): + redirect_to = self.success_url # 不安全的URL使用默认URL + return redirect_to + + +def account_result(request): + """账户操作结果页面视图函数""" + type = request.GET.get('type') # 操作类型 + id = request.GET.get('id') # 用户ID + + # 获取用户对象,不存在则返回404 + user = get_object_or_404(get_user_model(), id=id) + logger.info(type) + + # 如果用户已激活,重定向到首页 + if user.is_active: + return HttpResponseRedirect('/') + + # 处理注册和验证两种类型 + if type and type in ['register', 'validation']: + if type == 'register': + # 注册成功页面内容 + content = ''' + 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 + ''' + title = '注册成功' + else: + # 邮箱验证处理 + c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + sign = request.GET.get('sign') + # 验证签名 + if sign != c_sign: + return HttpResponseForbidden() # 签名不匹配,拒绝访问 + # 激活用户 + user.is_active = True + user.save() + content = ''' + 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 + ''' + title = '验证成功' + # 渲染结果页面 + return render(request, 'account/result.html', { + 'title': title, + 'content': content + }) + 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): + """处理POST请求,发送验证码""" + form = ForgetPasswordCodeForm(request.POST) + if not form.is_valid(): + return HttpResponse("错误的邮箱") # 表单验证失败 + + to_email = form.cleaned_data["email"] + + # 生成并发送验证码 + code = generate_code() + utils.send_verify_email(to_email, code) + utils.set_code(to_email, code) + + return HttpResponse("ok") # 返回成功响应 \ No newline at end of file -- 2.34.1