From cce64f3c0ca418e48749b7e9a292ee7121333ff2 Mon Sep 17 00:00:00 2001 From: jiang0110-Jan Date: Fri, 7 Nov 2025 20:35:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=BD=93=E5=89=8D=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=88=86=E6=94=AF=E7=9A=84=E5=B7=A5=E4=BD=9C=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Django | 2 +- accounts质量分析.docx | Bin 0 -> 13483 bytes comments-jrx/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 150 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 2787 bytes comments-jrx/__pycache__/apps.cpython-312.pyc | Bin 0 -> 399 bytes .../__pycache__/forms.cpython-312.pyc | Bin 0 -> 822 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 2078 bytes comments-jrx/__pycache__/urls.cpython-312.pyc | Bin 0 -> 463 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 2477 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 3491 bytes comments-jrx/admin.py | 64 ++++ comments-jrx/apps.py | 6 + comments-jrx/forms.py | 14 + comments-jrx/migrations/0001_initial.py | 51 +++ .../0002_alter_comment_is_enable.py | 24 ++ ...ns_remove_comment_created_time_and_more.py | 72 ++++ comments-jrx/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2568 bytes ...02_alter_comment_is_enable.cpython-312.pyc | Bin 0 -> 773 bytes ...ment_created_time_and_more.cpython-312.pyc | Bin 0 -> 2899 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161 bytes comments-jrx/models.py | 48 +++ comments-jrx/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 163 bytes .../__pycache__/comments_tags.cpython-312.pyc | Bin 0 -> 1359 bytes comments-jrx/templatetags/comments_tags.py | 40 +++ comments-jrx/tests.py | 119 +++++++ comments-jrx/urls.py | 13 + comments-jrx/utils.py | 51 +++ comments-jrx/views.py | 78 +++++ ...(or any of the parent directories) .git | 324 ++++++++++++++++++ ...ckout -b recovery-branch commit-hash | 324 ++++++++++++++++++ 33 files changed, 1229 insertions(+), 1 deletion(-) create mode 100644 accounts质量分析.docx create mode 100644 comments-jrx/__init__.py create mode 100644 comments-jrx/__pycache__/__init__.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/admin.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/apps.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/forms.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/models.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/urls.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/utils.cpython-312.pyc create mode 100644 comments-jrx/__pycache__/views.cpython-312.pyc create mode 100644 comments-jrx/admin.py create mode 100644 comments-jrx/apps.py create mode 100644 comments-jrx/forms.py create mode 100644 comments-jrx/migrations/0001_initial.py create mode 100644 comments-jrx/migrations/0002_alter_comment_is_enable.py create mode 100644 comments-jrx/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py create mode 100644 comments-jrx/migrations/__init__.py create mode 100644 comments-jrx/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 comments-jrx/migrations/__pycache__/0002_alter_comment_is_enable.cpython-312.pyc create mode 100644 comments-jrx/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-312.pyc create mode 100644 comments-jrx/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 comments-jrx/models.py create mode 100644 comments-jrx/templatetags/__init__.py create mode 100644 comments-jrx/templatetags/__pycache__/__init__.cpython-312.pyc create mode 100644 comments-jrx/templatetags/__pycache__/comments_tags.cpython-312.pyc create mode 100644 comments-jrx/templatetags/comments_tags.py create mode 100644 comments-jrx/tests.py create mode 100644 comments-jrx/urls.py create mode 100644 comments-jrx/utils.py create mode 100644 comments-jrx/views.py create mode 100644 itory (or any of the parent directories) .git create mode 100644 tergit checkout -b recovery-branch commit-hash diff --git a/Django b/Django index db6090e..de6af35 160000 --- a/Django +++ b/Django @@ -1 +1 @@ -Subproject commit db6090e01f0f159a01f48651caf20f3a80417181 +Subproject commit de6af3510d198edb88d9eb751c46957da2bd78ea diff --git a/accounts质量分析.docx b/accounts质量分析.docx new file mode 100644 index 0000000000000000000000000000000000000000..50a57f2c32e95149bd70f4551cd874752ae99a9d GIT binary patch literal 13483 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 BRyY6v literal 0 HcmV?d00001 diff --git a/comments-jrx/__init__.py b/comments-jrx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comments-jrx/__pycache__/__init__.cpython-312.pyc b/comments-jrx/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a16df2471018889066a6bf294c221f653d50eff GIT binary patch literal 150 zcmX@j%ge<81a?X%GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-VJH3AaORxzQ)sYS&x zPnXYm(YyHBn#D0LS&4b+`A#|c=`qRqxw)x%CB-rE@tJv7fK6rTO@za2Y2gr8Djk`P$O2~rVAn8XD_o060uHHCwTwAy&b$%ge0v+D|5 zAh{A!xm1EvD)r~Wp|OI*wK>tATB#QsQaOy8ORHAp7Ls!5sc**K5F(ITsmG3X-@bY8 z?VC3<-+MDZhC+S>E!p$KT*ZyhudL%VSBu&F3YZ0?BV902R!9i~<3&@@O0 zcS&mUsZc4P1fQ~wB2bs6XN?>%9)dr@#KOP^GI^5KY<14gn$Qi-V%x^oZPnDurF6>+ z_r-YjZXN-$fNqHMt%$j{MRei5=)^0a5>lkecdG*R8J3E3+Kh>z<1Q9-&Bj%U`m=c* zn>;8M(zPPCZ0dy%DOuRI*l5vc^!ZWvjwYrrSrGE{Z(o1$$EWvxzkhG~+`N{X&7U>% zv(xE(Hj8t%HO+ku6-soMZdhzQ)lu1+U9?C$xbnbjMSxV$svP{};>Q<1!I+H4g-{Gse zB}jwWR%7eO+U6XP1%y#b&{0a%g_NX=DOs0NE?rK!byv!Rz1TM^ru-a{0o?<*3 z`U~_0)Hd!72-obi~SdWL($mTGU83qX5sUu3pd0vSiQz4 zFF%fL^P6D_){69|WQQEBF5Qqm7j*eGp0>8mv4{2R{^z#2U9Yiac;3+<3fGq|2OLwV z>Yglj@VrbMvQ&7|bC9>J^vD{SwaUI>EjmYVW~8Sq_9V;l@KG(P_^4Ye+H-kAU1}EF z8ue*~0@S1#Y__U~n$xnFu>EP`f)!-y%^B&dOhH{Zs~Kihrm}_243+XT^Hku*G&^+>c4iPa;q#nA`j)$vAr zxE>#F#K-FKv0C`O+w#xh?%M8wMZLcJt#5iC#+G9b2bTwHC(o~h$8XDP;eEe^+828t z#Hz7IY~)dF6}`ZgwQ2nQxYSWg>@wQE(^RQ zyc*_v{)ahiRI}}bBRK$a#Y;U-t?7Y2>1CvkksXL~*@g`T=`g5vVCD@FmG{3AGcDKb zptM-2X+kD&H_(V2tVa$uA_Mivz~a<{E7dEF!DM|f*%&-uA3R@UsCXGN1Iq&shn9zG zC&yR96C5(MpMhlSxqOEF!icDUsRR) zRaPd8a5AbisH)eBn#mFddLh$*8d|_aEoift26-2b(SoU!Xvg-R%2S92xGZCsHX99J zPn$&@^COn9q7O@)X64)B=|$LjQ9-5c7NoVU>exyXqreQRh-0~#irrvF80m!9`T)o+ zwA$5Oxv(y}yY^PzUvESEj@RVw)sARoVzup9Wo$LvSL;vI+D=u*He{FUvar!6yG{$w zf+#kycw%X9P3~He`=1<97L}zSJ53KYhhu|GDs8k)=NE~gbUtSjV}`Jkf(BZ`A~7wF z3EJ*RY{RsM*ma5UnzQF6xlOd3W%6T-u<}n5jIe75UlVeQIrm>+_a1&c@~M(xre{Yy zUxUBA!_6&9lCvOV*DUJ-5U2ryu!f>*=)f9M*3gL!w;&wa@CriTGcOv5*W$^?vA1hz o*9toPM2Xb~h94^2<3bh05YaCq%uS?rZ7Y?r7%V@ zS2Afb-{N!5&&^HED=CKPy~U9X<@;%}+~SCj&r8frjgP;@6(66QpHi9wV)Mku7nUaG zKxJ4!^h$=$AbY;*0SRZTn9$SRpTv^^3y> m;!3+Bejpd*_hJzs@qw9@IYcOU5y`?Azx%Z$4 zp|^VQ)`Lp>hX`KmsbQceso0yC7NIBK%tUPl_S^5x+xOo0-rFy;voi=zW9jqDWsJ}_ zW&AzPgp3c+ zmd<7-k{rmj^^Ebj3Dy}DR<6QomLxpwZ^ z?P2)PI7g`wk9S)2$^qME)X>8md5@V;~f-yU=YXH&|QYIVl90RAiE zB(NFo;J_JLKDn%1$ys0L{kF7UWNg@ia&8o*?Sw~>oQa|&V}n@nQWR|u+OhEn3b~~N zP996T+2t|IKcTTIdvPCN^Zs+rLit5mK}CUDs4T6eecs`ssl>?Q zvo2#iT}yj|zMSi|1?(@H)Q!4KXA(D6uui<7m|B~L0+75002;j7JLlM|9eK4;dH%S( zcvN2e_~4x2J1bx9h2PY2s=p9`Nux5f{DP!RH#-q#jJj@CsQ*S%J-A#?(UhH*a_K%Y lb8m=y@V(k-UIq9`FvcgyJ3&jQ= literal 0 HcmV?d00001 diff --git a/comments-jrx/__pycache__/models.cpython-312.pyc b/comments-jrx/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cefff994c9ba28f2890a7acc4da555f4e1606fe GIT binary patch literal 2078 zcma)7O>7%Q6rT0&de`=@e-b-RNJvcj7q`U%A&La3;5JQLwMiwC^sq!)t+NxyVZB>s z*QK_4@Szgv0X3)+?STU*2dEMxjvRZe#3fcMdAlMfXa%>Tv>^4wn_W9eX^R-k&+pB= z_vZb4Z~U_?M-i-}qjwg6;SlofR~%5aD;IjW&G>L3e6;#B4hJip@=A)zB|{l?G~Fj5#DDWMR8FvQ)8 z0k1Q9Xq{aP1(L%1deZZHurKJ`8u+9Oi|hQhP2@jq!Y{Om`Zg~p3zn8%3GUcU{o93Y zPwdjr?$dx@7q?5Ac&YcK?s&+{=uvvB%8}dBCnp3gNoKvY{3LqUXXf?xWBn{1Wa)7tWLft>~!dlH)v`9YW zNf$9GSvFQpt%|*fWvYNm>|jsO0{D5Hjg5sy{DMXxhTMD*%S@glGKAytB||em@i?<) z7$i=2!r0VG2A;^XBnC1`QPKnC86bD&18p6IlN})IOqo$wC>W=Qx&>2s%qwA?RfIa1 z=buWQaug=9qczyqE4n9iqVhr&eJU?{qUxuks$Nu8A@7=jftOVE)0$?`L_rcQqGJNX z=rL-LO7HGcs27YH(F{+9sf3AiuJ@??duYdny_2K&J z-1w4amaSukRi5iaWX}a-Ubs;AI2tg?fN2iC_NzdyAU8d*^3Jk&KihwE;QHld$sInp zAr89zLsw-&)yOE2`OaVhixA~$^|t6F-9VQSea-&8rU4yOfB~*#>F}74Bq-R_GDltm z>Itf9J496*iEcGJ8x+1aeIc62P4r%?U#)M5{eC1<`7G&&12RAfy_`@4dyyGg%9r@F5Y?TUiRDumv-gh<@0TMxFrv}VzMpfT4L^g zrf=oa^^e`2{u_la3y%dhKH8jkB%(xad7&L2X~joeF+r8;*XMSW5~IzNpwxF@<$OD{ zzm?hVig8fd$R4Bu#wV%P=R z*oI}|pyB<`o)OKoVUpm5BJ?FA6d7Ku1k+LohQx2E$4}Bf#-Jh3HtDJOJ}AO4A0{sGP|ZXHD0 zKSXdU4lM_QA}w|+R2L`jniTX6@4e6QzVG|)S=ToJkLuo2Ys%|~fK@1IvuN?=4q$+x z3p{8-2yg~VZpM>LiTAQ8X8?g%AyRv}ZM7h&ZT&wB>Zeb5ZJ_q01e(xPyQsK66Ig^y z4(+%#g9#`X4!NWZH3H8geoU|S?I?B{E-6(UKQ5bV%b_^b;{SBb91KJv@Wh-8dve#ViNiE5iGb2d>cH~ z%GMbr5w#`{_n)`D>7ZvFFKv$8plPixw}ht=c7IUC3vkCJ)o6>0#XspGPjd-E_zH3p tu>Gd(T%&gxjgc`%`UvHRC_h4lAu7C}gR#E*rAkU}E-Bf}jTUWk?JtF@eV+gT literal 0 HcmV?d00001 diff --git a/comments-jrx/__pycache__/utils.cpython-312.pyc b/comments-jrx/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8cc5fb4e368e80e07a1aeb0c4cdd6c32bd871a8 GIT binary patch literal 2477 zcmcIm&2Jk;6rc6lYddzF65_;dQaW)`HEvBT6-29&CZb6bMO2lLij-PWthIM)ue08@ zX2(e!*~kYDDG0SUBDF|GJrtDw70^Qw2Vw%+YB3TLDuSC!sD#RenYA}=!gAqbWY4@e z^FDv??aqGL-Q5M~n2bKmJrw}>kumn*+r(~?5+4EyNSp$aY;`GI!Ik7WM)Qih;7NKc z+D&O8DR96A5XgsYL!A}*wzw9T^h=(1L9(mN#e`qj*5w#y2D69|v_?{jxL&6GAUzeUM?+qF(TSKU-6xc`iC zPRLe0mIqSH->6t{?~eESFyUyt88YxmZ2_Bvxm;`M!=1o05dv{9aZLvHUlg zmO&Ye0Mc)J9R>JURUi?Y&kx#EXRQc!<{Y?6d%7yTBb3|gDz^&O_^V)*i+L}v4_wKK z>aq^6X&9EZ5{4PAP@v7Z?#mHU)Xyan;(N65CW@1mAd`VB2$x}@l*#=?ONW{YNlw-w z>+Oj|ak2|KjH}d0MpWU7OvT`Y2y+-+J0CqhCSoFI6qG7qC8kFqM#}kUTGN&bB3_P8 zcFu`R6cp1Z5~8yMqEX!y8LjTiNsl{5(@9Lsin2QFC^V7AFyZWM{u)ggK}A-Vp_tZI z5X()~2uvfW6_JXhI4mki)aij%EVuBnq-mHI=4q_0>IjcJdhTRxp%QbMf#+Z{yKTn8 zM0}qXi^98CM^=!32QbY%F$r^`4xPfoD1+n`BtelPUR28CuqA(dtldbdv?i5fx|42O z6gnD)Sq)}#D6;#vk3 ziL9xnplI1FVoDfmIOa9Id!pHgitnY^fGwF}hWV%vqjSR%5bj~B~kuXWAY5VR6F zO6>&bUq4LSwgwu3!JWWSBQRPIjMiovp`PtSv7OM#+T5eQq0K9svvXv}_s-ScY`oal1VaCbT_E(0HG|;wS#EnFWh`Xt3t3~KP^Z6>>XUAvPvdHr znjsJh-&nf7WONVLyN7GD4R`3Ku(7r|a3}taJJEE7JdsC%UL!F4bzrzLIMf8*mm<5s zJ9L<8#?L$)`0Cgf$Bg-<`ux(P_$x;ILOp)Lh|ksIb5D3~Xr6o0J&rDiYaKWc@8$8n-0Kw?*zePYW{43x*k%ljHBX~V{ z^Q{{_*L(hG@^tH&ewae^ng5Mfr>DT@Q~sF~`~zQjCdxmEdMF(Wm|j+4SF3F%R=4<4DOD_>R0^}353@>r3U=uX z@DQDz`6ddQR8r6+K2BNoZK99SiAKzE-+|C$@Y-YW^7r7x5B?(?1BU-d-G5~B?2do5 OChQ7>Tm+9&;XeU8$5J=| literal 0 HcmV?d00001 diff --git a/comments-jrx/__pycache__/views.cpython-312.pyc b/comments-jrx/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbff5a7e94a61cda169321c6cfed38be11a0471f GIT binary patch literal 3491 zcma)9TWs6b89pRMi4rB#lI-|W+p%NIjhaSvIy>FkEm)U1*#^uQ+O7|(0fG@}TMBjQ zA(gg%)o;*zf{wGPFe_Mb!2 z7a?u;13LWA`Oo$Huljo^6hJ^1PW^4B&5zK(NXI4KVH!UNW(lcCWptEfk_A`F$8KS(Pw`8TH1iW7j=&jjs zGE8|wZ zS$Iw}3i+I&J*TN@tffpC4^L~RoWGKRo}9<>=*TDxwq`YRCa=n>mdazrq#FcN2A+}& zIB(hug-dyyeKD=Q0`rA$>G|m&8X7ss_bfKkDP7xR?7N5;^VzJHGs$!!cDgD1-gfIe z0yQR}|2jacp(LZCB&%^rkIE!@7HMA1w@S7IDx)bzWfz21yNi;7$}OOzU-dv6Pnv5|c(H_BQht0|zn_an9n6VnM^0thWR;Z4?wUH52z(9z)ZoERTYx z4a%nd;Ek zx^%uGoX4H8{22b^F$C2H!Tn3{XCI0`djx+D^!E__OH7GVxwqM4SYrYPegv90Yk8)? zdGSfo@ie0x!CFq$ux#dK?1UGXL?Fh4q@nwerLJ*1KnBU4ayoa7gsRz|W<-xld&%lA zgLX@b)}-O8G+b}%tatQ%%yXv$4aBvI`-t<4UjXgTz&t+>+RRaBfLjonV-L+cny`Agt z?)~A--L)TYFP3-SyS2Ue7L4!B|7G{DH>rbC7qggDkzljC_Pd>5zW&kb^6uKg?z5qv^nAkF8U>X)l29yoWG}F217p!nW0WUUXCzi_Kkl~mysHU%-b%xHy z)OaWEgB>hS5t0%nMsE4+!ok1;(8E2XX^O!u?v{qBm_^8W_7{_&2FXylrqQrswW>6; zk!vN2W?DBe(XbVDm*+H1h4?~po#js(vX)b>=$hqKicqLvD+mRLl7lHw^eC8^q}6i# zO*Hmn5=r8LBhl$G7{E`G<{(a4KIc&T3W9@@fk$DQK~zZ70d4G@5TL=z4rJXI?Y!(3 z8;L_1v1g+jiimjx!RkiojZ#hOuS)%8&qlbb79OaE2WsJ1H5|Lsw$`!IQ3=P^!{^H0 z&(9`XVx%SxRK3gO;u@(CY+2z!hAl(SQ z5nO!XfzS;({1zmw$Pi9~enlVXtv@o(wG)B5l zk%b|r=t^n2F3WKSpMX(=RCwQ{N~NV6ZAj=f#-RCZ0oB7j^WUqtCFZ~XPriLoU*`uK zEW=-78XWQt?Njy>uZMr?lc0|m{}bur?HAE~CgSbZ3ERvQP*!UR?bVc4AT_#SyMyQm zXP{E2=IVMy!T=jLQ$^FD)s5BROr(yII4IH$`eduek=k|Pp)^4XcnXNsb!^JCmeX)L zm4H75CKlkKmZYRo`C`sA5`eu1&Pcsoq~7U_5I?YZV%&zU39lm{&PU(7f@spEX#Q9D zx~;_v_`4t{FdHOP?sE*oY@;VP(c_yav57`E(HJn>XlN6i+(dnw=#hVWN0{@AGoK>( H&<+0w)pj_l literal 0 HcmV?d00001 diff --git a/comments-jrx/admin.py b/comments-jrx/admin.py new file mode 100644 index 0000000..a4c2ab4 --- /dev/null +++ b/comments-jrx/admin.py @@ -0,0 +1,64 @@ +from django.contrib import admin +from django.urls import reverse +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + + +#jrx: 批量禁用评论的动作 +def disable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=False) + + +#jrx: 批量启用评论的动作 +def enable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=True) + + +disable_commentstatus.short_description = _('Disable comments') +enable_commentstatus.short_description = _('Enable comments') + + +class CommentAdmin(admin.ModelAdmin): + #jrx: 每页显示20条评论 + list_per_page = 20 + #jrx: 列表页显示的字段 + list_display = ( + 'id', + 'body', + 'link_to_userinfo', + 'link_to_article', + 'is_enable', + 'creation_time') + #jrx: 列表页可点击跳转的字段 + list_display_links = ('id', 'body', 'is_enable') + #jrx: 筛选器(按是否启用) + list_filter = ('is_enable',) + #jrx: 编辑页排除的字段(创建时间和修改时间不允许手动编辑) + exclude = ('creation_time', 'last_modify_time') + #jrx: 自定义动作(批量启用/禁用) + actions = [disable_commentstatus, enable_commentstatus] + + #jrx: 生成评论作者的后台编辑链接 + def link_to_userinfo(self, obj): + #jrx: 获取用户模型的app标签和模型名称 + info = (obj.author._meta.app_label, obj.author._meta.model_name) + #jrx: 生成用户编辑页URL + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + #jrx: 返回HTML格式的链接(显示昵称或邮箱) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + #jrx: 生成评论关联文章的后台编辑链接 + def link_to_article(self, obj): + #jrx: 获取文章模型的app标签和模型名称 + info = (obj.article._meta.app_label, obj.article._meta.model_name) + #jrx: 生成文章编辑页URL + link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + #jrx: 返回HTML格式的链接(显示文章标题) + return format_html( + u'%s' % (link, obj.article.title)) + + #jrx: 自定义字段在列表页的显示名称 + link_to_userinfo.short_description = _('User') + link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/comments-jrx/apps.py b/comments-jrx/apps.py new file mode 100644 index 0000000..ea70691 --- /dev/null +++ b/comments-jrx/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CommentsConfig(AppConfig): + # jrx: 评论应用的配置类,指定应用名称 + name = 'comments' diff --git a/comments-jrx/forms.py b/comments-jrx/forms.py new file mode 100644 index 0000000..85f5cc8 --- /dev/null +++ b/comments-jrx/forms.py @@ -0,0 +1,14 @@ +from django import forms +from django.forms import ModelForm + +from .models import Comment + + +class CommentForm(ModelForm): + #jrx: 父评论ID(隐藏字段,用于回复功能,非必填) + parent_comment_id = forms.IntegerField( + widget=forms.HiddenInput, required=False) + + class Meta: + model = Comment #jrx: 关联的模型 + fields = ['body'] #jrx: 需要显示的字段(评论正文) \ No newline at end of file diff --git a/comments-jrx/migrations/0001_initial.py b/comments-jrx/migrations/0001_initial.py new file mode 100644 index 0000000..19a341b --- /dev/null +++ b/comments-jrx/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 +#jrx: 该文件为Django数据迁移文件,定义了评论模型(Comment)的数据库结构初始化操作 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + #jrx: 标识这是初始迁移(首次创建模型时生成) + initial = True + + #jrx: 依赖关系:执行此迁移前需先完成blog应用的0001_initial迁移和用户模型迁移 + dependencies = [ + ('blog', '0001_initial'), #jrx: 依赖blog应用的初始迁移(因关联Article模型) + migrations.swappable_dependency(settings.AUTH_USER_MODEL), #jrx: 依赖用户模型的迁移 + ] + + #jrx: 迁移操作列表 + operations = [ + #jrx: 创建Comment模型对应的数据库表 + migrations.CreateModel( + name='Comment', #jrx: 模型名称 + fields=[ #jrx: 模型字段定义 + #jrx: 自增主键ID(BigAutoField适用于大数据量场景) + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + #jrx: 评论正文字段,最大长度300,显示名称为“正文” + ('body', models.TextField(max_length=300, verbose_name='正文')), + #jrx: 创建时间字段,默认值为当前时间,显示名称“创建时间” + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + #jrx: 最后修改时间字段,默认值为当前时间,显示名称“修改时间” + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + #jrx: 是否显示字段,布尔类型,默认值为True,显示名称“是否显示” + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), + #jrx: 外键关联到blog应用的Article模型,级联删除,显示名称“文章” + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), + #jrx: 外键关联到用户模型,级联删除,显示名称“作者” + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), + #jrx: 自关联外键,指向自身,用于表示上级评论(回复功能),允许为空,级联删除,显示名称“上级评论” + ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), + ], + options={ #jrx: 模型元数据配置 + 'verbose_name': '评论', #jrx: 单数显示名称 + 'verbose_name_plural': '评论', #jrx: 复数显示名称 + 'ordering': ['-id'], #jrx: 默认排序方式:按ID倒序(最新评论在前) + 'get_latest_by': 'id', #jrx: 指定通过id字段获取最新记录 + }, + ), + ] diff --git a/comments-jrx/migrations/0002_alter_comment_is_enable.py b/comments-jrx/migrations/0002_alter_comment_is_enable.py new file mode 100644 index 0000000..b223849 --- /dev/null +++ b/comments-jrx/migrations/0002_alter_comment_is_enable.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.7 on 2023-04-24 13:48 +#jrx: 该文件为Django数据迁移文件,用于修改Comment模型中is_enable字段的默认值 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + #jrx: 依赖关系:需先执行comments应用的0001_initial初始迁移 + dependencies = [ + ('comments', '0001_initial'), #jrx: 基于初始迁移进行修改 + ] + + #jrx: 迁移操作列表 + operations = [ + #jrx: 修改Comment模型的is_enable字段属性 + migrations.AlterField( + model_name='Comment', #jrx: 目标模型名称 + name='is_enable', #jrx: 要修改的字段名称 + #jrx: 字段新定义:布尔类型,默认值从True改为False,显示名称仍为“是否显示” + #jrx: 此修改意味着新评论默认不显示,需要人工审核后启用 + field=models.BooleanField(default=False, verbose_name='是否显示'), + ), + ] \ No newline at end of file diff --git a/comments-jrx/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/comments-jrx/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py new file mode 100644 index 0000000..80cc3cc --- /dev/null +++ b/comments-jrx/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py @@ -0,0 +1,72 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:13 +#jrx: 该迁移文件用于更新Comment模型的字段名称、 verbose_name 及模型选项,可能为了国际化或统一命名规范 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + #jrx: 依赖关系:需先执行用户模型迁移、blog应用的0005迁移及comments应用的0002迁移 + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('blog', '0005_alter_article_options_alter_category_options_and_more'), #jrx: 依赖blog应用的最新迁移 + ('comments', '0002_alter_comment_is_enable'), #jrx: 基于comments应用的0002迁移进行修改 + ] + + operations = [ + #jrx: 修改Comment模型的元数据选项 + migrations.AlterModelOptions( + name='comment', + #jrx: 更新模型的显示名称为英文(可能为了国际化),其他选项保持不变 + options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, + ), + #jrx: 删除原有的创建时间字段(旧字段名) + migrations.RemoveField( + model_name='comment', + name='created_time', + ), + #jrx: 删除原有的最后修改时间字段(旧字段名) + migrations.RemoveField( + model_name='comment', + name='last_mod_time', + ), + #jrx: 添加新的创建时间字段(新字段名),默认值为当前时间,显示名称改为英文 + migrations.AddField( + model_name='comment', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + #jrx: 添加新的最后修改时间字段(新字段名),默认值为当前时间,显示名称改为英文 + migrations.AddField( + model_name='comment', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), + ), + #jrx: 更新article字段的显示名称为英文 + migrations.AlterField( + model_name='comment', + name='article', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), + ), + #jrx: 更新author字段的显示名称为英文 + migrations.AlterField( + model_name='comment', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), + ), + #jrx: 更新is_enable字段的显示名称为英文,默认值保持False(需审核) + migrations.AlterField( + model_name='comment', + name='is_enable', + field=models.BooleanField(default=False, verbose_name='enable'), + ), + #jrx: 更新parent_comment字段的显示名称为英文 + migrations.AlterField( + model_name='comment', + name='parent_comment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), + ), + ] \ No newline at end of file diff --git a/comments-jrx/migrations/__init__.py b/comments-jrx/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comments-jrx/migrations/__pycache__/0001_initial.cpython-312.pyc b/comments-jrx/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14ecabf37060d84de6601a2f86edbe12e76ab8fd GIT binary patch literal 2568 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 literal 0 HcmV?d00001 diff --git a/comments-jrx/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-312.pyc b/comments-jrx/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07b0b9dd3e9c49c05bbb5157df2995ba5a68a1b1 GIT binary patch literal 2899 zcmcguO>EOv9DlYG$4L`tO$)TNCD0EVwxwunhq0-mkN_H#YS3;X%CMZim*C*o!FE8& zinK`_cA3O&7j7I@)JW{ek)1Yams+co=yBL#+O*v&Q7${}y=S}aNUFhs`Cz~Q@BP2t z|LghZL?Q;rGjsH(8;K|YzfmH1!v5lUjx4?f5I{}`6)wkdbj|C0MaT)2P%gx1K@aCd zf(v>iC-UGtfT4Q;hLKS24HRyFR|RjKlj=fB{EHGP4ofz29L*@%DIUivTFFu!%`|K* zR!oR=`|{GZ0s!{;#PgvL^$vj?2SJYK0kJ6UWc1c3T#k35tDJ{H4)P@-7h%#}{DlGx z!ElKqUjd2_Le0o3?@2NVxl=RYlbX?2;EKJ()!$WD53$ozKG?a366N7ev^?4cOLk%~ z-b@5OHunTsVQ1>@$KE&ONAIqF9PQ$@0Q;C9QAff{FI`( z1ND*AD2~nrqlCpml4(I? zk(r0%w~t==DW$5{ELEr8v(1R_DvmE%h^T^!qgAM5F*Ycf28+%loX}Nb zhoFoDCm27Osf=uFkx5@Z0#Q+|>5fH5U`ntC$j4I@E^0`JxHr(J73-c3h9rpvf-p5W z;s>-5r=d>!R7${6VwcQuwdUL~Egap7+Bq!x;oDPHEfQtLkK#h3H;8-M zUo?D{q)(^9I9$+G<0c7IP1h|t8Lbieq6fO1I*28OMMqI^Orde9k;E%Ft|+%^s!oHe zC>FU-0PeT%sMRV>Bk{S442X=Sx<#)oymxkS;exVwHG4&wo6lq~;rJGZhHTt7OUr7G zURm>AN#UL=sAAqm=U5D-SqRz6q9uzH8Ir*Tl0R=(gk{wznK)c?G~LD$%}m2I5Ecyc z4vsP19%t+vsYb12Tts!u!vdCAY)Nu)UU7OTDKy^Fnf`o4q!{*^(z+d2;3Ux0G7E7ze#?lvivRzhuiW5b!x(uCsxqYsdf3{E}Tbp znjZgt;pwM8Ui;x%E4%1s7hBouZuWXBTXeIg zDvEo&ZXo?`6ust)`UotrKJ|KNa0^V6o+iJF>7W;^G|`3!{C(C_>7b>&BK&}wV>Uk| ztG}g>ys0o_ohBsxJ+vptYeV2T?u7`rBfo;FXCV0u3~!1jIq`n1)jRI?j=uneU~2vW D{f4o& literal 0 HcmV?d00001 diff --git a/comments-jrx/migrations/__pycache__/__init__.cpython-312.pyc b/comments-jrx/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6b09cc16d9dc7a769a37e695c288a26568aef35 GIT binary patch literal 161 zcmX@j%ge<81a?X%GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-VJwFMH+RxzQ)sYS&x zPnXYm(YyHBn#D0LS&4b+`A#|c=`qRqxw)x%CB-qhndwD|C7Jno#WC^mnR%Hd@$q^E imA^P_a`RJ4b5iY!Sb=6S0&y{j@sXL4k+Fyw$N~Tsj4E#c literal 0 HcmV?d00001 diff --git a/comments-jrx/models.py b/comments-jrx/models.py new file mode 100644 index 0000000..9670a14 --- /dev/null +++ b/comments-jrx/models.py @@ -0,0 +1,48 @@ +from django.conf import settings +from django.db import models +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ + +from blog.models import Article + + +# Create your models here. + +class Comment(models.Model): + #jrx: 评论正文,最大长度300 + body = models.TextField('正文', max_length=300) + #jrx: 评论创建时间,默认当前时间 + creation_time = models.DateTimeField(_('creation time'), default=now) + #jrx: 评论最后修改时间,默认当前时间 + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + #jrx: 评论作者(外键关联用户表) + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + on_delete=models.CASCADE) #jrx: 级联删除,用户删除则评论也删除 + #jrx: 关联的文章(外键关联文章表) + article = models.ForeignKey( + Article, + verbose_name=_('article'), + on_delete=models.CASCADE) #jrx: 级联删除,文章删除则评论也删除 + #jrx: 父评论(自关联,用于实现评论回复功能) + parent_comment = models.ForeignKey( + 'self', + verbose_name=_('parent comment'), + blank=True, + null=True, + on_delete=models.CASCADE) #jrx: 级联删除,父评论删除则子评论也删除 + #jrx: 评论是否启用(是否显示) + is_enable = models.BooleanField(_('enable'), + default=False, blank=False, null=False) + + class Meta: + #jrx: 按id倒序排序(最新评论在前) + ordering = ['-id'] + verbose_name = _('comment') + verbose_name_plural = verbose_name + get_latest_by = 'id' + + #jrx: 打印模型实例时显示评论正文 + def __str__(self): + return self.body \ No newline at end of file diff --git a/comments-jrx/templatetags/__init__.py b/comments-jrx/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/comments-jrx/templatetags/__pycache__/__init__.cpython-312.pyc b/comments-jrx/templatetags/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f56259255c6206a2ff3fe9f289d69f47dba001fc GIT binary patch literal 163 zcmX@j%ge<81a?X%GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-VJwFeT;RxzQ)sYS&x zPnXYm(YyHBn#D0LS&4b+`A#|c=`qRqxw)x%CB-o%sksF?i6yBeiRr~L@$s2?nI-Y@ kdIgogIBatBQ%ZAE?TT1|rZEC>F^KVznURsPh#ANN0DO)sKL7v# literal 0 HcmV?d00001 diff --git a/comments-jrx/templatetags/__pycache__/comments_tags.cpython-312.pyc b/comments-jrx/templatetags/__pycache__/comments_tags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3553f25a54fd1b940d1cedafbc9f6f4486780144 GIT binary patch literal 1359 zcmZuwO=ufO6n-=NV?{qnHnn3L#dup|iY^L06cRU>w6$--hyGlGinGyfq!p_lX4V8J zGVDQMDb#JC4GJZfl0#FR{y-0fLQ9W@USeFZyQL5)O5~e_Xel}M&1%;+qyszi{^rg5 z-uw2~R4NX*7GL?L)T01@2_;%mC=8n@bif3YI8Y&12muNfDa%pd(YYOpS0qa+$Byig zWYQ)a#wZw*iabZBDQ%OHX3EE+$JUjMdLSGb;>TR8Qg;m3DqzuvOB5`=VHUSd*rc0M z2a0f;wxC5@(sjus&Dd>;LR)H(=n(>K`YvSX+AX})`{?`bPo3_MpLE+>y>FVmuYc%% zb!Q|z`>fe*b$XvajO*cg_UXf?-`!o%Z_Vpno<=8t4kT#8oT>JLq}d-%BXIy^w#OTa4$sU)=9) zJ$U-)L4K)hRExE@oLVs-btxYW+%<~)(7GzLY<-=b!bCS;oI>`JQ!5w_U(5zi3$pRo zar@VUeeK+yHn*eAef!$a@?&joS6gnWe@vuX%lk>~<6Jv;Z{oM)?C(kK&iqzxpupL4 zL%axILPlmHk3SZc#!jLZ6u`yQuIiUBv(q9#dw~>e9PI)=pTX_92mLstPI=4!$g(m$ zNP-e?z1m#aPn`C0Ov8wqvl0CB0_gcrTeYzn*^*mv{-6C*c(nr!!l+5A#H1Uv0S#(O z_XW@8HBrEb(g%W42ZB8~)#UN)I5Mm5me?8LlbE2QKW6h_wrm%7pn5P+;mmaV&3lHo{A6l&UpqTcq@>yhNl^o+E18(Dn!((eNbQg1>}$+m z>wb(`MI;gOEzWKH=`Atzpp$;et`?j%Zr7@TO*|P?9ApeCdmg(~shMkzwa8vZufWUS z!L2V5LjHvGlSI08VK0&1Nu+lZGhVE(0*&vH@f|WgpedpaC7=_1A@%jZGr2=1A5UG} RB?|+p5>@!$2&CZ6{{SgwU_k%? literal 0 HcmV?d00001 diff --git a/comments-jrx/templatetags/comments_tags.py b/comments-jrx/templatetags/comments_tags.py new file mode 100644 index 0000000..5e47c9f --- /dev/null +++ b/comments-jrx/templatetags/comments_tags.py @@ -0,0 +1,40 @@ +from django import template + +register = template.Library() + + +@register.simple_tag +def parse_commenttree(commentlist, comment): + #jrx: 模板标签函数,用于获取当前评论的所有子评论(递归获取所有层级的子评论) + #jrx: 参数说明: + #jrx: commentlist: 评论查询集(包含所有评论) + #jrx: comment: 父评论对象,以此为起点查找子评论 + #jrx: 用法示例:{% parse_commenttree article_comments comment as childcomments %} + datas = [] #jrx: 存储子评论的列表 + + def parse(c): + #jrx: 递归函数:获取当前评论c的直接子评论,并继续递归查找子评论的子评论 + #jrx: 筛选出父评论为c且已启用的子评论 + childs = commentlist.filter(parent_comment=c, is_enable=True) + for child in childs: + datas.append(child) #jrx: 将子评论添加到列表 + parse(child) #jrx: 递归处理子评论的子评论 + + parse(comment) #jrx: 从传入的comment开始递归查找所有子评论 + return datas #jrx: 返回所有层级的子评论列表 + + +@register.inclusion_tag('comments/tags/comment_item.html') +def show_comment_item(comment, ischild): + #jrx: 包含标签函数,用于渲染单个评论项的HTML + #jrx: 参数说明: + #jrx: comment: 要渲染的评论对象 + #jrx: ischild: 布尔值,标识该评论是否为子评论(用于控制显示样式深度) + #jrx: 模板路径:comments/tags/comment_item.html + #jrx: 评论显示深度:子评论深度为1,顶级评论深度为2(用于CSS样式区分层级) + depth = 1 if ischild else 2 + #jrx: 传递给模板的上下文变量 + return { + 'comment_item': comment, #jrx: 评论对象 + 'depth': depth #jrx: 评论层级深度(用于样式控制) + } \ No newline at end of file diff --git a/comments-jrx/tests.py b/comments-jrx/tests.py new file mode 100644 index 0000000..f20bae0 --- /dev/null +++ b/comments-jrx/tests.py @@ -0,0 +1,119 @@ +from django.test import Client, RequestFactory, TransactionTestCase +from django.urls import reverse + +from accounts.models import BlogUser +from blog.models import Category, Article +from comments.models import Comment +from comments.templatetags.comments_tags import * +from djangoblog.utils import get_max_articleid_commentid + + +# Create your tests here. + +class CommentsTest(TransactionTestCase): + def setUp(self): + #jrx: 初始化测试客户端和工厂 + self.client = Client() + self.factory = RequestFactory() + #jrx: 设置博客评论需要审核 + from blog.models import BlogSettings + value = BlogSettings() + value.comment_need_review = True + value.save() + + #jrx: 创建超级用户用于测试 + self.user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="liangliangyy1") + + #jrx: 辅助方法:更新文章下所有评论为启用状态 + def update_article_comment_status(self, article): + comments = article.comment_set.all() + for comment in comments: + comment.is_enable = True + comment.save() + + #jrx: 测试评论功能(提交、回复、显示等) + def test_validate_comment(self): + #jrx: 登录测试用户 + self.client.login(username='liangliangyy1', password='liangliangyy1') + + #jrx: 创建测试分类和文章 + category = Category() + category.name = "categoryccc" + category.save() + + article = Article() + article.title = "nicetitleccc" + article.body = "nicecontentccc" + article.author = self.user + article.category = category + article.type = 'a' + article.status = 'p' + article.save() + + #jrx: 获取评论提交URL + comment_url = reverse( + 'comments:postcomment', kwargs={ + 'article_id': article.id}) + + #jrx: 测试提交第一条评论 + response = self.client.post(comment_url, + { + 'body': '123ffffffffff' + }) + self.assertEqual(response.status_code, 302) #jrx: 验证重定向状态码 + + #jrx: 验证评论需审核(未启用时不显示) + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 0) + self.update_article_comment_status(article) #jrx: 手动启用评论 + self.assertEqual(len(article.comment_list()), 1) #jrx: 验证评论数量 + + #jrx: 测试提交第二条评论 + response = self.client.post(comment_url, + { + 'body': '123ffffffffff', + }) + self.assertEqual(response.status_code, 302) + self.update_article_comment_status(article) + self.assertEqual(len(article.comment_list()), 2) #jrx: 验证评论数量 + + #jrx: 测试回复评论功能 + parent_comment_id = article.comment_list()[0].id #jrx: 获取父评论ID + response = self.client.post(comment_url, + { + 'body': ''' + # Title1 + + ```python + import os + ``` + + [url](https://www.lylinux.net/) + + [ddd](http://www.baidu.com) + + + ''', + 'parent_comment_id': parent_comment_id#jrx: 指定父评论 + }) + + self.assertEqual(response.status_code, 302) + self.update_article_comment_status(article) + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 3)#jrx: 验证包含回复的总评论数 + # jrx: 测试评论树解析和模板标签 + comment = Comment.objects.get(id=parent_comment_id) + tree = parse_commenttree(article.comment_list(), comment)#jrx: 解析评论树 + self.assertEqual(len(tree), 1)#jrx: 验证评论树结构 + data = show_comment_item(comment, True)#jrx: 测试评论项渲染 + self.assertIsNotNone(data) + # jrx: 测试最大 ID 工具函数 + s = get_max_articleid_commentid() + self.assertIsNotNone(s) + + # jrx: 测试评论邮件发送功能 + from comments.utils import send_comment_email + send_comment_email(comment) diff --git a/comments-jrx/urls.py b/comments-jrx/urls.py new file mode 100644 index 0000000..6e9cfcd --- /dev/null +++ b/comments-jrx/urls.py @@ -0,0 +1,13 @@ +from django.urls import path + +from . import views + +#jrx: 应用命名空间 +app_name = "comments" +#jrx: 评论相关URL配置 +urlpatterns = [ + path( + 'article//postcomment', #jrx: 接收文章ID参数 + views.CommentPostView.as_view(), #jrx: 关联评论提交视图 + name='postcomment'), #jrx: URL名称,用于反向解析 +] \ No newline at end of file diff --git a/comments-jrx/utils.py b/comments-jrx/utils.py new file mode 100644 index 0000000..a0010a4 --- /dev/null +++ b/comments-jrx/utils.py @@ -0,0 +1,51 @@ +import logging + +from django.utils.translation import gettext_lazy as _ + +from djangoblog.utils import get_current_site +from djangoblog.utils import send_email + +logger = logging.getLogger(__name__) + + +def send_comment_email(comment): + # jrx: 发送评论相关邮件(感谢评论和回复通知) + # jrx: 获取当前站点域名 + site = get_current_site().domain + # jrx: 邮件主题(感谢评论) + subject = _('Thanks for your comment') + # jrx: 构建文章访问链接 + article_url = f"https://{site}{comment.article.get_absolute_url()}" + # jrx: 构建感谢评论的邮件内容(HTML格式) + html_content = _("""

Thank you very much for your comments on this site

+ You can visit %(article_title)s + to review your comments, + Thank you again! +
+ If the link above cannot be opened, please copy this link to your browser. + %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title} + # jrx: 评论者邮箱 + tomail = comment.author.email + # jrx: 发送感谢邮件给评论者 + send_email([tomail], subject, html_content) + + try: + # jrx: 如果是回复评论,给被回复者发送通知邮件 + if comment.parent_comment: + # jrx: 构建回复通知的邮件内容(HTML格式) + html_content = _("""Your comment on %(article_title)s
has + received a reply.
%(comment_body)s +
+ go check it out! +
+ If the link above cannot be opened, please copy this link to your browser. + %(article_url)s + """) % {'article_url': article_url, 'article_title': comment.article.title, + 'comment_body': comment.parent_comment.body} + # jrx: 被回复者(父评论作者)邮箱 + tomail = comment.parent_comment.author.email + # jrx: 发送回复通知邮件 + send_email([tomail], subject, html_content) + except Exception as e: + # jrx: 记录邮件发送失败的错误日志 + logger.error(e) \ No newline at end of file diff --git a/comments-jrx/views.py b/comments-jrx/views.py new file mode 100644 index 0000000..caab71e --- /dev/null +++ b/comments-jrx/views.py @@ -0,0 +1,78 @@ +# Create your views here. +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_protect +from django.views.generic.edit import FormView + +from accounts.models import BlogUser +from blog.models import Article +from .forms import CommentForm +from .models import Comment + + +class CommentPostView(FormView): + # jrx: 评论表单类 + form_class = CommentForm + # jrx: 渲染的模板(文章详情页) + template_name = 'blog/article_detail.html' + + # jrx: 启用CSRF保护 + @method_decorator(csrf_protect) + def dispatch(self, *args, **kwargs): + return super(CommentPostView, self).dispatch(*args, **kwargs) + + # jrx: 处理GET请求:重定向到文章详情页的评论区 + def get(self, request, *args, **kwargs): + article_id = self.kwargs['article_id'] # jrx: 从URL获取文章ID + article = get_object_or_404(Article, pk=article_id) # jrx: 获取文章对象,不存在则404 + url = article.get_absolute_url() # jrx: 获取文章详情页URL + return HttpResponseRedirect(url + "#comments") # jrx: 重定向到评论区锚点 + + # jrx: 表单验证失败时的处理 + def form_invalid(self, form): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + # jrx: 重新渲染文章详情页,携带错误表单和文章数据 + return self.render_to_response({ + 'form': form, + 'article': article + }) + + # jrx: 表单验证通过后的处理(核心逻辑) + def form_valid(self, form): + """提交的数据验证合法后的逻辑""" + user = self.request.user # jrx: 当前登录用户 + author = BlogUser.objects.get(pk=user.pk) # jrx: 获取用户详细信息 + article_id = self.kwargs['article_id'] # jrx: 文章ID + article = get_object_or_404(Article, pk=article_id) # jrx: 获取文章对象 + + # jrx: 检查文章是否允许评论 + if article.comment_status == 'c' or article.status == 'c': + raise ValidationError("该文章评论已关闭.") + + # jrx: 不立即保存表单数据,先处理关联关系 + comment = form.save(False) + comment.article = article # jrx: 关联评论到文章 + + # jrx: 根据站点设置决定评论是否需要审核 + from djangoblog.utils import get_blog_setting + settings = get_blog_setting() + if not settings.comment_need_review: + comment.is_enable = True # jrx: 无需审核则直接启用 + + comment.author = author # jrx: 设置评论作者 + + # jrx: 处理回复功能(如果存在父评论ID) + if form.cleaned_data['parent_comment_id']: + parent_comment = Comment.objects.get( + pk=form.cleaned_data['parent_comment_id']) + comment.parent_comment = parent_comment # jrx: 关联到父评论 + + comment.save(True) # jrx: 保存评论到数据库 + + # jrx: 重定向到文章详情页的当前评论位置 + return HttpResponseRedirect( + "%s#div-comment-%d" % + (article.get_absolute_url(), comment.pk)) \ No newline at end of file diff --git a/itory (or any of the parent directories) .git b/itory (or any of the parent directories) .git new file mode 100644 index 0000000..74570f6 --- /dev/null +++ b/itory (or any of the parent directories) .git @@ -0,0 +1,324 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + ESC-j * Forward one file line (or _N file lines). + ESC-k * Backward one file line (or _N file lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + ESC-b * Backward one window, but don't stop at beginning-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-m_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + ^O^O Open the currently selected OSC8 hyperlink. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + #_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k _f_i_l_e ... --lesskey-file=_f_i_l_e + Use a compiled lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n ......... --line-numbers + Suppress line numbers in prompts and messages. + -N ......... --LINE-NUMBERS + Display line number at start of each line. + -o [_f_i_l_e] .. --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] .. --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p _p_a_t_t_e_r_n . --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t _t_a_g .... --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces, tabs and carriage returns. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + + --exit-follow-on-close + Exit F command on a pipe when writer closes pipe. + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --form-feed + Stop scrolling when a form feed character is reached. + --header=[_L[,_C[,_N]]] + Use _L lines (starting at line _N) and _C columns as headers. + --incsearch + Search file as each pattern character is typed in. + --intr=[_C] + Use _C instead of ^X to interrupt a read. + --lesskey-context=_t_e_x_t + Use lesskey source file contents. + --lesskey-src=_f_i_l_e + Use a lesskey source file. + --line-num-width=[_N] + Set the width of the -N line number field to _N characters. + --match-shift=[_N] + Show at least _N characters to the left of a search match. + --modelines=[_N] + Read _N lines from the input file and look for vim modelines. + --mouse + Enable mouse input. + --no-edit-warn + Don't warn when using v command on a file opened via LESSOPEN. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --no-number-headers + Don't give line numbers to header lines. + --no-paste + Ignore pasted input. + --no-search-header-lines + Searches do not include header lines. + --no-search-header-columns + Searches do not include header columns. + --no-search-headers + Searches do not include header lines or columns. + --no-vbell + Disable the terminal's visual bell. + --redraw-on-quit + Redraw final screen when quitting. + --rscroll=[_C] + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --search-options=[EFKNRW-] + Set default options for every search. + --show-preproc-errors + Display a message if preprocessor exits with an error status. + --proc-backspace + Process backspaces for bold/underline. + --PROC-BACKSPACE + Treat backspaces as control characters. + --proc-return + Delete carriage returns before newline. + --PROC-RETURN + Treat carriage returns as control characters. + --proc-tab + Expand tabs to spaces. + --PROC-TAB + Treat tabs as control characters. + --status-col-width=[_N] + Set the width of the -J status column to _N characters. + --status-line + Highlight or color the entire line containing a mark. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=[_N] + Each click of the mouse wheel moves _N lines. + --wordwrap + Wrap lines at spaces. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. diff --git a/tergit checkout -b recovery-branch commit-hash b/tergit checkout -b recovery-branch commit-hash new file mode 100644 index 0000000..74570f6 --- /dev/null +++ b/tergit checkout -b recovery-branch commit-hash @@ -0,0 +1,324 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + ESC-j * Forward one file line (or _N file lines). + ESC-k * Backward one file line (or _N file lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + ESC-b * Backward one window, but don't stop at beginning-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-m_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + ^O^O Open the currently selected OSC8 hyperlink. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + #_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k _f_i_l_e ... --lesskey-file=_f_i_l_e + Use a compiled lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n ......... --line-numbers + Suppress line numbers in prompts and messages. + -N ......... --LINE-NUMBERS + Display line number at start of each line. + -o [_f_i_l_e] .. --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] .. --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p _p_a_t_t_e_r_n . --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t _t_a_g .... --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces, tabs and carriage returns. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + + --exit-follow-on-close + Exit F command on a pipe when writer closes pipe. + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --form-feed + Stop scrolling when a form feed character is reached. + --header=[_L[,_C[,_N]]] + Use _L lines (starting at line _N) and _C columns as headers. + --incsearch + Search file as each pattern character is typed in. + --intr=[_C] + Use _C instead of ^X to interrupt a read. + --lesskey-context=_t_e_x_t + Use lesskey source file contents. + --lesskey-src=_f_i_l_e + Use a lesskey source file. + --line-num-width=[_N] + Set the width of the -N line number field to _N characters. + --match-shift=[_N] + Show at least _N characters to the left of a search match. + --modelines=[_N] + Read _N lines from the input file and look for vim modelines. + --mouse + Enable mouse input. + --no-edit-warn + Don't warn when using v command on a file opened via LESSOPEN. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --no-number-headers + Don't give line numbers to header lines. + --no-paste + Ignore pasted input. + --no-search-header-lines + Searches do not include header lines. + --no-search-header-columns + Searches do not include header columns. + --no-search-headers + Searches do not include header lines or columns. + --no-vbell + Disable the terminal's visual bell. + --redraw-on-quit + Redraw final screen when quitting. + --rscroll=[_C] + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --search-options=[EFKNRW-] + Set default options for every search. + --show-preproc-errors + Display a message if preprocessor exits with an error status. + --proc-backspace + Process backspaces for bold/underline. + --PROC-BACKSPACE + Treat backspaces as control characters. + --proc-return + Delete carriage returns before newline. + --PROC-RETURN + Treat carriage returns as control characters. + --proc-tab + Expand tabs to spaces. + --PROC-TAB + Treat tabs as control characters. + --status-col-width=[_N] + Set the width of the -J status column to _N characters. + --status-line + Highlight or color the entire line containing a mark. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=[_N] + Each click of the mouse wheel moves _N lines. + --wordwrap + Wrap lines at spaces. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all.