From beeab5ef0c9e4faafc0d380fb1f79a45630c2f56 Mon Sep 17 00:00:00 2001 From: pbiqa59u2 <3263392850@qq.com> Date: Sat, 8 Nov 2025 13:58:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0djangoblog=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/src/djangoblog/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 214 bytes .../__pycache__/admin_site.cpython-310.pyc | Bin 0 -> 1968 bytes .../__pycache__/apps.cpython-310.pyc | Bin 0 -> 695 bytes .../__pycache__/blog_signals.cpython-310.pyc | Bin 0 -> 3406 bytes .../elasticsearch_backend.cpython-310.pyc | Bin 0 -> 6467 bytes .../__pycache__/feeds.cpython-310.pyc | Bin 0 -> 2140 bytes .../__pycache__/logentryadmin.cpython-310.pyc | Bin 0 -> 2918 bytes .../__pycache__/settings.cpython-310.pyc | Bin 0 -> 7009 bytes .../__pycache__/sitemap.cpython-310.pyc | Bin 0 -> 2430 bytes .../__pycache__/spider_notify.cpython-310.pyc | Bin 0 -> 936 bytes .../__pycache__/urls.cpython-310.pyc | Bin 0 -> 2572 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 7390 bytes .../whoosh_cn_backend.cpython-310.pyc | Bin 0 -> 19418 bytes .../__pycache__/wsgi.cpython-310.pyc | Bin 0 -> 566 bytes doc/src/djangoblog/admin_site.py | 68 ++ doc/src/djangoblog/apps.py | 13 + doc/src/djangoblog/blog_signals.py | 129 ++++ doc/src/djangoblog/elasticsearch_backend.py | 186 +++++ doc/src/djangoblog/feeds.py | 44 ++ doc/src/djangoblog/logentryadmin.py | 95 +++ .../__pycache__/base_plugin.cpython-310.pyc | Bin 0 -> 1546 bytes .../hook_constants.cpython-310.pyc | Bin 0 -> 379 bytes .../__pycache__/hooks.cpython-310.pyc | Bin 0 -> 1585 bytes .../__pycache__/loader.cpython-310.pyc | Bin 0 -> 967 bytes .../djangoblog/plugin_manage/base_plugin.py | 41 + .../plugin_manage/hook_constants.py | 7 + doc/src/djangoblog/plugin_manage/hooks.py | 44 ++ doc/src/djangoblog/plugin_manage/loader.py | 19 + doc/src/djangoblog/settings.py | 332 ++++++++ doc/src/djangoblog/sitemap.py | 67 ++ doc/src/djangoblog/spider_notify.py | 19 + doc/src/djangoblog/tests.py | 36 + doc/src/djangoblog/urls.py | 54 ++ doc/src/djangoblog/utils.py | 245 ++++++ doc/src/djangoblog/whoosh_cn_backend.py | 712 ++++++++++++++++++ .../djangoblog/whoosh_index/MAIN_WRITELOCK | 0 .../whoosh_index/MAIN_dn7zl97zuwmzfmle.seg | Bin 0 -> 6327 bytes .../whoosh_index/MAIN_qqs5xlu2z40nrzsk.seg | Bin 0 -> 18102 bytes .../whoosh_index/MAIN_qstselg7s7e0fhg2.seg | Bin 0 -> 6327 bytes doc/src/djangoblog/whoosh_index/_MAIN_38.toc | Bin 0 -> 2327 bytes doc/src/djangoblog/wsgi.py | 16 + 42 files changed, 2128 insertions(+) create mode 100644 doc/src/djangoblog/__init__.py create mode 100644 doc/src/djangoblog/__pycache__/__init__.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/admin_site.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/apps.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/blog_signals.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/feeds.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/logentryadmin.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/settings.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/sitemap.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/spider_notify.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/urls.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/utils.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc create mode 100644 doc/src/djangoblog/__pycache__/wsgi.cpython-310.pyc create mode 100644 doc/src/djangoblog/admin_site.py create mode 100644 doc/src/djangoblog/apps.py create mode 100644 doc/src/djangoblog/blog_signals.py create mode 100644 doc/src/djangoblog/elasticsearch_backend.py create mode 100644 doc/src/djangoblog/feeds.py create mode 100644 doc/src/djangoblog/logentryadmin.py create mode 100644 doc/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc create mode 100644 doc/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc create mode 100644 doc/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc create mode 100644 doc/src/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc create mode 100644 doc/src/djangoblog/plugin_manage/base_plugin.py create mode 100644 doc/src/djangoblog/plugin_manage/hook_constants.py create mode 100644 doc/src/djangoblog/plugin_manage/hooks.py create mode 100644 doc/src/djangoblog/plugin_manage/loader.py create mode 100644 doc/src/djangoblog/settings.py create mode 100644 doc/src/djangoblog/sitemap.py create mode 100644 doc/src/djangoblog/spider_notify.py create mode 100644 doc/src/djangoblog/tests.py create mode 100644 doc/src/djangoblog/urls.py create mode 100644 doc/src/djangoblog/utils.py create mode 100644 doc/src/djangoblog/whoosh_cn_backend.py create mode 100644 doc/src/djangoblog/whoosh_index/MAIN_WRITELOCK create mode 100644 doc/src/djangoblog/whoosh_index/MAIN_dn7zl97zuwmzfmle.seg create mode 100644 doc/src/djangoblog/whoosh_index/MAIN_qqs5xlu2z40nrzsk.seg create mode 100644 doc/src/djangoblog/whoosh_index/MAIN_qstselg7s7e0fhg2.seg create mode 100644 doc/src/djangoblog/whoosh_index/_MAIN_38.toc create mode 100644 doc/src/djangoblog/wsgi.py diff --git a/doc/src/djangoblog/__init__.py b/doc/src/djangoblog/__init__.py new file mode 100644 index 0000000..1e205f4 --- /dev/null +++ b/doc/src/djangoblog/__init__.py @@ -0,0 +1 @@ +default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/doc/src/djangoblog/__pycache__/__init__.cpython-310.pyc b/doc/src/djangoblog/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d4c390b78ef211b48997aeb866033536d8e3707 GIT binary patch literal 214 zcmd1j<>g`kf+vlQ8P-7hF^Gc<7=auIATH(r5-AK(3@MDk44O<;$|+fidFlB{Ir-^& zi3J74dM zIL5-z(9|NvCAB!aB)=dAVw@Auc-`E@;*!*&7=(o}@$s2?nI-Y@dIgoYIBatBQ%ZAE N?LcN0GXV(>1^@$&J9_{C literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/admin_site.cpython-310.pyc b/doc/src/djangoblog/__pycache__/admin_site.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba59d72361ba6447392208ba4aab604396fbd57 GIT binary patch literal 1968 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 Ndm6q7zBgUy{a*|}m3sgH literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc b/doc/src/djangoblog/__pycache__/elasticsearch_backend.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cc68c0fcc29e47caa15d4b324c30f3cdcfe645d GIT binary patch literal 6467 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 MkTa9be13l7f5$aIv;Y7A literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/feeds.cpython-310.pyc b/doc/src/djangoblog/__pycache__/feeds.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..757115e9553b4b5e5dc5b822c6c4a3420abbb750 GIT binary patch literal 2140 zcmZ`)&2Jk;6rb5$+iQF6xJ~$IpsfW}!4h>zC8P>fsE`Uy9iW3JRl`4>3x-Z*w#$60G<-p;(4_xrssCzVQx!1MCa zqus3ol9-4`IdnoO1@VErW;; zlbEB{S800d!wtdj-#XbXd@5wn3H9b?5=BY;r7!kavLAQw=K05fkGt?{R-Cw$5!c|R zYccBPnBnG`=@yv9a!-hBvpg%nTx2#Y!dzk{R))FE99DtZVO6#QbA{E|1(>VsB3p%d zg{=Xr*Hrah&yTywXJOJspxHXu9smA({Nnk)zl_E&{`lbd`-AZhKf}lIvmcw;`>_4; zuV>?@e;)t#)A;GH_49S>!~D%1xbkL)!lCJdGVHS2%IyRqmCBBT&R*%eOztMa!<#Q4C^TNB!}ZhS=$Pi5+WCZPaQ2kY z$UG$5L=<81gp|N^7G)Sz4fDuvr%9Me?qwnr72HEclL7CVp$mg}Zw(eSy4tzuQ0)vl zEE4VRDAn(L^vw+0 z>+pft4#`8x03Tp9nQ;Ip8|GGa6?Qk1{y+rXUHSJ>{o4We#l!l1A!8rIRR}ERi!$S6 z9|D_*$d}?>SZPeWEUtmPNUDm}Y$S1EjEQhy1YQ@Royf0Mp`=DHV6;sh@93C z6&GP`sauQMt(g9ZPy5rR!#5X9&y>zsoh2EB2|2wjfgY0tm5z%4j5nY z4i49GKu7{R^<12EU%{3l(688rQmhA?SkyA$rM6+4v!CUdT5qdSQL9XM&1NTwr3l)( z5$X=@))#itLCk`Vk6GQkAMpKDV16p+L@kp+nCk3u*XNg-$hXU5E_|5??E_WDCf(Hk pu&M4esx~>I*C#h&-D#A|Xe#^8#K)gtX!SphLmaAqhC|oP{{g{q4+Q`K literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/logentryadmin.cpython-310.pyc b/doc/src/djangoblog/__pycache__/logentryadmin.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45ebe6f5361bdb06fa78efb14e954cd1672a9d3a GIT binary patch literal 2918 zcmZ`*Pj3@P6rY)0uh;9f<2VT+w5n*R)VdX^s6;^sP=U~@5}2w`p{^pWHlB&&WY>0Q zCeRq?fD(yQFZ~3ew|u3!wwIoJs|57DSv#?lvdKKZdGmH>-tYaJUZoNcc>a3%a`UiE z$e%cwd|a4(149jfaKdRyG8$2eJWE;TM2_X1)XfS}!SZhEWyPqN`H^qug|w6fQDAv5 zEoWgATE3W8vT9Vdyr0fwwP==-?+7pP;DGQz(5?Eu6V37RW6}u!hW$x{nnJ>}P8S$| z_5OqVPriTD1ipN)*VUq{pX~NUgPEcf&xBOMRGYmYbM|4PTbrO++UUuah?SNw@q}t6 z@CJypL~h3_*}zHI7Fvs6b(|)H-4^aU`8cDA`W+ZSBFag`IBnC&;fyk;jYNKOj+&pXUKDAJE7LF60&9O1#QvU@hQG1aVn}ytYq)tMFM~2YHpxiK>;) zfxH@5UXcyT=lQ~cAI(_)9A5;!_KLV9ngyhmOyxnZeZQ;a?j3~6gsmK$SOYqy+*{ug zEgehIm!{g07E>j&?v|mu-cS%l#p@S>2Avq=i`rB zNev_|24wsoPhs4H@ec+Rr;o`IYkqq~z*(*m=?1#&mmL9aS%7s*{$0IyW9=u{N3C7I za^>pvwN;_Eb+5m+x&>$M-A#M#%UPndkZasZ)=i4F5T7P%qp>w$)(nW^!>;ij7;#FZPyizu3F^WpcX_N<6u&Ajvajlwlib z(v}!W63sInl%*`urj+#iPd`ubhx*i|?48HFH`Rjggl|y-0m24L)8R4N9P`?E`Q-h+>B1Hovf+?v z^ohF<*|5iSL3{ZOJmw58=puN=AF{0yjKF%v-6~J`#s}Xx;2Q^gQyDrV--Hvs8A+cL zi4oqYN-Qd-n4eFX3KlK^G|mxpvF^zaK_z!a5jjL%ev0HXBn=>j^|#-HF~Tcnk$i#V zxJKFJvkP}XZVN)4Rw<*5OBWQ+k+)5Lr z;yAYy(^`H7jm@mu>`5JSxUuk?0s}o~exd))Hwt_MJt* zVqv|Aae+f&J@yG>?mJds)P2i5q`vF>ffIPX6m1TdM~YC%RGy<)^vuFotUIY%5#3gg zce-tfnQUgK);4vP+B7$dQ*yl8!R-Ry!U}56PpxX1bQL58f{Oi8-bJ0@Ay(i!c!{zA eOyvD>U>W~r9v8QxIb+K=p?b1ZV)X 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 literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/sitemap.cpython-310.pyc b/doc/src/djangoblog/__pycache__/sitemap.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da9a4ef4113d49a8f7f088cc239c952d5b49a4f1 GIT binary patch literal 2430 zcmb_dOK;mo5Z)yzlGejk5+`nh6n#I`Hfn>QtziUiS_kN)rL4pbY{T2iNeV$BaRWE;0*|xrm~celf{B9U=XT(u z>G zMS_+!JNLeUEN&~A`$thGJ1Gov`-iC0CZ)yCLg68f%lD8-7I47=OYp!JR^SL*I2SA^ zh=OqOADu?SetxZ)MR@p6vHW#vyMxO3{rNTSjbb%y^T~vv#M3y!p-%TQ& zNoWZ(L&vrB1dn=um=}oEB|Ptv0(JRlZ^iez+0&-=*+)DLFyZP@zd*rESC~?QNr^=^USrZm-&%ZEMuL+z-QEl*lm5i(#0gVxWjGhvAb! zq^2u=AL10^Y?v3@rx?FH0r>>T2wW#}1>1xpUtkM~hvSkKedch7`&QXD^J{AXC}hm@ z_Oym5$=^r)z)R9zcB+Oi?a%99uB4c7F@TXr8SC;c){(G&r&QaGocFO-U5%A9&@~$h zxGDG2U?lR*_(EV8P? zk|B`elVO;}i5yR}X`?$!)^m?GxIvqCiQFWzM}%CTB#Ca= z!to|uCPF&fWbifGLDI(5?96WUG&i~Xvy_k35N?z9^6i_|vZ(?}&2#I;3=qr(K?H9g z$?Zs~wMlpzcbIEb(hzeBM_t(M)AJOR)1%gJm%76wn;?`B}r?mJnl2LVT>wiM) zGdkO#b$#LN%|LyEdL|SD6iFL_Dp40+L<%Ud6{M(=Ob@t&OvW;vnI3S#GP~Ps0|3F2_v4{@$Nta69Wutc&{GFg( ze?a|K=|f~D`)h9Qs90yYttAqCLQPlt4@v!MX6X(eU0boV@|4Va(fk%*ASHqCa@bAd zo)l~E!vPwaWcbFSnawG_i;DM2*`%1HMy(R;OiO8y{C2TeiCP>V^3OzDSdibOs@jEX+Tv3WM?5gS635rG5&iOvRAn93tV8&=>%XrCA3bmp&D4v z>blMJ7`=j6_K*sP1E*Ky5<-T!;6?0m7g;N&-kl^|$WE@4qw@jQf4dkW+u1H|^$E>v zlf#nC$u%rM!-#5U#OBETjV`Emmz6mkRj&caN9?Xk7}4H&3&;XHo7FL1n>%In0OJei zi(_w<=oC&#;B*Z5*dODujsxGYQJ!SRlj3A5lvajKa-}83LQHe#)n2<7b>4m0dvg%= z-tU`Anh%FU8kS^7xnWwIX;XWBHV_k?*)E@ z-hI~ZDIryVdu!|IcE2suXPr;_?NOW!^Bqj_WE?9kWSHK6bZo~hco;bWFn44ZqUuB}0y q9hH-=4Tat>Mk^m!I?GbFTcr4Jiax#PWyLS7YBw1@S_hx5JO2P*F5Ke) literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/urls.cpython-310.pyc b/doc/src/djangoblog/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07098e4371c494b0df363ca9758f415b38fcde8f GIT binary patch literal 2572 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 IjPbnrzt)vK6951J literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc b/doc/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a91443791761ab8094c89f628058a94691e01973 GIT binary patch literal 19418 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 X`h9YW$;EO>S#G zz5$N>k~zYKU*Lo{5o$*ASmW`$_dH|V-Hl1G-?z8(uMr`?nqbq~A$S#*JqS*a2@#Q? zDw;%LZz)=}Cas7-v}k|Z5^b^dorb9Og-o^u9h2_%&CmGbcrauI>X|Ir3=I z?j-8zG~m4rL=_rfQU}gzm0+j?xU_606+C4YAf7u{+oLoUSlDDO?7yZ0s}ywFOL_PG%?2`+EW@(msSN&nsJ(cm;6pPZcyM}u+x;k1AL z{^Vxwt^$Z%7(sLj#xZfL=<2+Q*?#z>Hri=kITj z{vf8A=gR$k{^lq 0 + except Exception as e: + logger.error(f"失败邮箱号: {emailto}, {e}") + log.send_result = False + log.save() + + +@receiver(oauth_user_login_signal) +def oauth_user_login_signal_handler(sender, **kwargs): + #mj OAuth用户登录信号处理器 + id = kwargs['id'] + oauthuser = OAuthUser.objects.get(id=id) + site = get_current_site().domain + if oauthuser.picture and not oauthuser.picture.find(site) >= 0: + from djangoblog.utils import save_user_avatar + oauthuser.picture = save_user_avatar(oauthuser.picture) + oauthuser.save() + + delete_sidebar_cache() + + +@receiver(post_save) +def model_post_save_callback( + sender, + instance, + created, + raw, + using, + update_fields, + **kwargs): + #mj 模型保存后的回调函数 + clearcache = False + if isinstance(instance, LogEntry): + return + if 'get_full_url' in dir(instance): + is_update_views = update_fields == {'views'} + if not settings.TESTING and not is_update_views: + try: + notify_url = instance.get_full_url() + SpiderNotify.baidu_notify([notify_url]) + except Exception as ex: + logger.error("notify sipder", ex) + if not is_update_views: + clearcache = True + + if isinstance(instance, Comment): + #mj 处理评论保存逻辑 + if instance.is_enable: + path = instance.article.get_absolute_url() + site = get_current_site().domain + if site.find(':') > 0: + site = site[0:site.find(':')] + + expire_view_cache( + path, + servername=site, + serverport=80, + key_prefix='blogdetail') + if cache.get('seo_processor'): + cache.delete('seo_processor') + comment_cache_key = 'article_comments_{id}'.format( + id=instance.article.id) + cache.delete(comment_cache_key) + delete_sidebar_cache() + delete_view_cache('article_comments', [str(instance.article.pk)]) + + _thread.start_new_thread(send_comment_email, (instance,)) + + if clearcache: + cache.clear() + + +@receiver(user_logged_in) +@receiver(user_logged_out) +def user_auth_callback(sender, request, user, **kwargs): + #mj 用户登录/登出回调函数 + if user and user.username: + logger.info(user) + delete_sidebar_cache() + # cache.clear() +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/elasticsearch_backend.py b/doc/src/djangoblog/elasticsearch_backend.py new file mode 100644 index 0000000..e50fead --- /dev/null +++ b/doc/src/djangoblog/elasticsearch_backend.py @@ -0,0 +1,186 @@ +from django.utils.encoding import force_str +from elasticsearch_dsl import Q +from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query +from haystack.forms import ModelSearchForm +from haystack.models import SearchResult +from haystack.utils import log as logging + +from blog.documents import ArticleDocument, ArticleDocumentManager +from blog.models import Article + +logger = logging.getLogger(__name__) + + +class ElasticSearchBackend(BaseSearchBackend): + #mj ElasticSearch搜索后端实现 + def __init__(self, connection_alias, **connection_options): + super( + ElasticSearchBackend, + self).__init__( + connection_alias, + **connection_options) + self.manager = ArticleDocumentManager() + self.include_spelling = True + + def _get_models(self, iterable): + models = iterable if iterable and iterable[0] else Article.objects.all() + docs = self.manager.convert_to_doc(models) + return docs + + def _create(self, models): + self.manager.create_index() + docs = self._get_models(models) + self.manager.rebuild(docs) + + def _delete(self, models): + for m in models: + m.delete() + return True + + def _rebuild(self, models): + models = models if models else Article.objects.all() + docs = self.manager.convert_to_doc(models) + self.manager.update_docs(docs) + + def update(self, index, iterable, commit=True): + models = self._get_models(iterable) + self.manager.update_docs(models) + + def remove(self, obj_or_string): + models = self._get_models([obj_or_string]) + self._delete(models) + + def clear(self, models=None, commit=True): + self.remove(None) + + @staticmethod + def get_suggestion(query: str) -> str: + """获取推荐词, 如果没有找到添加原搜索词""" + search = ArticleDocument.search() \ + .query("match", body=query) \ + .suggest('suggest_search', query, term={'field': 'body'}) \ + .execute() + + keywords = [] + for suggest in search.suggest.suggest_search: + if suggest["options"]: + keywords.append(suggest["options"][0]["text"]) + else: + keywords.append(suggest["text"]) + + return ' '.join(keywords) + + @log_query + def search(self, query_string, **kwargs): + #mj 执行搜索 + logger.info('search query_string:' + query_string) + + start_offset = kwargs.get('start_offset') + end_offset = kwargs.get('end_offset') + + # 推荐词搜索 + if getattr(self, "is_suggest", None): + suggestion = self.get_suggestion(query_string) + else: + suggestion = query_string + + q = Q('bool', + should=[Q('match', body=suggestion), Q('match', title=suggestion)], + minimum_should_match="70%") + + search = ArticleDocument.search() \ + .query('bool', filter=[q]) \ + .filter('term', status='p') \ + .filter('term', type='a') \ + .source(False)[start_offset: end_offset] + + results = search.execute() + hits = results['hits'].total + raw_results = [] + for raw_result in results['hits']['hits']: + app_label = 'blog' + model_name = 'Article' + additional_fields = {} + + result_class = SearchResult + + result = result_class( + app_label, + model_name, + raw_result['_id'], + raw_result['_score'], + **additional_fields) + raw_results.append(result) + facets = {} + spelling_suggestion = None if query_string == suggestion else suggestion + + return { + 'results': raw_results, + 'hits': hits, + 'facets': facets, + 'spelling_suggestion': spelling_suggestion, + } + + +class ElasticSearchQuery(BaseSearchQuery): + #mj ElasticSearch查询类 + def _convert_datetime(self, date): + if hasattr(date, 'hour'): + return force_str(date.strftime('%Y%m%d%H%M%S')) + else: + return force_str(date.strftime('%Y%m%d000000')) + + def clean(self, query_fragment): + """ + Provides a mechanism for sanitizing user input before presenting the + value to the backend. + + Whoosh 1.X differs here in that you can no longer use a backslash + to escape reserved characters. Instead, the whole word should be + quoted. + """ + words = query_fragment.split() + cleaned_words = [] + + for word in words: + if word in self.backend.RESERVED_WORDS: + word = word.replace(word, word.lower()) + + for char in self.backend.RESERVED_CHARACTERS: + if char in word: + word = "'%s'" % word + break + + cleaned_words.append(word) + + return ' '.join(cleaned_words) + + def build_query_fragment(self, field, filter_type, value): + return value.query_string + + def get_count(self): + results = self.get_results() + return len(results) if results else 0 + + def get_spelling_suggestion(self, preferred_query=None): + return self._spelling_suggestion + + def build_params(self, spelling_query=None): + kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) + return kwargs + + +class ElasticSearchModelSearchForm(ModelSearchForm): + #mj ElasticSearch模型搜索表单 + def search(self): + # 是否建议搜索 + self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" + sqs = super().search() + return sqs + + +class ElasticSearchEngine(BaseEngine): + #mj ElasticSearch引擎 + backend = ElasticSearchBackend + query = ElasticSearchQuery +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/feeds.py b/doc/src/djangoblog/feeds.py new file mode 100644 index 0000000..7042638 --- /dev/null +++ b/doc/src/djangoblog/feeds.py @@ -0,0 +1,44 @@ +from django.contrib.auth import get_user_model +from django.contrib.syndication.views import Feed +from django.utils import timezone +from django.utils.feedgenerator import Rss201rev2Feed + +from blog.models import Article +from djangoblog.utils import CommonMarkdown + + +class DjangoBlogFeed(Feed): + #mj RSS订阅源 + feed_type = Rss201rev2Feed + + description = '大巧无工,重剑无锋.' + title = "且听风吟 大巧无工,重剑无锋. " + link = "/feed/" + + def author_name(self): + return get_user_model().objects.first().nickname + + def author_link(self): + return get_user_model().objects.first().get_absolute_url() + + def items(self): + #mj 返回最新的5篇文章 + return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] + + def item_title(self, item): + return item.title + + def item_description(self, item): + #mj 将Markdown内容转换为HTML + return CommonMarkdown.get_markdown(item.body) + + def feed_copyright(self): + now = timezone.now() + return "Copyright© {year} 且听风吟".format(year=now.year) + + def item_link(self, item): + return item.get_absolute_url() + + def item_guid(self, item): + return +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/logentryadmin.py b/doc/src/djangoblog/logentryadmin.py new file mode 100644 index 0000000..b1c26a3 --- /dev/null +++ b/doc/src/djangoblog/logentryadmin.py @@ -0,0 +1,95 @@ +from django.contrib import admin +from django.contrib.admin.models import DELETION +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse, NoReverseMatch +from django.utils.encoding import force_str +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + + +class LogEntryAdmin(admin.ModelAdmin): + #mj 日志条目管理 + list_filter = [ + 'content_type' + ] + + search_fields = [ + 'object_repr', + 'change_message' + ] + + list_display_links = [ + 'action_time', + 'get_change_message', + ] + list_display = [ + 'action_time', + 'user_link', + 'content_type', + 'object_link', + 'get_change_message', + ] + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return ( + request.user.is_superuser or + request.user.has_perm('admin.change_logentry') + ) and request.method != 'POST' + + def has_delete_permission(self, request, obj=None): + return False + + def object_link(self, obj): + #mj 生成对象链接 + object_link = escape(obj.object_repr) + content_type = obj.content_type + + if obj.action_flag != DELETION and content_type is not None: + # try returning an actual link instead of object repr string + try: + url = reverse( + 'admin:{}_{}_change'.format(content_type.app_label, + content_type.model), + args=[obj.object_id] + ) + object_link = '{}'.format(url, object_link) + except NoReverseMatch: + pass + return mark_safe(object_link) + + object_link.admin_order_field = 'object_repr' + object_link.short_description = _('object') + + def user_link(self, obj): + #mj 生成用户链接 + content_type = ContentType.objects.get_for_model(type(obj.user)) + user_link = escape(force_str(obj.user)) + try: + # try returning an actual link instead of object repr string + url = reverse( + 'admin:{}_{}_change'.format(content_type.app_label, + content_type.model), + args=[obj.user.pk] + ) + user_link = '{}'.format(url, user_link) + except NoReverseMatch: + pass + return mark_safe(user_link) + + user_link.admin_order_field = 'user' + user_link.short_description = _('user') + + def get_queryset(self, request): + queryset = super(LogEntryAdmin, self).get_queryset(request) + return queryset.prefetch_related('content_type') + + def get_actions(self, request): + actions = super(LogEntryAdmin, self).get_actions(request) + if 'delete_selected' in actions: + del actions['delete_selected'] + return actions +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc b/doc/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7922de6d10a917bee5bc774a9694f9561121cb64 GIT binary patch literal 1546 zcmaJ=-D})N5MO=VoqYaCa8g71q=!EApf$}yz?c%!*d`d#tFaS590#iGT`Nj-lCzQu zJ^E1BcG?g-PE+F2G%hwYw5Hfe3FHIQ$oaFdl2oyRaomIt7yJANBF*`fIncsNb z-DLu!H8*!@QYGXiF7iWx#TgL)Jy4WT$ssku$hySj4XLJ_BUGl!0-?&hTvMquK`QDV zSd!{A>im#laR$U60L4g6qNFBM@BsohYlYNPtw>c`SRl0$Ez%PFHLB4L_;)1U%c6YN z^x2pbTDDuAmcZBkz&Su5Lm>V~paSw0xh#RmAd02TOMXuPPt8l;!0visgGgYpUc^5o z5V~A#j;EgW1`AAT2B!Z0*!hd+Myp2klTSxJ(A$gQk%^)4(Xk7opH;KH$&v91SXcB$ z=m+`~(a@k_KOw)I9cYIr{gl{<|!F zNm z(aM9?&i%b_m!oSx#aq9|tIy&`cjC4C(e^L9Hy`d^e->@u-MzbzIf;K*ZvAyHbJFbA z5tVH^b`$DUttg@hsjwIqs_o8r9NEONKa#wrsvDP>fnE?FN!?dy_T`e0E7YMpAVm{; zqUVIqa`-t+vykFPzem@WQtW%THvwjL^#734iy7ntcdKdaZ-deS&z}HEps0oBi5!$S zX?6%r)@+yxXtAu_sB}-kb_y@#vAw$aBwATbv9(@o#B&caY=fKyA$JG$XlXIJ^+&t* zi}UfFhw<_@mT>FGXz^)g50z3~v%y3sW&Sj`X9L@FMd=IRJWMJ|TDbze&Wott0$d!i z*nUF+xo12b3=?XrMO9Lz;@{18bE!CBTq$EuUmH?gjBU%OLRbN=M&oUci@O0 cL4`&+x5+E$TWBArv7SaQ`uK`Q%5tyr4_5BA!~g&Q literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc b/doc/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bfb5350480b91f88173c66462bea1fd7c33ad33 GIT binary patch literal 379 zcmY+A!AiqG5QdX9m7=tlUi1-qQ0PG@B4SJyEXG9I^s+20Yj#LtH`$Qg96a`MdYcfxHAAc=Zw-aezXUpzX!qd>kbxO6X%8Khem0Mjuw=n5%Z_XX)RN zm4Zf**>rk6BLS$jmNg09Se3~KA+yVpDGd$bJLT0Vq!w*fRJ3Fj%YfukuDkV>W|dY~ Z_4{^kTT0#vxa;1ix=*{7Yj-|yegPgDZEOGl literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc b/doc/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e4aa870bdf14c8fcd16c61d739116ea427a9a10 GIT binary patch literal 1585 zcmaKs@oOAK6vtFE^>!$iHhtma@-FVD-=oz+L}~~NQAT^hLdA+Gudpi*}cy0 zHO;YCt~S&rL@fn>urZP=6^&>O_)Dl&|BwB(cbDW}NPo~bvv=v)gbvJ`doy!y-p=Pc z&(CDi2wLmLjhXuzLQjO#-v~J8pa&PhP(-ndCJ{p$m?=#>sXAzqP=#q!WvOXG3DuU! z2!-e>=DH1Ighr$thIAs_#2AIjhDvdS)(}-9JWF6y*EO+12^_0c--Hlp6I$nw zpWC-qTicD+_Ah&PH`~A6X|MgTyLIzP;|sZ7%T6%c2|~shHAR+YzLw3K3r;v=_A)i| z`#3*d(Bf1{>m~V#rBXrR;)A#fW)pF7^ZOr;o-KY7 zFdh`ooH}*-OmU0_bD>`;j?G%0?Y|F^-kP^W9z`l=reLO6ajUlDmF6wavRP3cezLL{ z>%5z@4v_<4bfV)S{08QO;FK|#UIW?UkK-O9|%omw$D>-Rgy@9h<0np5xi3m7I|P z=R*9o*Di4GbCVynV}CH;uLd=L*1WulW1U?qmmF`}AKy%IanW38mr^ zFL_E7egc;M_n=ZRae#dloG$jU7hr#d4V#4@ z$(ke8l*mm8FIIz)C$-g@a+xdxQWP>oUkaiYaj7nSWZtJ$m%S^hMIh>ij?xNIa_Uez GH}oHR8O09( literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc b/doc/src/djangoblog/plugin_manage/__pycache__/loader.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53ffeb042a387ef4d940fbef17497cfecbd791ee GIT binary patch literal 967 zcmYjQPiqrF6rY*hB%41iwR%wSFnB3B7^onWBBC}`qP2)^Er!Ul-I;7AZg$g|32l}{ zTJ-8CkV1kdztUdy;#cTJ-|VKP1M}wnnfLp>_hvU@C=82127gZlX`Gr%02&HM7AkOwgQ6ab_1Mc#h@UOfTg(CYa$lG2UcVJB&=w z55%ks>np}cmuD4b8_xyYz~~SiTQ<_w@eW2)f>E-f%l#Sy26JOVSn)e}579@&N>lQM z99#K$f=?imm7pUh`B|Z`n&ZeD2;U3CF%4snDe6Uxib!bTg(BgK4#QC(B1OA09#Gxm zbZy~TqfAKd>sXE(8Pg$ksM}F$NBI?Gw^(9H*#nR>HTl=jK+wQxagIBHHy_Cc%wTGVWrFK6SQCeLD+-B>2 zS`Z4FPmBB%%2sK`b#n^WO^ab11YD-JK~Jl#jCxzjSf&MTj=I&Z=0&0W1#(p3+=)ST@Ha@N^cq126Id-Z2l# zB@8dE8P(0Bss!-rE{IBD1wX(9ui!g)8B^?>k$>dOzLd8iSa)Orj 1 and sys.argv[1] == 'test' + +# ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] +# django 4.0新增配置 +CSRF_TRUSTED_ORIGINS = ['http://example.com'] +# Application definition + + +INSTALLED_APPS = [ + # 'django.contrib.admin', + 'django.contrib.admin.apps.SimpleAdminConfig', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.sites', + 'django.contrib.sitemaps', + 'mdeditor', + 'haystack', + 'blog', + 'accounts', + 'comments', + 'oauth', + 'servermanager', + 'owntracks', + 'compressor', + 'djangoblog' +] + +MIDDLEWARE = [ + + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.gzip.GZipMiddleware', + # 'django.middleware.cache.UpdateCacheMiddleware', + 'django.middleware.common.CommonMiddleware', + # 'django.middleware.cache.FetchFromCacheMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.http.ConditionalGetMiddleware', + 'blog.middleware.OnlineMiddleware' +] + +ROOT_URLCONF = 'djangoblog.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'blog.context_processors.seo_processor' + ], + }, + }, +] + +WSGI_APPLICATION = 'djangoblog.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'djangoblog', + 'USER': 'root', + 'PASSWORD': '123456', + 'HOST': '127.0.0.1', + 'PORT': 3306, + } +} + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +LANGUAGES = ( + ('en', _('English')), + ('zh-hans', _('Simplified Chinese')), + ('zh-hant', _('Traditional Chinese')), +) +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale'), +) + +LANGUAGE_CODE = 'zh-hans' + +TIME_ZONE = 'Asia/Shanghai' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = False + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + + +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', + 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), + }, +} +# Automatically update searching index +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' +# Allow user login with username and password +AUTHENTICATION_BACKENDS = [ + 'accounts.user_login_backend.EmailOrUsernameModelBackend'] + +STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') + +STATIC_URL = '/static/' +STATICFILES = os.path.join(BASE_DIR, 'static') + +AUTH_USER_MODEL = 'accounts.BlogUser' +LOGIN_URL = '/login/' + +TIME_FORMAT = '%Y-%m-%d %H:%M:%S' +DATE_TIME_FORMAT = '%Y-%m-%d' + +# bootstrap color styles +BOOTSTRAP_COLOR_TYPES = [ + 'default', 'primary', 'success', 'info', 'warning', 'danger' +] + +# paginate +PAGINATE_BY = 10 +# http cache timeout +CACHE_CONTROL_MAX_AGE = 2592000 +# cache setting +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'TIMEOUT': 10800, + 'LOCATION': 'unique-snowflake', + } +} +# 使用redis作为缓存 +if os.environ.get("DJANGO_REDIS_URL"): + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.redis.RedisCache', + 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', + } + } + +SITE_ID = 1 +BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ + or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' + +# Email: +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) +EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) +EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' +EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) +EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') +EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') +DEFAULT_FROM_EMAIL = EMAIL_HOST_USER +SERVER_EMAIL = EMAIL_HOST_USER +# Setting debug=false did NOT handle except email notifications +ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] +# WX ADMIN password(Two times md5) +WXADMIN = os.environ.get( + 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' + +LOG_PATH = os.path.join(BASE_DIR, 'logs') +if not os.path.exists(LOG_PATH): + os.makedirs(LOG_PATH, exist_ok=True) + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'root': { + 'level': 'INFO', + 'handlers': ['console', 'log_file'], + }, + 'formatters': { + 'verbose': { + 'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s', + } + }, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + }, + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', + }, + }, + 'handlers': { + 'log_file': { + 'level': 'INFO', + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': os.path.join(LOG_PATH, 'djangoblog.log'), + 'when': 'D', + 'formatter': 'verbose', + 'interval': 1, + 'delay': True, + 'backupCount': 5, + 'encoding': 'utf-8' + }, + 'console': { + 'level': 'DEBUG', + 'filters': ['require_debug_true'], + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + }, + 'null': { + 'class': 'logging.NullHandler', + }, + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'djangoblog': { + 'handlers': ['log_file', 'console'], + 'level': 'INFO', + 'propagate': True, + }, + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': False, + } + } +} + +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # other + 'compressor.finders.CompressorFinder', +) +COMPRESS_ENABLED = True +# COMPRESS_OFFLINE = True + + +COMPRESS_CSS_FILTERS = [ + # creates absolute urls from relative ones + 'compressor.filters.css_default.CssAbsoluteFilter', + # css minimizer + 'compressor.filters.cssmin.CSSMinFilter' +] +COMPRESS_JS_FILTERS = [ + 'compressor.filters.jsmin.JSMinFilter' +] + +MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') +MEDIA_URL = '/media/' +X_FRAME_OPTIONS = 'SAMEORIGIN' + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): + ELASTICSEARCH_DSL = { + 'default': { + 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST') + }, + } + HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', + }, + } + +# Plugin System +PLUGINS_DIR = BASE_DIR / 'plugins' +ACTIVE_PLUGINS = [ + 'article_copyright', + 'reading_time', + 'external_links', + 'view_count', + 'seo_optimizer' +] +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/sitemap.py b/doc/src/djangoblog/sitemap.py new file mode 100644 index 0000000..1ba81ea --- /dev/null +++ b/doc/src/djangoblog/sitemap.py @@ -0,0 +1,67 @@ +f# [file name]: sitemap.py +# [file content begin] +from django.contrib.sitemaps import Sitemap +from django.urls import reverse + +from blog.models import Article, Category, Tag + + +class StaticViewSitemap(Sitemap): + #mj 静态视图站点地图 + priority = 0.5 + changefreq = 'daily' + + def items(self): + return ['blog:index', ] + + def location(self, item): + return reverse(item) + + +class ArticleSiteMap(Sitemap): + #mj 文章站点地图 + changefreq = "monthly" + priority = "0.6" + + def items(self): + return Article.objects.filter(status='p') + + def lastmod(self, obj): + return obj.last_modify_time + + +class CategorySiteMap(Sitemap): + #mj 分类站点地图 + changefreq = "Weekly" + priority = "0.6" + + def items(self): + return Category.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class TagSiteMap(Sitemap): + #mj 标签站点地图 + changefreq = "Weekly" + priority = "0.3" + + def items(self): + return Tag.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class UserSiteMap(Sitemap): + #mj 用户站点地图 + changefreq = "Weekly" + priority = "0.3" + + def items(self): + return list(set(map(lambda x: x.author, Article.objects.all()))) + + def lastmod(self, obj): + return obj.date_joined +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/spider_notify.py b/doc/src/djangoblog/spider_notify.py new file mode 100644 index 0000000..2df82ab --- /dev/null +++ b/doc/src/djangoblog/spider_notify.py @@ -0,0 +1,19 @@ +# [file name]: spider_notify.py +# [file content begin] +import logging + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class SpiderNotify(): + #mj 搜索引擎爬虫通 + @staticmethod + def baidu_notify(urls): + try: + data = '\n'.join(urls) + result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) + logger.info(result.text) + except Exception as e: \ No newline at end of file diff --git a/doc/src/djangoblog/tests.py b/doc/src/djangoblog/tests.py new file mode 100644 index 0000000..cc95f06 --- /dev/null +++ b/doc/src/djangoblog/tests.py @@ -0,0 +1,36 @@ + +from django.test import TestCase + +from djangoblog.utils import * + + +class DjangoBlogTest(TestCase): + #mj Django博客测试类 + def setUp(self): + pass + + def test_utils(self): + #mj 测试工具函数 + md5 = get_sha256('test') + self.assertIsNotNone(md5) + c = CommonMarkdown.get_markdown(''' + # Title1 + + ```python + import os + ``` + + [url](https://www.lylinux.net/) + + [ddd](http://www.baidu.com) + + + ''') + self.assertIsNotNone(c) + d = { + 'd': 'key1', + 'd2': 'key2' + } + data = parse_dict_to_url(d) + self.assertIsNotNone(data) +# [file content end] diff --git a/doc/src/djangoblog/urls.py b/doc/src/djangoblog/urls.py new file mode 100644 index 0000000..317c176 --- /dev/null +++ b/doc/src/djangoblog/urls.py @@ -0,0 +1,54 @@ +from django.conf import settings +from django.conf.urls.i18n import i18n_patterns +from django.conf.urls.static import static +from django.contrib.sitemaps.views import sitemap +from django.urls import path, include +from django.urls import re_path +from haystack.views import search_view_factory + +from blog.views import EsSearchView +from djangoblog.admin_site import admin_site +from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm +from djangoblog.feeds import DjangoBlogFeed +from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap + +#mj 站点地图配置 +sitemaps = { + + 'blog': ArticleSiteMap, + 'Category': CategorySiteMap, + 'Tag': TagSiteMap, + 'User': UserSiteMap, + 'static': StaticViewSitemap +} + +#mj 错误处理视图 +handler404 = 'blog.views.page_not_found_view' +handler500 = 'blog.views.server_error_view' +handle403 = 'blog.views.permission_denied_view' + +urlpatterns = [ + path('i18n/', include('django.conf.urls.i18n')), +] +#mj 国际化URL模式 +urlpatterns += i18n_patterns( + re_path(r'^admin/', admin_site.urls), + re_path(r'', include('blog.urls', namespace='blog')), + re_path(r'mdeditor/', include('mdeditor.urls')), + re_path(r'', include('comments.urls', namespace='comment')), + re_path(r'', include('accounts.urls', namespace='account')), + re_path(r'', include('oauth.urls', namespace='oauth')), + re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, + name='django.contrib.sitemaps.views.sitemap'), + re_path(r'^feed/$', DjangoBlogFeed()), + re_path(r'^rss/$', DjangoBlogFeed()), + re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), + name='search'), + re_path(r'', include('servermanager.urls', namespace='servermanager')), + re_path(r'', include('owntracks.urls', namespace='owntracks')) + , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +#mj 调试模式下添加媒体文件服务 +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT) +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/utils.py b/doc/src/djangoblog/utils.py new file mode 100644 index 0000000..9913268 --- /dev/null +++ b/doc/src/djangoblog/utils.py @@ -0,0 +1,245 @@ +import logging +import os +import random +import string +import uuid +from hashlib import sha256 + +import bleach +import markdown +import requests +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.cache import cache +from django.templatetags.static import static + +logger = logging.getLogger(__name__) + + +def get_max_articleid_commentid(): + #mj 获取最大文章ID和评论ID + from blog.models import Article + from comments.models import Comment + return (Article.objects.latest().pk, Comment.objects.latest().pk) + + +def get_sha256(str): + #mj 计算字符串的SHA256哈希值 + m = sha256(str.encode('utf-8')) + return m.hexdigest() + + +def cache_decorator(expiration=3 * 60): + #mj 缓存装饰器 + def wrapper(func): + def news(*args, **kwargs): + try: + view = args[0] + key = view.get_cache_key() + except: + key = None + if not key: + unique_str = repr((func, args, kwargs)) + + m = sha256(unique_str.encode('utf-8')) + key = m.hexdigest() + value = cache.get(key) + if value is not None: + # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key)) + if str(value) == '__default_cache_value__': + return None + else: + return value + else: + logger.debug( + 'cache_decorator set cache:%s key:%s' % + (func.__name__, key)) + value = func(*args, **kwargs) + if value is None: + cache.set(key, '__default_cache_value__', expiration) + else: + cache.set(key, value, expiration) + return value + + return news + + return wrapper + + +def expire_view_cache(path, servername, serverport, key_prefix=None): + ''' + 刷新视图缓存 + :param path:url路径 + :param servername:host + :param serverport:端口 + :param key_prefix:前缀 + :return:是否成功 + ''' + from django.http import HttpRequest + from django.utils.cache import get_cache_key + + request = HttpRequest() + request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport} + request.path = path + + key = get_cache_key(request, key_prefix=key_prefix, cache=cache) + if key: + logger.info('expire_view_cache:get key:{path}'.format(path=path)) + if cache.get(key): + cache.delete(key) + return True + return False + + +@cache_decorator() +def get_current_site(): + #mj 获取当前站点(带缓存) + site = Site.objects.get_current() + return site + + +class CommonMarkdown: + #mj Markdown处理工具类 + @staticmethod + def _convert_markdown(value): + md = markdown.Markdown( + extensions=[ + 'extra', + 'codehilite', + 'toc', + 'tables', + ] + ) + body = md.convert(value) + toc = md.toc + return body, toc + + @staticmethod + def get_markdown_with_toc(value): + #mj 获取带目录的Markdown + body, toc = CommonMarkdown._convert_markdown(value) + return body, toc + + @staticmethod + def get_markdown(value): + #mj 获取Markdown内容 + body, toc = CommonMarkdown._convert_markdown(value) + return body + + +def send_email(emailto, title, content): + #mj 发送邮件 + from djangoblog.blog_signals import send_email_signal + send_email_signal.send( + send_email.__class__, + emailto=emailto, + title=title, + content=content) + + +def generate_code() -> str: + """生成随机数验证码""" + return ''.join(random.sample(string.digits, 6)) + + +def parse_dict_to_url(dict): + #mj 将字典转换为URL查询字符串 + from urllib.parse import quote + url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) + for k, v in dict.items()]) + return url + + +def get_blog_setting(): + #mj 获取博客设置(带缓存) + value = cache.get('get_blog_setting') + if value: + return value + else: + from blog.models import BlogSettings + if not BlogSettings.objects.count(): + #mj 如果不存在设置,创建默认设置 + setting = BlogSettings() + setting.site_name = 'djangoblog' + setting.site_description = '基于Django的博客系统' + setting.site_seo_description = '基于Django的博客系统' + setting.site_keywords = 'Django,Python' + setting.article_sub_length = 300 + setting.sidebar_article_count = 10 + setting.sidebar_comment_count = 5 + setting.show_google_adsense = False + setting.open_site_comment = True + setting.analytics_code = '' + setting.beian_code = '' + setting.show_gongan_code = False + setting.comment_need_review = False + setting.save() + value = BlogSettings.objects.first() + logger.info('set cache get_blog_setting') + cache.set('get_blog_setting', value) + return value + + +def save_user_avatar(url): + ''' + 保存用户头像 + :param url:头像url + :return: 本地路径 + ''' + logger.info(url) + + try: + basedir = os.path.join(settings.STATICFILES, 'avatar') + rsp = requests.get(url, timeout=2) + if rsp.status_code == 200: + if not os.path.exists(basedir): + os.makedirs(basedir) + + image_extensions = ['.jpg', '.png', 'jpeg', '.gif'] + isimage = len([i for i in image_extensions if url.endswith(i)]) > 0 + ext = os.path.splitext(url)[1] if isimage else '.jpg' + save_filename = str(uuid.uuid4().hex) + ext + logger.info('保存用户头像:' + basedir + save_filename) + with open(os.path.join(basedir, save_filename), 'wb+') as file: + file.write(rsp.content) + return static('avatar/' + save_filename) + except Exception as e: + logger.error(e) + return static('blog/img/avatar.png') + + +def delete_sidebar_cache(): + #mj 删除侧边栏缓存 + from blog.models import LinkShowType + keys = ["sidebar" + x for x in LinkShowType.values] + for k in keys: + logger.info('delete sidebar key:' + k) + cache.delete(k) + + +def delete_view_cache(prefix, keys): + #mj 删除视图缓存 + from django.core.cache.utils import make_template_fragment_key + key = make_template_fragment_key(prefix, keys) + cache.delete(key) + + +def get_resource_url(): + #mj 获取资源URL + if settings.STATIC_URL: + return settings.STATIC_URL + else: + site = get_current_site() + return 'http://' + site.domain + '/static/' + + +#mj 允许的HTML标签和属性(用于HTML清理) +ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', + 'h2', 'p'] +ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} + + +def sanitize_html(html): + #mj 清理HTML,防止XSS攻击 + return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) +# [file content end] \ No newline at end of file diff --git a/doc/src/djangoblog/whoosh_cn_backend.py b/doc/src/djangoblog/whoosh_cn_backend.py new file mode 100644 index 0000000..da132cf --- /dev/null +++ b/doc/src/djangoblog/whoosh_cn_backend.py @@ -0,0 +1,712 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +import json +import os +import re +import shutil +import threading +import warnings + +import six +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from datetime import datetime +from django.utils.encoding import force_str +from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, EmptyResults, log_query +from haystack.constants import DJANGO_CT, DJANGO_ID, ID +from haystack.exceptions import MissingDependency, SearchBackendError, SkipDocument +from haystack.inputs import Clean, Exact, PythonData, Raw +from haystack.models import SearchResult +from haystack.utils import get_identifier, get_model_ct +from haystack.utils import log as logging +from haystack.utils.app_loading import haystack_get_model +from jieba.analyse import ChineseAnalyzer +from whoosh import index +from whoosh.analysis import StemmingAnalyzer +from whoosh.fields import BOOLEAN, DATETIME, IDLIST, KEYWORD, NGRAM, NGRAMWORDS, NUMERIC, Schema, TEXT +from whoosh.fields import ID as WHOOSH_ID +from whoosh.filedb.filestore import FileStorage, RamStorage +from whoosh.highlight import ContextFragmenter, HtmlFormatter +from whoosh.highlight import highlight as whoosh_highlight +from whoosh.qparser import QueryParser +from whoosh.searching import ResultsPage +from whoosh.writing import AsyncWriter + +try: + import whoosh +except ImportError: + raise MissingDependency( + "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") + +# Handle minimum requirement. +if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): + raise MissingDependency( + "The 'whoosh' backend requires version 2.5.0 or greater.") + +# Bubble up the correct error. + +DATETIME_REGEX = re.compile( + '^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$') +LOCALS = threading.local() +LOCALS.RAM_STORE = None + + +class WhooshHtmlFormatter(HtmlFormatter): + """ + This is a HtmlFormatter simpler than the whoosh.HtmlFormatter. + We use it to have consistent results across backends. Specifically, + Solr, Xapian and Elasticsearch are using this formatting. + """ + template = '<%(tag)s>%(t)s' + + +class WhooshSearchBackend(BaseSearchBackend): + #mj Whoosh搜索后端 + # Word reserved by Whoosh for special use. + RESERVED_WORDS = ( + 'AND', + 'NOT', + 'OR', + 'TO', + ) + + # Characters reserved by Whoosh for special use. + # The '\\' must come first, so as not to overwrite the other slash + # replacements. + RESERVED_CHARACTERS = ( + '\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', + '[', ']', '^', '"', '~', '*', '?', ':', '.', + ) + + def __init__(self, connection_alias, **connection_options): + super( + WhooshSearchBackend, + self).__init__( + connection_alias, + **connection_options) + self.setup_complete = False + self.use_file_storage = True + self.post_limit = getattr( + connection_options, + 'POST_LIMIT', + 128 * 1024 * 1024) + self.path = connection_options.get('PATH') + + if connection_options.get('STORAGE', 'file') != 'file': + self.use_file_storage = False + + if self.use_file_storage and not self.path: + raise ImproperlyConfigured( + "You must specify a 'PATH' in your settings for connection '%s'." % + connection_alias) + + self.log = logging.getLogger('haystack') + + def setup(self): + """ + Defers loading until needed. + """ + from haystack import connections + new_index = False + + # Make sure the index is there. + if self.use_file_storage and not os.path.exists(self.path): + os.makedirs(self.path) + new_index = True + + if self.use_file_storage and not os.access(self.path, os.W_OK): + raise IOError( + "The path to your Whoosh index '%s' is not writable for the current user/group." % + self.path) + + if self.use_file_storage: + self.storage = FileStorage(self.path) + else: + global LOCALS + + if getattr(LOCALS, 'RAM_STORE', None) is None: + LOCALS.RAM_STORE = RamStorage() + + self.storage = LOCALS.RAM_STORE + + self.content_field_name, self.schema = self.build_schema( + connections[self.connection_alias].get_unified_index().all_searchfields()) + self.parser = QueryParser(self.content_field_name, schema=self.schema) + + if new_index is True: + self.index = self.storage.create_index(self.schema) + else: + try: + self.index = self.storage.open_index(schema=self.schema) + except index.EmptyIndexError: + self.index = self.storage.create_index(self.schema) + + self.setup_complete = True + + def build_schema(self, fields): + #mj 构建Whoosh索引模式 + schema_fields = { + ID: WHOOSH_ID(stored=True, unique=True), + DJANGO_CT: WHOOSH_ID(stored=True), + DJANGO_ID: WHOOSH_ID(stored=True), + } + # Grab the number of keys that are hard-coded into Haystack. + # We'll use this to (possibly) fail slightly more gracefully later. + initial_key_count = len(schema_fields) + content_field_name = '' + + for field_name, field_class in fields.items(): + if field_class.is_multivalued: + if field_class.indexed is False: + schema_fields[field_class.index_fieldname] = IDLIST( + stored=True, field_boost=field_class.boost) + else: + schema_fields[field_class.index_fieldname] = KEYWORD( + stored=True, commas=True, scorable=True, field_boost=field_class.boost) + elif field_class.field_type in ['date', 'datetime']: + schema_fields[field_class.index_fieldname] = DATETIME( + stored=field_class.stored, sortable=True) + elif field_class.field_type == 'integer': + schema_fields[field_class.index_fieldname] = NUMERIC( + stored=field_class.stored, numtype=int, field_boost=field_class.boost) + elif field_class.field_type == 'float': + schema_fields[field_class.index_fieldname] = NUMERIC( + stored=field_class.stored, numtype=float, field_boost=field_class.boost) + elif field_class.field_type == 'boolean': + # Field boost isn't supported on BOOLEAN as of 1.8.2. + schema_fields[field_class.index_fieldname] = BOOLEAN( + stored=field_class.stored) + elif field_class.field_type == 'ngram': + schema_fields[field_class.index_fieldname] = NGRAM( + minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost) + elif field_class.field_type == 'edge_ngram': + schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start', + stored=field_class.stored, + field_boost=field_class.boost) + else: + # schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True) + #mj 使用中文分析器处理文本字段 + schema_fields[field_class.index_fieldname] = TEXT( + stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True) + if field_class.document is True: + content_field_name = field_class.index_fieldname + schema_fields[field_class.index_fieldname].spelling = True + + # Fail more gracefully than relying on the backend to die if no fields + # are found. + if len(schema_fields) <= initial_key_count: + raise SearchBackendError( + "No fields were found in any search_indexes. Please correct this before attempting to search.") + + return (content_field_name, Schema(**schema_fields)) + + def update(self, index, iterable, commit=True): + #mj 更新索引 + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + writer = AsyncWriter(self.index) + + for obj in iterable: + try: + doc = index.full_prepare(obj) + except SkipDocument: + self.log.debug(u"Indexing for object `%s` skipped", obj) + else: + # Really make sure it's unicode, because Whoosh won't have it any + # other way. + for key in doc: + doc[key] = self._from_python(doc[key]) + + # Document boosts aren't supported in Whoosh 2.5.0+. + if 'boost' in doc: + del doc['boost'] + + try: + writer.update_document(**doc) + except Exception as e: + if not self.silently_fail: + raise + + # We'll log the object identifier but won't include the actual object + # to avoid the possibility of that generating encoding errors while + # processing the log message: + self.log.error( + u"%s while preparing object for update" % + e.__class__.__name__, + exc_info=True, + extra={ + "data": { + "index": index, + "object": get_identifier(obj)}}) + + if len(iterable) > 0: + # For now, commit no matter what, as we run into locking issues + # otherwise. + writer.commit() + + def remove(self, obj_or_string, commit=True): + #mj 从索引中移除文档 + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + whoosh_id = get_identifier(obj_or_string) + + try: + self.index.delete_by_query( + q=self.parser.parse( + u'%s:"%s"' % + (ID, whoosh_id))) + except Exception as e: + if not self.silently_fail: + raise + + self.log.error( + "Failed to remove document '%s' from Whoosh: %s", + whoosh_id, + e, + exc_info=True) + + def clear(self, models=None, commit=True): + #mj 清空索引 + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + + if models is not None: + assert isinstance(models, (list, tuple)) + + try: + if models is None: + self.delete_index() + else: + models_to_delete = [] + + for model in models: + models_to_delete.append( + u"%s:%s" % + (DJANGO_CT, get_model_ct(model))) + + self.index.delete_by_query( + q=self.parser.parse( + u" OR ".join(models_to_delete))) + except Exception as e: + if not self.silently_fail: + raise + + if models is not None: + self.log.error( + "Failed to clear Whoosh index of models '%s': %s", + ','.join(models_to_delete), + e, + exc_info=True) + else: + self.log.error( + "Failed to clear Whoosh index: %s", e, exc_info=True) + + def delete_index(self): + #mj 删除索引文件 + # Per the Whoosh mailing list, if wiping out everything from the index, + # it's much more efficient to simply delete the index files. + if self.use_file_storage and os.path.exists(self.path): + shutil.rmtree(self.path) + elif not self.use_file_storage: + self.storage.clean() + + # Recreate everything. + self.setup() + + def optimize(self): + #mj 优化索引 + if not self.setup_complete: + self.setup() + + self.index = self.index.refresh() + self.index.optimize() + + def calculate_page(self, start_offset=0, end_offset=None): + #mj 计算分页参数 + # Prevent against Whoosh throwing an error. Requires an end_offset + # greater than 0. + if end_offset is not None and end_offset <= 0: + end_offset = 1 + + # Determine the page. + page_num = 0 + + if end_offset is None: + end_offset = 1000000 + + if start_offset is None: + start_offset = 0 + + page_length = end_offset - start_offset + + if page_length and page_length > 0: + page_num = int(start_offset / page_length) + + # Increment because Whoosh uses 1-based page numbers. + page_num += 1 + return page_num, page_length + + @log_query + def search( + self, + query_string, + sort_by=None, + start_offset=0, + end_offset=None, + fields='', + highlight=False, + facets=None, + date_facets=None, + query_facets=None, + narrow_queries=None, + spelling_query=None, + within=None, + dwithin=None, + distance_point=None, + models=None, + limit_to_registered_models=None, + result_class=None, + **kwargs): + #mj 执行搜索 + if not self.setup_complete: + self.setup() + + # A zero length query should return no results. + if len(query_string) == 0: + return { + 'results': [], + 'hits': 0, + } + + query_string = force_str(query_string) + + # A one-character query (non-wildcard) gets nabbed by a stopwords + # filter and should yield zero results. + if len(query_string) <= 1 and query_string != u'*': + return { + 'results': [], + 'hits': 0, + } + + reverse = False + + if sort_by is not None: + # Determine if we need to reverse the results and if Whoosh can + # handle what it's being asked to sort by. Reversing is an + # all-or-nothing action, unfortunately. + sort_by_list = [] + reverse_counter = 0 + + for order_by in sort_by: + if order_by.startswith('-'): + reverse_counter += 1 + + if reverse_counter and reverse_counter != len(sort_by): + raise SearchBackendError("Whoosh requires all order_by fields" + " to use the same sort direction") + + for order_by in sort_by: + if order_by.startswith('-'): + sort_by_list.append(order_by[1:]) + + if len(sort_by_list) == 1: + reverse = True + else: + sort_by_list.append(order_by) + + if len(sort_by_list) == 1: + reverse = False + + sort_by = sort_by_list[0] + + if facets is not None: + warnings.warn( + "Whoosh does not handle faceting.", + Warning, + stacklevel=2) + + if date_facets is not None: + warnings.warn( + "Whoosh does not handle date faceting.", + Warning, + stacklevel=2) + + if query_facets is not None: + warnings.warn( + "Whoosh does not handle query faceting.", + Warning, + stacklevel=2) + + narrowed_results = None + self.index = self.index.refresh() + + if limit_to_registered_models is None: + limit_to_registered_models = getattr( + settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + + if models and len(models): + model_choices = sorted(get_model_ct(model) for model in models) + elif limit_to_registered_models: + # Using narrow queries, limit the results to only models handled + # with the current routers. + model_choices = self.build_models_list() + else: + model_choices = [] + + if len(model_choices) > 0: + if narrow_queries is None: + narrow_queries = set() + + narrow_queries.add(' OR '.join( + ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + + narrow_searcher = None + + if narrow_queries is not None: + # Potentially expensive? I don't see another way to do it in + # Whoosh... + narrow_searcher = self.index.searcher() + + for nq in narrow_queries: + recent_narrowed_results = narrow_searcher.search( + self.parser.parse(force_str(nq)), limit=None) + + if len(recent_narrowed_results) <= 0: + return { + 'results': [], + 'hits': 0, + } + + if narrowed_results: + narrowed_results.filter(recent_narrowed_results) + else: + narrowed_results = recent_narrowed_results + + self.index = self.index.refresh() + + if self.index.doc_count(): + searcher = self.index.searcher() + parsed_query = self.parser.parse(query_string) + + # In the event of an invalid/stopworded query, recover gracefully. + if parsed_query is None: + return { + 'results': [], + 'hits': 0, + } + + page_num, page_length = self.calculate_page( + start_offset, end_offset) + + search_kwargs = { + 'pagelen': page_length, + 'sortedby': sort_by, + 'reverse': reverse, + } + + # Handle the case where the results have been narrowed. + if narrowed_results is not None: + search_kwargs['filter'] = narrowed_results + + try: + raw_page = searcher.search_page( + parsed_query, + page_num, + **search_kwargs + ) + except ValueError: + if not self.silently_fail: + raise + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + # Because as of Whoosh 2.5.1, it will return the wrong page of + # results if you request something too high. :( + if raw_page.pagenum < page_num: + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + results = self._process_results( + raw_page, + highlight=highlight, + query_string=query_string, + spelling_query=spelling_query, + result_class=result_class) + searcher.close() + + if hasattr(narrow_searcher, 'close'): + narrow_searcher.close() + + return results + else: + if self.include_spelling: + if spelling_query: + spelling_suggestion = self.create_spelling_suggestion( + spelling_query) + else: + spelling_suggestion = self.create_spelling_suggestion( + query_string) + else: + spelling_suggestion = None + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': spelling_suggestion, + } + + def more_like_this( + self, + model_instance, + additional_query_string=None, + start_offset=0, + end_offset=None, + models=None, + limit_to_registered_models=None, + result_class=None, + **kwargs): + #mj 查找相似文档 + if not self.setup_complete: + self.setup() + + # Deferred models will have a different class ("RealClass_Deferred_fieldname") + # which won't be in our registry: + model_klass = model_instance._meta.concrete_model + + field_name = self.content_field_name + narrow_queries = set() + narrowed_results = None + self.index = self.index.refresh() + + if limit_to_registered_models is None: + limit_to_registered_models = getattr( + settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + + if models and len(models): + model_choices = sorted(get_model_ct(model) for model in models) + elif limit_to_registered_models: + # Using narrow queries, limit the results to only models handled + # with the current routers. + model_choices = self.build_models_list() + else: + model_choices = [] + + if len(model_choices) > 0: + if narrow_queries is None: + narrow_queries = set() + + narrow_queries.add(' OR '.join( + ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + + if additional_query_string and additional_query_string != '*': + narrow_queries.add(additional_query_string) + + narrow_searcher = None + + if narrow_queries is not None: + # Potentially expensive? I don't see another way to do it in + # Whoosh... + narrow_searcher = self.index.searcher() + + for nq in narrow_queries: + recent_narrowed_results = narrow_searcher.search( + self.parser.parse(force_str(nq)), limit=None) + + if len(recent_narrowed_results) <= 0: + return { + 'results': [], + 'hits': 0, + } + + if narrowed_results: + narrowed_results.filter(recent_narrowed_results) + else: + narrowed_results = recent_narrowed_results + + page_num, page_length = self.calculate_page(start_offset, end_offset) + + self.index = self.index.refresh() + raw_results = EmptyResults() + + if self.index.doc_count(): + query = "%s:%s" % (ID, get_identifier(model_instance)) + searcher = self.index.searcher() + parsed_query = self.parser.parse(query) + results = searcher.search(parsed_query) + + if len(results): + raw_results = results[0].more_like_this( + field_name, top=end_offset) + + # Handle the case where the results have been narrowed. + if narrowed_results is not None and hasattr(raw_results, 'filter'): + raw_results.filter(narrowed_results) + + try: + raw_page = ResultsPage(raw_results, page_num, page_length) + except ValueError: + if not self.silently_fail: + raise + + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + # Because as of Whoosh 2.5.1, it will return the wrong page of + # results if you request something too high. :( + if raw_page.pagenum < page_num: + return { + 'results': [], + 'hits': 0, + 'spelling_suggestion': None, + } + + results = self._process_results(raw_page, result_class=result_class) + searcher.close() + + if hasattr(narrow_searcher, 'close'): + narrow_searcher.close() + + return results + + def _process_results( + self, + raw_page, + highlight=False, + query_string='', + spelling_query=None, + result_class=None): + #mj 处理搜索结果 + from haystack import connections + results = [] + + # It's important to grab the hits first before slicing. Otherwise, this + # can cause pagination failures. + hits = len(raw_page) + + if result_class is None: + result_class = SearchResult + + facets = {} + spelling_suggestion = None + unified_index = connections[self.connection_alias].get_unified_index() + indexed_models = unified_index.get_indexed_models() + + for doc_offset, raw_result in enumerate(raw_page): + score = raw_page.score(doc_offset) or 0 + app_label, model_name = raw_result[DJANGO_CT]. \ No newline at end of file diff --git a/doc/src/djangoblog/whoosh_index/MAIN_WRITELOCK b/doc/src/djangoblog/whoosh_index/MAIN_WRITELOCK new file mode 100644 index 0000000..e69de29 diff --git a/doc/src/djangoblog/whoosh_index/MAIN_dn7zl97zuwmzfmle.seg b/doc/src/djangoblog/whoosh_index/MAIN_dn7zl97zuwmzfmle.seg new file mode 100644 index 0000000000000000000000000000000000000000..dc47ef31207c1e6d38af76751d70c4d570514649 GIT binary patch literal 6327 zcmeHL4@?_X7=L$H*zspF1_2tAg$|5hO6AWIAR1SQ3|Q@0M1*XS^5+UI94&vY0WmXm ziy6r-uEH^4VpLRQC@^#k%_LDbv1E)t%Fr-p$ks4pqB7J3MK|BwyTcoHT{6gI%9q@G zzxUqvd*Ao{?rWe0fVWU909ZzDLMnaz`t=Dr9;L%{AbtCsa0qmS4(I?=1vGW}10545!R*ChjNBF;}bVO=+E$Vl^;Yl5N zL&I0r7TE2s1=k3lBwq$B0@%NO=;%jtDQ@81~J(v;WO zTAJtl=1J$~C!EuJoKyMEo(&+93Yr#st4)e1`-m-%ICncu-0$cfbUYORCX_*2#8|6S z-6wmKJXg2%RM5HAsxGnI{YO-)ZgeEbZ`EQMJjWWl92C4-u?DoBV~wkVLM-NDV>n}G zXk&O>vhQsFq3?Q|uW+5S4FkTyQL)v+7%dDud>}O=J0n4CSz)>dju8M9(biH;VKrke zrj1~`T{yykgoLGRNQ77cp9p}(B}-23-Fa932^8FMc^u8HxE7I%LJ_xee+#-7!N*4k zMTk{2a=a0Tm&-H3EE0HP1Rw;}Ju}XV3=%QW>+wjzV{W9Ncj=jOSfn7W6i_QI z+<}IXq*eg(0lx}Y)$l=DDVA$Rl41|YNJKGQ(C|T8DFm&w$iZz8VhkSgK|}(t)lw1e zA}CUzGams-rC27FTT1thlx0%6qj)Jr3Y?Hlh4%6~@U{_6Xsk$7J|wPf$6{QC2xGx= zA36Sh@gUzG(6CxGqtQ8f5&pBp8?J@JlFbHR5ck23%s0V4bbwu4a##l5`17lyXbZ2h zC)e@)4h>5~Ga6N8kGzi%eqO#W$KBul^trh1yK<`WYti3d?(^IJ$r*7i&BBsCRQ3bf z$GXJxe@s7WUjGGc;_YZA_%|xr9l8S#31e_9i;cT zTlOAT6HE7-WcS|==Kq{SBU;eBX0cT7aao@0SP+9)sSc91PMNlM8cR9M$Z}YVSjuxj zmgj#Ni}flt{ax=}i=|qkWVNg&mhRWe?q4fd$}LZp+e)!?XQk}UJC5yM&HOhy-u7`( zELmbU(o{uZCC9IPsIo#!snt--a5}KGGEKvUBTX9^V;%G%SOo$K8|!1X zP;m3>U7O%qFg)c_U{O{6T zG1Pd&3{_B&F}!m`H+DEn6HT!**nCs<{FlFOKD0BwspfINR!1|{5geZ$i#3Ps33<=1 z)CNwG&u-J#Yt1Lgkk?F^WoenJXX4Lz4}Q@&(pEHh>CB1AOO5R#A2B@z;R#`%s$C7^ zc$xF;W4m&qhSkHFIk6{P^(XrmKl}aI*xT>cP4&Ivr*3Y0HMj9-F3zc4bd0M^?b4hl zz_n}JhdNS8s9+NOZADU0sZYt=WuPpH^>lJ#y{)#QzNCUSYAS0OK~isPNlA?nYUR&* zLorGj6Q_UPizO?plo@JIap@`3@%|fIWB1$GE$*-4CDs&MY5qPFp?}fKq|m8NUWnMT zIAmjQk^@s+A#w&GNAD9Tky^J*+E|tQXoecbT5Y6c0f$0hz)p$V;<{{OBW}XwQ#HUc TWo%0*WMgu+O=I`k$7%ioZ37B4 literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/whoosh_index/MAIN_qqs5xlu2z40nrzsk.seg b/doc/src/djangoblog/whoosh_index/MAIN_qqs5xlu2z40nrzsk.seg new file mode 100644 index 0000000000000000000000000000000000000000..659b0324ab447f0195b2df1a3150d7d0801fb459 GIT binary patch literal 18102 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 PHt;vmE}8vVw%z{#O0dGn literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/whoosh_index/MAIN_qstselg7s7e0fhg2.seg b/doc/src/djangoblog/whoosh_index/MAIN_qstselg7s7e0fhg2.seg new file mode 100644 index 0000000000000000000000000000000000000000..264aaea6b463ae83d981465b920f79560eecf0ef GIT binary patch literal 6327 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 literal 0 HcmV?d00001 diff --git a/doc/src/djangoblog/wsgi.py b/doc/src/djangoblog/wsgi.py new file mode 100644 index 0000000..2295efd --- /dev/null +++ b/doc/src/djangoblog/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for djangoblog project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") + +application = get_wsgi_application()