From 0ef2d99dbc15e133ec5bac4c274a168709bad0b1 Mon Sep 17 00:00:00 2001 From: smy <2723863608@qq.com> Date: Sun, 28 Sep 2025 16:45:21 +0800 Subject: [PATCH 01/24] =?UTF-8?q?=E5=88=A0=E9=99=A4README.md=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7ecca17..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SoftwareMethodology - From 4201412bb40835d58c6b203f2b69274a8d9fbdff Mon Sep 17 00:00:00 2001 From: smy <2723863608@qq.com> Date: Sun, 28 Sep 2025 16:50:52 +0800 Subject: [PATCH 02/24] =?UTF-8?q?=E5=9C=A8master=E5=88=86=E6=94=AF?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0doc=E7=9B=AE=E5=BD=95=E5=8F=8AREADME.md?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/README.md diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..80ce0ff --- /dev/null +++ b/doc/README.md @@ -0,0 +1 @@ +这是doc目录的说明文档 From 2570a1b03040e84dc5f0ccc936569c48febb4373 Mon Sep 17 00:00:00 2001 From: smy <2723863608@qq.com> Date: Mon, 29 Sep 2025 11:07:09 +0800 Subject: [PATCH 03/24] =?UTF-8?q?=E5=88=A0=E9=99=A4master=E5=88=86?= =?UTF-8?q?=E6=94=AF=E4=B8=AD=E7=9A=84README.md=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 7ecca17..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SoftwareMethodology - From edbc1f8c1532c6d775ff791485c250e7eceb71b4 Mon Sep 17 00:00:00 2001 From: smy <2723863608@qq.com> Date: Mon, 29 Sep 2025 11:08:47 +0800 Subject: [PATCH 04/24] =?UTF-8?q?=E6=B7=BB=E5=8A=A0doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/README.md diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..80ce0ff --- /dev/null +++ b/doc/README.md @@ -0,0 +1 @@ +这是doc目录的说明文档 From 42884c5b0dde930e5e5ea82199cb05934380e68a Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 18:27:48 +0800 Subject: [PATCH 05/24] ADD file via upload --- ...价软件界面设计说明书模板.docx | Bin 0 -> 390441 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/电影评价软件界面设计说明书模板.docx diff --git a/doc/电影评价软件界面设计说明书模板.docx b/doc/电影评价软件界面设计说明书模板.docx new file mode 100644 index 0000000000000000000000000000000000000000..3c9baa08708029ab30a18c2c8e60b28ecef755d4 GIT binary patch literal 390441 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
  • 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 z

    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

    O zS}exG%z3t+quRCL(kt}LVtI`W0Qwz41fqy~p_PHRZ`qmd7cISI4Mi3@Ke;a}R5>fX z4qi96Hgb{ua-oQc2v|j|%+?K$jg_DK&fqZ?$JbM&5vIeO10evX{+75#%fHKIeXe?Z zZw&OG>-jgl0(S+{91rHoFIWHm8Dq%sD7_ZZu44T)j7H0GM7<9j{j3(IP2xd|5-@@D z3!_UA+`DRFHTvK0Wd$nGwYOy9gpABt|9}8DR}NYK3Zh>EtntdPw(}PuH2_2eW!K{_ zTi_Z^i132m^tLk3!l+|$Nd`y;QIcK(_-=SAcg$4ivLTpt5rCBry}Ohx1SpJ}A9=Hd z#1je~4Efkm13$)EQeplYl*EDeu1$qwH~a)Lv;0*1br z^m?9bgDLZ-pCMhft0-m~=5_p7`{*n=U?b&H%~RrsOUAxKnx_m`llBfo`r44caF5); zK=wKCeLvaK(!ZcMhsOGzX9%ls3nLf=+MGZqPQfag3^Kp-^RG{oQs|J<)OkEMme_$} z{ucrZH^6{B5(zNtWe2sS#6*V1zt@AJfz5m6x5WiqY@`M0 zMdKe79}iI^LLZ&bv?XNn1A?-CA`Cc+Vl$m>Y~=c2RiHx4%WTh#p6iF^<`2glZb4EP zE-buZhWc`Vu;}%tAs2o`wFl8n#SB&yC(+ zf+XijkxPID+OwixV=?_TYISw^Du78xgK8!xAIsB#b7zZK!kMfn$*q)0v2_lKC8~M$ z|01%&-YA@Q!vZ*GYoT)mgkKK1!d_A(x|1KyGnF;1uVqCGUL^;H5 zl6iZ7&mouF>$Kyn{qZ_PExcE@x%UFm>+7=kd7Lly)AIfXHWpsrkD#;pT+H=f7A2Y? zdIbev<)BOoB!oE-V2hmS@J-)6G(7d|&(z6c$H9LwiJ?T5$PqX+nJ3Q*Ft} zDhGI_lJGo+;~(xOODr^r1Jv{s$OTCwJi=jvTKApQSIqftI

    +8FPPh_rFK8xNBLjOs$!4k$2nA8ypP)HKT=rZ7p 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%0D1yP)GDnIfcdhK~5U^-pKs2Q3BO$Yp%G1Og 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 literal 0 HcmV?d00001 From 64cf96ce50045cd006eb15b63baf881be1691219 Mon Sep 17 00:00:00 2001 From: smy <2723863608@qq.com> Date: Sun, 12 Oct 2025 19:36:57 +0800 Subject: [PATCH 06/24] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=BA=90=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DjangoBlog-master/.coveragerc | 10 + src/DjangoBlog-master/.dockerignore | 11 + src/DjangoBlog-master/.gitattributes | 6 + .../.github/ISSUE_TEMPLATE.md | 18 + .../.github/workflows/codeql-analysis.yml | 47 + .../.github/workflows/django.yml | 136 +++ .../.github/workflows/docker.yml | 43 + .../.github/workflows/publish-release.yml | 39 + src/DjangoBlog-master/.gitignore | 80 ++ src/DjangoBlog-master/Dockerfile | 15 + src/DjangoBlog-master/LICENSE | 20 + src/DjangoBlog-master/README.md | 158 +++ src/DjangoBlog-master/accounts/__init__.py | 0 src/DjangoBlog-master/accounts/admin.py | 59 + src/DjangoBlog-master/accounts/apps.py | 5 + src/DjangoBlog-master/accounts/forms.py | 117 ++ .../accounts/migrations/0001_initial.py | 49 + ...s_remove_bloguser_created_time_and_more.py | 46 + .../accounts/migrations/__init__.py | 0 src/DjangoBlog-master/accounts/models.py | 35 + .../accounts/templatetags/__init__.py | 0 src/DjangoBlog-master/accounts/tests.py | 207 ++++ src/DjangoBlog-master/accounts/urls.py | 28 + .../accounts/user_login_backend.py | 26 + src/DjangoBlog-master/accounts/utils.py | 49 + src/DjangoBlog-master/accounts/views.py | 204 ++++ src/DjangoBlog-master/blog/__init__.py | 0 src/DjangoBlog-master/blog/admin.py | 112 ++ src/DjangoBlog-master/blog/apps.py | 5 + .../blog/context_processors.py | 43 + src/DjangoBlog-master/blog/documents.py | 213 ++++ src/DjangoBlog-master/blog/forms.py | 19 + .../blog/management/__init__.py | 0 .../blog/management/commands/__init__.py | 0 .../blog/management/commands/build_index.py | 18 + .../management/commands/build_search_words.py | 13 + .../blog/management/commands/clear_cache.py | 11 + .../management/commands/create_testdata.py | 40 + .../blog/management/commands/ping_baidu.py | 50 + .../management/commands/sync_user_avatar.py | 47 + src/DjangoBlog-master/blog/middleware.py | 42 + .../blog/migrations/0001_initial.py | 137 +++ ...002_blogsettings_global_footer_and_more.py | 23 + .../0003_blogsettings_comment_need_review.py | 17 + ...de_blogsettings_analytics_code_and_more.py | 27 + ...options_alter_category_options_and_more.py | 300 +++++ .../0006_alter_blogsettings_options.py | 17 + .../blog/migrations/__init__.py | 0 src/DjangoBlog-master/blog/models.py | 376 ++++++ src/DjangoBlog-master/blog/search_indexes.py | 13 + .../blog/templatetags/__init__.py | 0 .../blog/templatetags/blog_tags.py | 344 ++++++ src/DjangoBlog-master/blog/tests.py | 232 ++++ src/DjangoBlog-master/blog/urls.py | 62 + src/DjangoBlog-master/blog/views.py | 379 ++++++ src/DjangoBlog-master/comments/__init__.py | 0 src/DjangoBlog-master/comments/admin.py | 47 + src/DjangoBlog-master/comments/apps.py | 5 + src/DjangoBlog-master/comments/forms.py | 13 + .../comments/migrations/0001_initial.py | 38 + .../0002_alter_comment_is_enable.py | 18 + ...ns_remove_comment_created_time_and_more.py | 60 + .../comments/migrations/__init__.py | 0 src/DjangoBlog-master/comments/models.py | 39 + .../comments/templatetags/__init__.py | 0 .../comments/templatetags/comments_tags.py | 30 + src/DjangoBlog-master/comments/tests.py | 109 ++ src/DjangoBlog-master/comments/urls.py | 11 + src/DjangoBlog-master/comments/utils.py | 38 + src/DjangoBlog-master/comments/views.py | 63 + .../docker-compose/docker-compose.es.yml | 48 + .../deploy/docker-compose/docker-compose.yml | 60 + src/DjangoBlog-master/deploy/entrypoint.sh | 31 + .../deploy/k8s/configmap.yaml | 119 ++ .../deploy/k8s/deployment.yaml | 274 +++++ src/DjangoBlog-master/deploy/k8s/gateway.yaml | 17 + src/DjangoBlog-master/deploy/k8s/pv.yaml | 94 ++ src/DjangoBlog-master/deploy/k8s/pvc.yaml | 60 + src/DjangoBlog-master/deploy/k8s/service.yaml | 80 ++ .../deploy/k8s/storageclass.yaml | 10 + src/DjangoBlog-master/deploy/nginx.conf | 50 + src/DjangoBlog-master/djangoblog/__init__.py | 1 + .../djangoblog/admin_site.py | 64 + src/DjangoBlog-master/djangoblog/apps.py | 11 + .../djangoblog/blog_signals.py | 122 ++ .../djangoblog/elasticsearch_backend.py | 183 +++ src/DjangoBlog-master/djangoblog/feeds.py | 40 + .../djangoblog/logentryadmin.py | 91 ++ .../djangoblog/plugin_manage/base_plugin.py | 41 + .../plugin_manage/hook_constants.py | 7 + .../djangoblog/plugin_manage/hooks.py | 44 + .../djangoblog/plugin_manage/loader.py | 19 + src/DjangoBlog-master/djangoblog/settings.py | 343 ++++++ src/DjangoBlog-master/djangoblog/sitemap.py | 59 + .../djangoblog/spider_notify.py | 21 + src/DjangoBlog-master/djangoblog/tests.py | 32 + src/DjangoBlog-master/djangoblog/urls.py | 64 + src/DjangoBlog-master/djangoblog/utils.py | 232 ++++ .../djangoblog/whoosh_cn_backend.py | 1044 +++++++++++++++++ src/DjangoBlog-master/djangoblog/wsgi.py | 16 + src/DjangoBlog-master/docs/README-en.md | 158 +++ src/DjangoBlog-master/docs/config-en.md | 64 + src/DjangoBlog-master/docs/config.md | 58 + src/DjangoBlog-master/docs/docker-en.md | 114 ++ src/DjangoBlog-master/docs/docker.md | 114 ++ src/DjangoBlog-master/docs/es.md | 28 + src/DjangoBlog-master/docs/imgs/alipay.jpg | Bin 0 -> 17961 bytes .../docs/imgs/pycharm_logo.png | Bin 0 -> 132045 bytes src/DjangoBlog-master/docs/imgs/wechat.jpg | Bin 0 -> 24722 bytes src/DjangoBlog-master/docs/k8s-en.md | 141 +++ src/DjangoBlog-master/docs/k8s.md | 141 +++ .../locale/en/LC_MESSAGES/django.mo | Bin 0 -> 11097 bytes .../locale/en/LC_MESSAGES/django.po | 685 +++++++++++ .../locale/zh_Hans/LC_MESSAGES/django.mo | Bin 0 -> 10321 bytes .../locale/zh_Hans/LC_MESSAGES/django.po | 667 +++++++++++ .../locale/zh_Hant/LC_MESSAGES/django.mo | Bin 0 -> 10268 bytes .../locale/zh_Hant/LC_MESSAGES/django.po | 668 +++++++++++ src/DjangoBlog-master/manage.py | 22 + src/DjangoBlog-master/oauth/__init__.py | 0 src/DjangoBlog-master/oauth/admin.py | 54 + src/DjangoBlog-master/oauth/apps.py | 5 + src/DjangoBlog-master/oauth/forms.py | 12 + .../oauth/migrations/0001_initial.py | 57 + ...ptions_alter_oauthuser_options_and_more.py | 86 ++ .../0003_alter_oauthuser_nickname.py | 18 + .../oauth/migrations/__init__.py | 0 src/DjangoBlog-master/oauth/models.py | 67 ++ src/DjangoBlog-master/oauth/oauthmanager.py | 504 ++++++++ .../oauth/templatetags/__init__.py | 1 + .../oauth/templatetags/oauth_tags.py | 22 + src/DjangoBlog-master/oauth/tests.py | 249 ++++ src/DjangoBlog-master/oauth/urls.py | 25 + src/DjangoBlog-master/oauth/views.py | 253 ++++ src/DjangoBlog-master/owntracks/__init__.py | 0 src/DjangoBlog-master/owntracks/admin.py | 7 + src/DjangoBlog-master/owntracks/apps.py | 5 + .../owntracks/migrations/0001_initial.py | 31 + ...0002_alter_owntracklog_options_and_more.py | 22 + .../owntracks/migrations/__init__.py | 0 src/DjangoBlog-master/owntracks/models.py | 20 + src/DjangoBlog-master/owntracks/tests.py | 64 + src/DjangoBlog-master/owntracks/urls.py | 12 + src/DjangoBlog-master/owntracks/views.py | 127 ++ src/DjangoBlog-master/plugins/__init__.py | 1 + .../plugins/article_copyright/__init__.py | 1 + .../plugins/article_copyright/plugin.py | 32 + .../plugins/external_links/__init__.py | 1 + .../plugins/external_links/plugin.py | 48 + .../plugins/reading_time/__init__.py | 1 + .../plugins/reading_time/plugin.py | 43 + .../plugins/seo_optimizer/__init__.py | 1 + .../plugins/seo_optimizer/plugin.py | 142 +++ .../plugins/view_count/__init__.py | 1 + .../plugins/view_count/plugin.py | 18 + src/DjangoBlog-master/requirements.txt | Bin 0 -> 2554 bytes .../servermanager/MemcacheStorage.py | 32 + .../servermanager/__init__.py | 0 src/DjangoBlog-master/servermanager/admin.py | 19 + .../servermanager/api/__init__.py | 1 + .../servermanager/api/blogapi.py | 27 + .../servermanager/api/commonapi.py | 64 + src/DjangoBlog-master/servermanager/apps.py | 5 + .../servermanager/migrations/0001_initial.py | 45 + ...002_alter_emailsendlog_options_and_more.py | 32 + .../servermanager/migrations/__init__.py | 0 src/DjangoBlog-master/servermanager/models.py | 33 + src/DjangoBlog-master/servermanager/robot.py | 187 +++ src/DjangoBlog-master/servermanager/tests.py | 79 ++ src/DjangoBlog-master/servermanager/urls.py | 10 + src/DjangoBlog-master/servermanager/views.py | 1 + .../templates/account/forget_password.html | 30 + .../templates/account/login.html | 46 + .../templates/account/registration_form.html | 29 + .../templates/account/result.html | 27 + .../templates/blog/article_archives.html | 60 + .../templates/blog/article_detail.html | 52 + .../templates/blog/article_index.html | 42 + .../templates/blog/error_page.html | 45 + .../templates/blog/links_list.html | 44 + .../templates/blog/tags/article_info.html | 74 ++ .../blog/tags/article_meta_info.html | 59 + .../blog/tags/article_pagination.html | 17 + .../templates/blog/tags/article_tag_list.html | 19 + .../templates/blog/tags/breadcrumb.html | 19 + .../templates/blog/tags/sidebar.html | 136 +++ .../templates/comments/tags/comment_item.html | 34 + .../comments/tags/comment_item_tree.html | 54 + .../templates/comments/tags/comment_list.html | 45 + .../templates/comments/tags/post_comment.html | 33 + .../templates/oauth/bindsuccess.html | 22 + .../templates/oauth/oauth_applications.html | 13 + .../templates/oauth/require_email.html | 46 + .../templates/owntracks/show_log_dates.html | 17 + .../templates/owntracks/show_maps.html | 135 +++ .../search/indexes/blog/article_text.txt | 3 + .../templates/search/search.html | 66 ++ .../templates/share_layout/adsense.html | 6 + .../templates/share_layout/base.html | 123 ++ .../templates/share_layout/base_account.html | 47 + .../templates/share_layout/footer.html | 56 + .../templates/share_layout/nav.html | 30 + .../templates/share_layout/nav_node.html | 19 + 202 files changed, 14469 insertions(+) create mode 100644 src/DjangoBlog-master/.coveragerc create mode 100644 src/DjangoBlog-master/.dockerignore create mode 100644 src/DjangoBlog-master/.gitattributes create mode 100644 src/DjangoBlog-master/.github/ISSUE_TEMPLATE.md create mode 100644 src/DjangoBlog-master/.github/workflows/codeql-analysis.yml create mode 100644 src/DjangoBlog-master/.github/workflows/django.yml create mode 100644 src/DjangoBlog-master/.github/workflows/docker.yml create mode 100644 src/DjangoBlog-master/.github/workflows/publish-release.yml create mode 100644 src/DjangoBlog-master/.gitignore create mode 100644 src/DjangoBlog-master/Dockerfile create mode 100644 src/DjangoBlog-master/LICENSE create mode 100644 src/DjangoBlog-master/README.md create mode 100644 src/DjangoBlog-master/accounts/__init__.py create mode 100644 src/DjangoBlog-master/accounts/admin.py create mode 100644 src/DjangoBlog-master/accounts/apps.py create mode 100644 src/DjangoBlog-master/accounts/forms.py create mode 100644 src/DjangoBlog-master/accounts/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py create mode 100644 src/DjangoBlog-master/accounts/migrations/__init__.py create mode 100644 src/DjangoBlog-master/accounts/models.py create mode 100644 src/DjangoBlog-master/accounts/templatetags/__init__.py create mode 100644 src/DjangoBlog-master/accounts/tests.py create mode 100644 src/DjangoBlog-master/accounts/urls.py create mode 100644 src/DjangoBlog-master/accounts/user_login_backend.py create mode 100644 src/DjangoBlog-master/accounts/utils.py create mode 100644 src/DjangoBlog-master/accounts/views.py create mode 100644 src/DjangoBlog-master/blog/__init__.py create mode 100644 src/DjangoBlog-master/blog/admin.py create mode 100644 src/DjangoBlog-master/blog/apps.py create mode 100644 src/DjangoBlog-master/blog/context_processors.py create mode 100644 src/DjangoBlog-master/blog/documents.py create mode 100644 src/DjangoBlog-master/blog/forms.py create mode 100644 src/DjangoBlog-master/blog/management/__init__.py create mode 100644 src/DjangoBlog-master/blog/management/commands/__init__.py create mode 100644 src/DjangoBlog-master/blog/management/commands/build_index.py create mode 100644 src/DjangoBlog-master/blog/management/commands/build_search_words.py create mode 100644 src/DjangoBlog-master/blog/management/commands/clear_cache.py create mode 100644 src/DjangoBlog-master/blog/management/commands/create_testdata.py create mode 100644 src/DjangoBlog-master/blog/management/commands/ping_baidu.py create mode 100644 src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py create mode 100644 src/DjangoBlog-master/blog/middleware.py create mode 100644 src/DjangoBlog-master/blog/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py create mode 100644 src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py create mode 100644 src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py create mode 100644 src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py create mode 100644 src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py create mode 100644 src/DjangoBlog-master/blog/migrations/__init__.py create mode 100644 src/DjangoBlog-master/blog/models.py create mode 100644 src/DjangoBlog-master/blog/search_indexes.py create mode 100644 src/DjangoBlog-master/blog/templatetags/__init__.py create mode 100644 src/DjangoBlog-master/blog/templatetags/blog_tags.py create mode 100644 src/DjangoBlog-master/blog/tests.py create mode 100644 src/DjangoBlog-master/blog/urls.py create mode 100644 src/DjangoBlog-master/blog/views.py create mode 100644 src/DjangoBlog-master/comments/__init__.py create mode 100644 src/DjangoBlog-master/comments/admin.py create mode 100644 src/DjangoBlog-master/comments/apps.py create mode 100644 src/DjangoBlog-master/comments/forms.py create mode 100644 src/DjangoBlog-master/comments/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py create mode 100644 src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py create mode 100644 src/DjangoBlog-master/comments/migrations/__init__.py create mode 100644 src/DjangoBlog-master/comments/models.py create mode 100644 src/DjangoBlog-master/comments/templatetags/__init__.py create mode 100644 src/DjangoBlog-master/comments/templatetags/comments_tags.py create mode 100644 src/DjangoBlog-master/comments/tests.py create mode 100644 src/DjangoBlog-master/comments/urls.py create mode 100644 src/DjangoBlog-master/comments/utils.py create mode 100644 src/DjangoBlog-master/comments/views.py create mode 100644 src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml create mode 100644 src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml create mode 100644 src/DjangoBlog-master/deploy/entrypoint.sh create mode 100644 src/DjangoBlog-master/deploy/k8s/configmap.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/deployment.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/gateway.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/pv.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/pvc.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/service.yaml create mode 100644 src/DjangoBlog-master/deploy/k8s/storageclass.yaml create mode 100644 src/DjangoBlog-master/deploy/nginx.conf create mode 100644 src/DjangoBlog-master/djangoblog/__init__.py create mode 100644 src/DjangoBlog-master/djangoblog/admin_site.py create mode 100644 src/DjangoBlog-master/djangoblog/apps.py create mode 100644 src/DjangoBlog-master/djangoblog/blog_signals.py create mode 100644 src/DjangoBlog-master/djangoblog/elasticsearch_backend.py create mode 100644 src/DjangoBlog-master/djangoblog/feeds.py create mode 100644 src/DjangoBlog-master/djangoblog/logentryadmin.py create mode 100644 src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py create mode 100644 src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py create mode 100644 src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py create mode 100644 src/DjangoBlog-master/djangoblog/plugin_manage/loader.py create mode 100644 src/DjangoBlog-master/djangoblog/settings.py create mode 100644 src/DjangoBlog-master/djangoblog/sitemap.py create mode 100644 src/DjangoBlog-master/djangoblog/spider_notify.py create mode 100644 src/DjangoBlog-master/djangoblog/tests.py create mode 100644 src/DjangoBlog-master/djangoblog/urls.py create mode 100644 src/DjangoBlog-master/djangoblog/utils.py create mode 100644 src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py create mode 100644 src/DjangoBlog-master/djangoblog/wsgi.py create mode 100644 src/DjangoBlog-master/docs/README-en.md create mode 100644 src/DjangoBlog-master/docs/config-en.md create mode 100644 src/DjangoBlog-master/docs/config.md create mode 100644 src/DjangoBlog-master/docs/docker-en.md create mode 100644 src/DjangoBlog-master/docs/docker.md create mode 100644 src/DjangoBlog-master/docs/es.md create mode 100644 src/DjangoBlog-master/docs/imgs/alipay.jpg create mode 100644 src/DjangoBlog-master/docs/imgs/pycharm_logo.png create mode 100644 src/DjangoBlog-master/docs/imgs/wechat.jpg create mode 100644 src/DjangoBlog-master/docs/k8s-en.md create mode 100644 src/DjangoBlog-master/docs/k8s.md create mode 100644 src/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo create mode 100644 src/DjangoBlog-master/locale/en/LC_MESSAGES/django.po create mode 100644 src/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo create mode 100644 src/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po create mode 100644 src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo create mode 100644 src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po create mode 100644 src/DjangoBlog-master/manage.py create mode 100644 src/DjangoBlog-master/oauth/__init__.py create mode 100644 src/DjangoBlog-master/oauth/admin.py create mode 100644 src/DjangoBlog-master/oauth/apps.py create mode 100644 src/DjangoBlog-master/oauth/forms.py create mode 100644 src/DjangoBlog-master/oauth/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py create mode 100644 src/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py create mode 100644 src/DjangoBlog-master/oauth/migrations/__init__.py create mode 100644 src/DjangoBlog-master/oauth/models.py create mode 100644 src/DjangoBlog-master/oauth/oauthmanager.py create mode 100644 src/DjangoBlog-master/oauth/templatetags/__init__.py create mode 100644 src/DjangoBlog-master/oauth/templatetags/oauth_tags.py create mode 100644 src/DjangoBlog-master/oauth/tests.py create mode 100644 src/DjangoBlog-master/oauth/urls.py create mode 100644 src/DjangoBlog-master/oauth/views.py create mode 100644 src/DjangoBlog-master/owntracks/__init__.py create mode 100644 src/DjangoBlog-master/owntracks/admin.py create mode 100644 src/DjangoBlog-master/owntracks/apps.py create mode 100644 src/DjangoBlog-master/owntracks/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py create mode 100644 src/DjangoBlog-master/owntracks/migrations/__init__.py create mode 100644 src/DjangoBlog-master/owntracks/models.py create mode 100644 src/DjangoBlog-master/owntracks/tests.py create mode 100644 src/DjangoBlog-master/owntracks/urls.py create mode 100644 src/DjangoBlog-master/owntracks/views.py create mode 100644 src/DjangoBlog-master/plugins/__init__.py create mode 100644 src/DjangoBlog-master/plugins/article_copyright/__init__.py create mode 100644 src/DjangoBlog-master/plugins/article_copyright/plugin.py create mode 100644 src/DjangoBlog-master/plugins/external_links/__init__.py create mode 100644 src/DjangoBlog-master/plugins/external_links/plugin.py create mode 100644 src/DjangoBlog-master/plugins/reading_time/__init__.py create mode 100644 src/DjangoBlog-master/plugins/reading_time/plugin.py create mode 100644 src/DjangoBlog-master/plugins/seo_optimizer/__init__.py create mode 100644 src/DjangoBlog-master/plugins/seo_optimizer/plugin.py create mode 100644 src/DjangoBlog-master/plugins/view_count/__init__.py create mode 100644 src/DjangoBlog-master/plugins/view_count/plugin.py create mode 100644 src/DjangoBlog-master/requirements.txt create mode 100644 src/DjangoBlog-master/servermanager/MemcacheStorage.py create mode 100644 src/DjangoBlog-master/servermanager/__init__.py create mode 100644 src/DjangoBlog-master/servermanager/admin.py create mode 100644 src/DjangoBlog-master/servermanager/api/__init__.py create mode 100644 src/DjangoBlog-master/servermanager/api/blogapi.py create mode 100644 src/DjangoBlog-master/servermanager/api/commonapi.py create mode 100644 src/DjangoBlog-master/servermanager/apps.py create mode 100644 src/DjangoBlog-master/servermanager/migrations/0001_initial.py create mode 100644 src/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py create mode 100644 src/DjangoBlog-master/servermanager/migrations/__init__.py create mode 100644 src/DjangoBlog-master/servermanager/models.py create mode 100644 src/DjangoBlog-master/servermanager/robot.py create mode 100644 src/DjangoBlog-master/servermanager/tests.py create mode 100644 src/DjangoBlog-master/servermanager/urls.py create mode 100644 src/DjangoBlog-master/servermanager/views.py create mode 100644 src/DjangoBlog-master/templates/account/forget_password.html create mode 100644 src/DjangoBlog-master/templates/account/login.html create mode 100644 src/DjangoBlog-master/templates/account/registration_form.html create mode 100644 src/DjangoBlog-master/templates/account/result.html create mode 100644 src/DjangoBlog-master/templates/blog/article_archives.html create mode 100644 src/DjangoBlog-master/templates/blog/article_detail.html create mode 100644 src/DjangoBlog-master/templates/blog/article_index.html create mode 100644 src/DjangoBlog-master/templates/blog/error_page.html create mode 100644 src/DjangoBlog-master/templates/blog/links_list.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/article_info.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/article_meta_info.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/article_pagination.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/article_tag_list.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/breadcrumb.html create mode 100644 src/DjangoBlog-master/templates/blog/tags/sidebar.html create mode 100644 src/DjangoBlog-master/templates/comments/tags/comment_item.html create mode 100644 src/DjangoBlog-master/templates/comments/tags/comment_item_tree.html create mode 100644 src/DjangoBlog-master/templates/comments/tags/comment_list.html create mode 100644 src/DjangoBlog-master/templates/comments/tags/post_comment.html create mode 100644 src/DjangoBlog-master/templates/oauth/bindsuccess.html create mode 100644 src/DjangoBlog-master/templates/oauth/oauth_applications.html create mode 100644 src/DjangoBlog-master/templates/oauth/require_email.html create mode 100644 src/DjangoBlog-master/templates/owntracks/show_log_dates.html create mode 100644 src/DjangoBlog-master/templates/owntracks/show_maps.html create mode 100644 src/DjangoBlog-master/templates/search/indexes/blog/article_text.txt create mode 100644 src/DjangoBlog-master/templates/search/search.html create mode 100644 src/DjangoBlog-master/templates/share_layout/adsense.html create mode 100644 src/DjangoBlog-master/templates/share_layout/base.html create mode 100644 src/DjangoBlog-master/templates/share_layout/base_account.html create mode 100644 src/DjangoBlog-master/templates/share_layout/footer.html create mode 100644 src/DjangoBlog-master/templates/share_layout/nav.html create mode 100644 src/DjangoBlog-master/templates/share_layout/nav_node.html diff --git a/src/DjangoBlog-master/.coveragerc b/src/DjangoBlog-master/.coveragerc new file mode 100644 index 0000000..9757484 --- /dev/null +++ b/src/DjangoBlog-master/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = . +include = *.py +omit = + *migrations* + *tests* + *.html + *whoosh_cn_backend* + *settings.py* + *venv* diff --git a/src/DjangoBlog-master/.dockerignore b/src/DjangoBlog-master/.dockerignore new file mode 100644 index 0000000..2818c38 --- /dev/null +++ b/src/DjangoBlog-master/.dockerignore @@ -0,0 +1,11 @@ +bin/data/ +# virtualenv +venv/ +collectedstatic/ +djangoblog/whoosh_index/ +uploads/ +settings_production.py +*.md +docs/ +logs/ +static/ \ No newline at end of file diff --git a/src/DjangoBlog-master/.gitattributes b/src/DjangoBlog-master/.gitattributes new file mode 100644 index 0000000..fd52ece --- /dev/null +++ b/src/DjangoBlog-master/.gitattributes @@ -0,0 +1,6 @@ +blog/static/* linguist-vendored +*.js linguist-vendored +*.css linguist-vendored +* text=auto +*.sh text eol=lf +*.conf text eol=lf \ No newline at end of file diff --git a/src/DjangoBlog-master/.github/ISSUE_TEMPLATE.md b/src/DjangoBlog-master/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..2b5b7aa --- /dev/null +++ b/src/DjangoBlog-master/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ + + +**我确定我已经查看了** (标注`[ ]`为`[x]`) + +- [ ] [DjangoBlog的readme](https://github.com/liangliangyy/DjangoBlog/blob/master/README.md) +- [ ] [配置说明](https://github.com/liangliangyy/DjangoBlog/blob/master/bin/config.md) +- [ ] [其他 Issues](https://github.com/liangliangyy/DjangoBlog/issues) + +---- + +**我要申请** (标注`[ ]`为`[x]`) + +- [ ] BUG 反馈 +- [ ] 添加新的特性或者功能 +- [ ] 请求技术支持 diff --git a/src/DjangoBlog-master/.github/workflows/codeql-analysis.yml b/src/DjangoBlog-master/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6b76522 --- /dev/null +++ b/src/DjangoBlog-master/.github/workflows/codeql-analysis.yml @@ -0,0 +1,47 @@ +name: "CodeQL" + +on: + push: + branches: + - master + - dev + paths-ignore: + - '**/*.md' + - '**/*.css' + - '**/*.js' + - '**/*.yml' + - '**/*.txt' + pull_request: + branches: + - master + - dev + paths-ignore: + - '**/*.md' + - '**/*.css' + - '**/*.js' + - '**/*.yml' + - '**/*.txt' + schedule: + - cron: '30 1 * * 0' + + +jobs: + CodeQL-Build: + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/src/DjangoBlog-master/.github/workflows/django.yml b/src/DjangoBlog-master/.github/workflows/django.yml new file mode 100644 index 0000000..94baea9 --- /dev/null +++ b/src/DjangoBlog-master/.github/workflows/django.yml @@ -0,0 +1,136 @@ +name: Django CI + +on: + push: + branches: + - master + - dev + paths-ignore: + - '**/*.md' + - '**/*.css' + - '**/*.js' + pull_request: + branches: + - master + - dev + paths-ignore: + - '**/*.md' + - '**/*.css' + - '**/*.js' + +jobs: + build-normal: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ["3.10","3.11" ] + + steps: + - name: Start MySQL + uses: samin/mysql-action@v1.3 + with: + host port: 3306 + container port: 3306 + character set server: utf8mb4 + collation server: utf8mb4_general_ci + mysql version: latest + mysql root password: root + mysql database: djangoblog + mysql user: root + mysql password: root + + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Tests + env: + DJANGO_MYSQL_PASSWORD: root + DJANGO_MYSQL_HOST: 127.0.0.1 + run: | + python manage.py makemigrations + python manage.py migrate + python manage.py test + + build-with-es: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ["3.10","3.11" ] + + steps: + - name: Start MySQL + uses: samin/mysql-action@v1.3 + with: + host port: 3306 + container port: 3306 + character set server: utf8mb4 + collation server: utf8mb4_general_ci + mysql version: latest + mysql root password: root + mysql database: djangoblog + mysql user: root + mysql password: root + + - name: Configure sysctl limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + + - uses: miyataka/elasticsearch-github-actions@1 + + with: + stack-version: '7.12.1' + plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip' + + + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Tests + env: + DJANGO_MYSQL_PASSWORD: root + DJANGO_MYSQL_HOST: 127.0.0.1 + DJANGO_ELASTICSEARCH_HOST: 127.0.0.1:9200 + run: | + python manage.py makemigrations + python manage.py migrate + coverage run manage.py test + coverage xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + push: false + tags: djangoblog/djangoblog:dev diff --git a/src/DjangoBlog-master/.github/workflows/docker.yml b/src/DjangoBlog-master/.github/workflows/docker.yml new file mode 100644 index 0000000..a312e2f --- /dev/null +++ b/src/DjangoBlog-master/.github/workflows/docker.yml @@ -0,0 +1,43 @@ +name: docker + +on: + push: + paths-ignore: + - '**/*.md' + - '**/*.yml' + branches: + - 'master' + - 'dev' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set env to docker dev tag + if: endsWith(github.ref, '/dev') + run: | + echo "DOCKER_TAG=test" >> $GITHUB_ENV + - name: Set env to docker latest tag + if: endsWith(github.ref, '/master') + run: | + echo "DOCKER_TAG=latest" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{env.DOCKER_TAG}} + + diff --git a/src/DjangoBlog-master/.github/workflows/publish-release.yml b/src/DjangoBlog-master/.github/workflows/publish-release.yml new file mode 100644 index 0000000..5eb0853 --- /dev/null +++ b/src/DjangoBlog-master/.github/workflows/publish-release.yml @@ -0,0 +1,39 @@ +name: publish release + +on: + release: + types: [ published ] + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: name/app + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + push: true + platforms: | + linux/amd64 + linux/arm64 + linux/arm/v7 + linux/arm/v6 + linux/386 + tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{ github.event.release.tag_name }} diff --git a/src/DjangoBlog-master/.gitignore b/src/DjangoBlog-master/.gitignore new file mode 100644 index 0000000..3015816 --- /dev/null +++ b/src/DjangoBlog-master/.gitignore @@ -0,0 +1,80 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.pot + +# Django stuff: +*.log +logs/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + + +# PyCharm +# http://www.jetbrains.com/pycharm/webhelp/project.html +.idea +.iml +static/ +# virtualenv +venv/ + +collectedstatic/ +djangoblog/whoosh_index/ +google93fd32dbd906620a.html +baidu_verify_FlHL7cUyC9.html +BingSiteAuth.xml +cb9339dbe2ff86a5aa169d28dba5f615.txt +werobot_session.* +django.jpg +uploads/ +settings_production.py +werobot_session.db +bin/datas/ diff --git a/src/DjangoBlog-master/Dockerfile b/src/DjangoBlog-master/Dockerfile new file mode 100644 index 0000000..80b46ac --- /dev/null +++ b/src/DjangoBlog-master/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11 +ENV PYTHONUNBUFFERED 1 +WORKDIR /code/djangoblog/ +RUN apt-get update && \ + apt-get install default-libmysqlclient-dev gettext -y && \ + rm -rf /var/lib/apt/lists/* +ADD requirements.txt requirements.txt +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt && \ + pip install --no-cache-dir gunicorn[gevent] && \ + pip cache purge + +ADD . . +RUN chmod +x /code/djangoblog/deploy/entrypoint.sh +ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"] diff --git a/src/DjangoBlog-master/LICENSE b/src/DjangoBlog-master/LICENSE new file mode 100644 index 0000000..3b08474 --- /dev/null +++ b/src/DjangoBlog-master/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2025 车亮亮 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/DjangoBlog-master/README.md b/src/DjangoBlog-master/README.md new file mode 100644 index 0000000..56aa4cc --- /dev/null +++ b/src/DjangoBlog-master/README.md @@ -0,0 +1,158 @@ +# DjangoBlog + +

    + Django CI + CodeQL + codecov + license +

    + +

    + 一款功能强大、设计优雅的现代化博客系统 +
    + English简体中文 +

    + +--- + +DjangoBlog 是一款基于 Python 3.10 和 Django 4.0 构建的高性能博客平台。它不仅提供了传统博客的所有核心功能,还通过一个灵活的插件系统,让您可以轻松扩展和定制您的网站。无论您是个人博主、技术爱好者还是内容创作者,DjangoBlog 都旨在为您提供一个稳定、高效且易于维护的写作和发布环境。 + +## ✨ 特性亮点 + +- **强大的内容管理**: 支持文章、独立页面、分类和标签的完整管理。内置强大的 Markdown 编辑器,支持代码语法高亮。 +- **全文搜索**: 集成搜索引擎,提供快速、精准的文章内容搜索。 +- **互动评论系统**: 支持回复、邮件提醒等功能,评论内容同样支持 Markdown。 +- **灵活的侧边栏**: 可自定义展示最新文章、最多阅读、标签云等模块。 +- **社交化登录**: 内置 OAuth 支持,已集成 Google, GitHub, Facebook, 微博, QQ 等主流平台。 +- **高性能缓存**: 原生支持 Redis 缓存,并提供自动刷新机制,确保网站高速响应。 +- **SEO 友好**: 具备基础 SEO 功能,新内容发布后可自动通知 Google 和百度。 +- **便捷的插件系统**: 通过创建独立的插件来扩展博客功能,代码解耦,易于维护。我们已经通过插件实现了文章浏览计数、SEO 优化等功能! +- **集成图床**: 内置简单的图床功能,方便图片上传和管理。 +- **自动化前端**: 集成 `django-compressor`,自动压缩和优化 CSS 及 JavaScript 文件。 +- **健壮的运维**: 内置网站异常邮件提醒和微信公众号管理功能。 + +## 🛠️ 技术栈 + +- **后端**: Python 3.10, Django 4.0 +- **数据库**: MySQL, SQLite (可配置) +- **缓存**: Redis +- **前端**: HTML5, CSS3, JavaScript +- **搜索**: Whoosh, Elasticsearch (可配置) +- **编辑器**: Markdown (mdeditor) + +## 🚀 快速开始 + +### 1. 环境准备 + +确保您的系统中已安装 Python 3.10+ 和 MySQL/MariaDB。 + +### 2. 克隆与安装 + +```bash +# 克隆项目到本地 +git clone https://github.com/liangliangyy/DjangoBlog.git +cd DjangoBlog + +# 安装依赖 +pip install -r requirements.txt +``` + +### 3. 项目配置 + +- **数据库**: + 打开 `djangoblog/settings.py` 文件,找到 `DATABASES` 配置项,修改为您的 MySQL 连接信息。 + + ```python + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'djangoblog', + 'USER': 'root', + 'PASSWORD': 'your_password', + 'HOST': '127.0.0.1', + 'PORT': 3306, + } + } + ``` + 在 MySQL 中创建数据库: + ```sql + CREATE DATABASE `djangoblog` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + ``` + +- **更多配置**: + 关于邮件发送、OAuth 登录、缓存等更多高级配置,请参阅我们的 [详细配置文档](/docs/config.md)。 + +### 4. 初始化数据库 + +```bash +python manage.py makemigrations +python manage.py migrate + +# 创建一个超级管理员账户 +python manage.py createsuperuser +``` + +### 5. 运行项目 + +```bash +# (可选) 生成一些测试数据 +python manage.py create_testdata + +# (可选) 收集和压缩静态文件 +python manage.py collectstatic --noinput +python manage.py compress --force + +# 启动开发服务器 +python manage.py runserver +``` + +现在,在您的浏览器中访问 `http://127.0.0.1:8000/`,您应该能看到 DjangoBlog 的首页了! + +## 部署 + +- **传统部署**: 我们为您准备了非常详细的 [服务器部署教程](https://www.lylinux.net/article/2019/8/5/58.html)。 +- **Docker 部署**: 项目已全面支持 Docker。如果您熟悉容器化技术,请参考 [Docker 部署文档](/docs/docker.md) 来快速启动。 +- **Kubernetes 部署**: 我们也提供了完整的 [Kubernetes 部署指南](/docs/k8s.md),助您轻松上云。 + +## 🧩 插件系统 + +插件系统是 DjangoBlog 的核心特色之一。它允许您在不修改核心代码的情况下,通过编写独立的插件来为您的博客添加新功能。 + +- **工作原理**: 插件通过在预定义的“钩子”上注册回调函数来工作。例如,当一篇文章被渲染时,`after_article_body_get` 钩子会被触发,所有注册到此钩子的函数都会被执行。 +- **现有插件**: `view_count`(浏览计数), `seo_optimizer`(SEO优化)等都是通过插件系统实现的。 +- **开发您自己的插件**: 只需在 `plugins` 目录下创建一个新的文件夹,并编写您的 `plugin.py`。欢迎探索并为 DjangoBlog 社区贡献您的创意! + +## 🤝 贡献指南 + +我们热烈欢迎任何形式的贡献!如果您有好的想法或发现了 Bug,请随时提交 Issue 或 Pull Request。 + +## 📄 许可证 + +本项目基于 [MIT License](LICENSE) 开源。 + +--- + +## ❤️ 支持与赞助 + +如果您觉得这个项目对您有帮助,并且希望支持我继续维护和开发新功能,欢迎请我喝杯咖啡!您的每一份支持都是我前进的最大动力。 + +

    + 支付宝赞助 + 微信赞助 +

    +

    + (左) 支付宝 / (右) 微信 +

    + +## 🙏 鸣谢 + +特别感谢 **JetBrains** 为本项目提供的免费开源许可证。 + +

    + + JetBrains Logo + +

    + +--- +> 如果本项目帮助到了你,请在[这里](https://github.com/liangliangyy/DjangoBlog/issues/214)留下你的网址,让更多的人看到。您的回复将会是我继续更新维护下去的动力。 diff --git a/src/DjangoBlog-master/accounts/__init__.py b/src/DjangoBlog-master/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py new file mode 100644 index 0000000..32e483c --- /dev/null +++ b/src/DjangoBlog-master/accounts/admin.py @@ -0,0 +1,59 @@ +from django import forms +from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.forms import UserChangeForm +from django.contrib.auth.forms import UsernameField +from django.utils.translation import gettext_lazy as _ + +# Register your models here. +from .models import BlogUser + + +class BlogUserCreationForm(forms.ModelForm): + password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) + + class Meta: + model = BlogUser + fields = ('email',) + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError(_("passwords do not match")) + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.source = 'adminsite' + user.save() + return user + + +class BlogUserChangeForm(UserChangeForm): + class Meta: + model = BlogUser + fields = '__all__' + field_classes = {'username': UsernameField} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class BlogUserAdmin(UserAdmin): + form = BlogUserChangeForm + add_form = BlogUserCreationForm + list_display = ( + 'id', + 'nickname', + 'username', + 'email', + 'last_login', + 'date_joined', + 'source') + list_display_links = ('id', 'username') + ordering = ('-id',) diff --git a/src/DjangoBlog-master/accounts/apps.py b/src/DjangoBlog-master/accounts/apps.py new file mode 100644 index 0000000..9b3fc5a --- /dev/null +++ b/src/DjangoBlog-master/accounts/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'accounts' diff --git a/src/DjangoBlog-master/accounts/forms.py b/src/DjangoBlog-master/accounts/forms.py new file mode 100644 index 0000000..fce4137 --- /dev/null +++ b/src/DjangoBlog-master/accounts/forms.py @@ -0,0 +1,117 @@ +from django import forms +from django.contrib.auth import get_user_model, password_validation +from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +from django.core.exceptions import ValidationError +from django.forms import widgets +from django.utils.translation import gettext_lazy as _ +from . import utils +from .models import BlogUser + + +class LoginForm(AuthenticationForm): + def __init__(self, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) + self.fields['username'].widget = widgets.TextInput( + attrs={'placeholder': "username", "class": "form-control"}) + self.fields['password'].widget = widgets.PasswordInput( + attrs={'placeholder': "password", "class": "form-control"}) + + +class RegisterForm(UserCreationForm): + def __init__(self, *args, **kwargs): + super(RegisterForm, self).__init__(*args, **kwargs) + + self.fields['username'].widget = widgets.TextInput( + attrs={'placeholder': "username", "class": "form-control"}) + self.fields['email'].widget = widgets.EmailInput( + attrs={'placeholder': "email", "class": "form-control"}) + self.fields['password1'].widget = widgets.PasswordInput( + attrs={'placeholder': "password", "class": "form-control"}) + self.fields['password2'].widget = widgets.PasswordInput( + attrs={'placeholder': "repeat password", "class": "form-control"}) + + def clean_email(self): + email = self.cleaned_data['email'] + if get_user_model().objects.filter(email=email).exists(): + raise ValidationError(_("email already exists")) + return email + + class Meta: + model = get_user_model() + fields = ("username", "email") + + +class ForgetPasswordForm(forms.Form): + new_password1 = forms.CharField( + label=_("New password"), + widget=forms.PasswordInput( + attrs={ + "class": "form-control", + 'placeholder': _("New password") + } + ), + ) + + new_password2 = forms.CharField( + label="确认密码", + widget=forms.PasswordInput( + attrs={ + "class": "form-control", + 'placeholder': _("Confirm password") + } + ), + ) + + email = forms.EmailField( + label='邮箱', + widget=forms.TextInput( + attrs={ + 'class': 'form-control', + 'placeholder': _("Email") + } + ), + ) + + code = forms.CharField( + label=_('Code'), + widget=forms.TextInput( + attrs={ + 'class': 'form-control', + 'placeholder': _("Code") + } + ), + ) + + def clean_new_password2(self): + password1 = self.data.get("new_password1") + password2 = self.data.get("new_password2") + if password1 and password2 and password1 != password2: + raise ValidationError(_("passwords do not match")) + password_validation.validate_password(password2) + + return password2 + + def clean_email(self): + user_email = self.cleaned_data.get("email") + if not BlogUser.objects.filter( + email=user_email + ).exists(): + # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 + raise ValidationError(_("email does not exist")) + return user_email + + def clean_code(self): + code = self.cleaned_data.get("code") + error = utils.verify( + email=self.cleaned_data.get("email"), + code=code, + ) + if error: + raise ValidationError(error) + return code + + +class ForgetPasswordCodeForm(forms.Form): + email = forms.EmailField( + label=_('Email'), + ) diff --git a/src/DjangoBlog-master/accounts/migrations/0001_initial.py b/src/DjangoBlog-master/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..d2fbcab --- /dev/null +++ b/src/DjangoBlog-master/accounts/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='BlogUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': '用户', + 'verbose_name_plural': '用户', + 'ordering': ['-id'], + 'get_latest_by': 'id', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py new file mode 100644 index 0000000..1a9f509 --- /dev/null +++ b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:13 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='bloguser', + options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, + ), + migrations.RemoveField( + model_name='bloguser', + name='created_time', + ), + migrations.RemoveField( + model_name='bloguser', + name='last_mod_time', + ), + migrations.AddField( + model_name='bloguser', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='bloguser', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), + ), + migrations.AlterField( + model_name='bloguser', + name='nickname', + field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), + ), + migrations.AlterField( + model_name='bloguser', + name='source', + field=models.CharField(blank=True, max_length=100, verbose_name='create source'), + ), + ] diff --git a/src/DjangoBlog-master/accounts/migrations/__init__.py b/src/DjangoBlog-master/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py new file mode 100644 index 0000000..3baddbb --- /dev/null +++ b/src/DjangoBlog-master/accounts/models.py @@ -0,0 +1,35 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.urls import reverse +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +from djangoblog.utils import get_current_site + + +# Create your models here. + +class BlogUser(AbstractUser): + nickname = models.CharField(_('nick name'), max_length=100, blank=True) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + source = models.CharField(_('create source'), max_length=100, blank=True) + + def get_absolute_url(self): + return reverse( + 'blog:author_detail', kwargs={ + 'author_name': self.username}) + + def __str__(self): + return self.email + + def get_full_url(self): + site = get_current_site().domain + url = "https://{site}{path}".format(site=site, + path=self.get_absolute_url()) + return url + + class Meta: + ordering = ['-id'] + verbose_name = _('user') + verbose_name_plural = verbose_name + get_latest_by = 'id' diff --git a/src/DjangoBlog-master/accounts/templatetags/__init__.py b/src/DjangoBlog-master/accounts/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/accounts/tests.py b/src/DjangoBlog-master/accounts/tests.py new file mode 100644 index 0000000..6893411 --- /dev/null +++ b/src/DjangoBlog-master/accounts/tests.py @@ -0,0 +1,207 @@ +from django.test import Client, RequestFactory, TestCase +from django.urls import reverse +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ + +from accounts.models import BlogUser +from blog.models import Article, Category +from djangoblog.utils import * +from . import utils + + +# Create your tests here. + +class AccountTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + self.blog_user = BlogUser.objects.create_user( + username="test", + email="admin@admin.com", + password="12345678" + ) + self.new_test = "xxx123--=" + + def test_validate_account(self): + site = get_current_site().domain + user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="qwer!@#$ggg") + testuser = BlogUser.objects.get(username='liangliangyy1') + + loginresult = self.client.login( + username='liangliangyy1', + password='qwer!@#$ggg') + self.assertEqual(loginresult, True) + response = self.client.get('/admin/') + self.assertEqual(response.status_code, 200) + + category = Category() + category.name = "categoryaaa" + category.creation_time = timezone.now() + category.last_modify_time = timezone.now() + category.save() + + article = Article() + article.title = "nicetitleaaa" + article.body = "nicecontentaaa" + article.author = user + article.category = category + article.type = 'a' + article.status = 'p' + article.save() + + response = self.client.get(article.get_admin_url()) + self.assertEqual(response.status_code, 200) + + def test_validate_register(self): + self.assertEquals( + 0, len( + BlogUser.objects.filter( + email='user123@user.com'))) + response = self.client.post(reverse('account:register'), { + 'username': 'user1233', + 'email': 'user123@user.com', + 'password1': 'password123!q@wE#R$T', + 'password2': 'password123!q@wE#R$T', + }) + self.assertEquals( + 1, len( + BlogUser.objects.filter( + email='user123@user.com'))) + user = BlogUser.objects.filter(email='user123@user.com')[0] + sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + path = reverse('accounts:result') + url = '{path}?type=validation&id={id}&sign={sign}'.format( + path=path, id=user.id, sign=sign) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + self.client.login(username='user1233', password='password123!q@wE#R$T') + user = BlogUser.objects.filter(email='user123@user.com')[0] + user.is_superuser = True + user.is_staff = True + user.save() + delete_sidebar_cache() + category = Category() + category.name = "categoryaaa" + category.creation_time = timezone.now() + category.last_modify_time = timezone.now() + category.save() + + article = Article() + article.category = category + article.title = "nicetitle333" + article.body = "nicecontentttt" + article.author = user + + article.type = 'a' + article.status = 'p' + article.save() + + response = self.client.get(article.get_admin_url()) + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse('account:logout')) + self.assertIn(response.status_code, [301, 302, 200]) + + response = self.client.get(article.get_admin_url()) + self.assertIn(response.status_code, [301, 302, 200]) + + response = self.client.post(reverse('account:login'), { + 'username': 'user1233', + 'password': 'password123' + }) + self.assertIn(response.status_code, [301, 302, 200]) + + response = self.client.get(article.get_admin_url()) + self.assertIn(response.status_code, [301, 302, 200]) + + def test_verify_email_code(self): + to_email = "admin@admin.com" + code = generate_code() + utils.set_code(to_email, code) + utils.send_verify_email(to_email, code) + + err = utils.verify("admin@admin.com", code) + self.assertEqual(err, None) + + err = utils.verify("admin@123.com", code) + self.assertEqual(type(err), str) + + def test_forget_password_email_code_success(self): + resp = self.client.post( + path=reverse("account:forget_password_code"), + data=dict(email="admin@admin.com") + ) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content.decode("utf-8"), "ok") + + def test_forget_password_email_code_fail(self): + resp = self.client.post( + path=reverse("account:forget_password_code"), + data=dict() + ) + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + + resp = self.client.post( + path=reverse("account:forget_password_code"), + data=dict(email="admin@com") + ) + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + + def test_forget_password_email_success(self): + code = generate_code() + utils.set_code(self.blog_user.email, code) + data = dict( + new_password1=self.new_test, + new_password2=self.new_test, + email=self.blog_user.email, + code=code, + ) + resp = self.client.post( + path=reverse("account:forget_password"), + data=data + ) + self.assertEqual(resp.status_code, 302) + + # 验证用户密码是否修改成功 + blog_user = BlogUser.objects.filter( + email=self.blog_user.email, + ).first() # type: BlogUser + self.assertNotEqual(blog_user, None) + self.assertEqual(blog_user.check_password(data["new_password1"]), True) + + def test_forget_password_email_not_user(self): + data = dict( + new_password1=self.new_test, + new_password2=self.new_test, + email="123@123.com", + code="123456", + ) + resp = self.client.post( + path=reverse("account:forget_password"), + data=data + ) + + self.assertEqual(resp.status_code, 200) + + + def test_forget_password_email_code_error(self): + code = generate_code() + utils.set_code(self.blog_user.email, code) + data = dict( + new_password1=self.new_test, + new_password2=self.new_test, + email=self.blog_user.email, + code="111111", + ) + resp = self.client.post( + path=reverse("account:forget_password"), + data=data + ) + + self.assertEqual(resp.status_code, 200) + diff --git a/src/DjangoBlog-master/accounts/urls.py b/src/DjangoBlog-master/accounts/urls.py new file mode 100644 index 0000000..107a801 --- /dev/null +++ b/src/DjangoBlog-master/accounts/urls.py @@ -0,0 +1,28 @@ +from django.urls import path +from django.urls import re_path + +from . import views +from .forms import LoginForm + +app_name = "accounts" + +urlpatterns = [re_path(r'^login/$', + views.LoginView.as_view(success_url='/'), + name='login', + kwargs={'authentication_form': LoginForm}), + re_path(r'^register/$', + views.RegisterView.as_view(success_url="/"), + name='register'), + re_path(r'^logout/$', + views.LogoutView.as_view(), + name='logout'), + path(r'account/result.html', + views.account_result, + name='result'), + re_path(r'^forget_password/$', + views.ForgetPasswordView.as_view(), + name='forget_password'), + re_path(r'^forget_password_code/$', + views.ForgetPasswordEmailCode.as_view(), + name='forget_password_code'), + ] diff --git a/src/DjangoBlog-master/accounts/user_login_backend.py b/src/DjangoBlog-master/accounts/user_login_backend.py new file mode 100644 index 0000000..73cdca1 --- /dev/null +++ b/src/DjangoBlog-master/accounts/user_login_backend.py @@ -0,0 +1,26 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + + +class EmailOrUsernameModelBackend(ModelBackend): + """ + 允许使用用户名或邮箱登录 + """ + + def authenticate(self, request, username=None, password=None, **kwargs): + if '@' in username: + kwargs = {'email': username} + else: + kwargs = {'username': username} + try: + user = get_user_model().objects.get(**kwargs) + if user.check_password(password): + return user + except get_user_model().DoesNotExist: + return None + + def get_user(self, username): + try: + return get_user_model().objects.get(pk=username) + except get_user_model().DoesNotExist: + return None diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py new file mode 100644 index 0000000..4b94bdf --- /dev/null +++ b/src/DjangoBlog-master/accounts/utils.py @@ -0,0 +1,49 @@ +import typing +from datetime import timedelta + +from django.core.cache import cache +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ + +from djangoblog.utils import send_email + +_code_ttl = timedelta(minutes=5) + + +def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): + """发送重设密码验证码 + Args: + to_mail: 接受邮箱 + subject: 邮件主题 + code: 验证码 + """ + html_content = _( + "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " + "properly") % {'code': code} + send_email([to_mail], subject, html_content) + + +def verify(email: str, code: str) -> typing.Optional[str]: + """验证code是否有效 + Args: + email: 请求邮箱 + code: 验证码 + Return: + 如果有错误就返回错误str + Node: + 这里的错误处理不太合理,应该采用raise抛出 + 否测调用方也需要对error进行处理 + """ + cache_code = get_code(email) + if cache_code != code: + return gettext("Verification code error") + + +def set_code(email: str, code: str): + """设置code""" + cache.set(email, code, _code_ttl.seconds) + + +def get_code(email: str) -> typing.Optional[str]: + """获取code""" + return cache.get(email) diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py new file mode 100644 index 0000000..ae67aec --- /dev/null +++ b/src/DjangoBlog-master/accounts/views.py @@ -0,0 +1,204 @@ +import logging +from django.utils.translation import gettext_lazy as _ +from django.conf import settings +from django.contrib import auth +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth import get_user_model +from django.contrib.auth import logout +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.hashers import make_password +from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.shortcuts import get_object_or_404 +from django.shortcuts import render +from django.urls import reverse +from django.utils.decorators import method_decorator +from django.utils.http import url_has_allowed_host_and_scheme +from django.views import View +from django.views.decorators.cache import never_cache +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.debug import sensitive_post_parameters +from django.views.generic import FormView, RedirectView + +from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache +from . import utils +from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm +from .models import BlogUser + +logger = logging.getLogger(__name__) + + +# Create your views here. + +class RegisterView(FormView): + form_class = RegisterForm + template_name = 'account/registration_form.html' + + @method_decorator(csrf_protect) + def dispatch(self, *args, **kwargs): + return super(RegisterView, self).dispatch(*args, **kwargs) + + def form_valid(self, form): + if form.is_valid(): + user = form.save(False) + user.is_active = False + user.source = 'Register' + user.save(True) + site = get_current_site().domain + sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + + if settings.DEBUG: + site = '127.0.0.1:8000' + path = reverse('account:result') + url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( + site=site, path=path, id=user.id, sign=sign) + + content = """ +

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

    + + {url} + + 再次感谢您! +
    + 如果上面链接无法打开,请将此链接复制至浏览器。 + {url} + """.format(url=url) + send_email( + emailto=[ + user.email, + ], + title='验证您的电子邮箱', + content=content) + + url = reverse('accounts:result') + \ + '?type=register&id=' + str(user.id) + return HttpResponseRedirect(url) + else: + return self.render_to_response({ + 'form': form + }) + + +class LogoutView(RedirectView): + url = '/login/' + + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + return super(LogoutView, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + logout(request) + delete_sidebar_cache() + return super(LogoutView, self).get(request, *args, **kwargs) + + +class LoginView(FormView): + form_class = LoginForm + template_name = 'account/login.html' + success_url = '/' + redirect_field_name = REDIRECT_FIELD_NAME + login_ttl = 2626560 # 一个月的时间 + + @method_decorator(sensitive_post_parameters('password')) + @method_decorator(csrf_protect) + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + + return super(LoginView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + redirect_to = self.request.GET.get(self.redirect_field_name) + if redirect_to is None: + redirect_to = '/' + kwargs['redirect_to'] = redirect_to + + return super(LoginView, self).get_context_data(**kwargs) + + def form_valid(self, form): + form = AuthenticationForm(data=self.request.POST, request=self.request) + + if form.is_valid(): + delete_sidebar_cache() + logger.info(self.redirect_field_name) + + auth.login(self.request, form.get_user()) + if self.request.POST.get("remember"): + self.request.session.set_expiry(self.login_ttl) + return super(LoginView, self).form_valid(form) + # return HttpResponseRedirect('/') + else: + return self.render_to_response({ + 'form': form + }) + + def get_success_url(self): + + redirect_to = self.request.POST.get(self.redirect_field_name) + if not url_has_allowed_host_and_scheme( + url=redirect_to, allowed_hosts=[ + self.request.get_host()]): + redirect_to = self.success_url + return redirect_to + + +def account_result(request): + type = request.GET.get('type') + id = request.GET.get('id') + + user = get_object_or_404(get_user_model(), id=id) + logger.info(type) + if user.is_active: + return HttpResponseRedirect('/') + if type and type in ['register', 'validation']: + if type == 'register': + content = ''' + 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 + ''' + title = '注册成功' + else: + c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + sign = request.GET.get('sign') + if sign != c_sign: + return HttpResponseForbidden() + user.is_active = True + user.save() + content = ''' + 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 + ''' + title = '验证成功' + return render(request, 'account/result.html', { + 'title': title, + 'content': content + }) + else: + return HttpResponseRedirect('/') + + +class ForgetPasswordView(FormView): + form_class = ForgetPasswordForm + template_name = 'account/forget_password.html' + + def form_valid(self, form): + if form.is_valid(): + blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() + blog_user.password = make_password(form.cleaned_data["new_password2"]) + blog_user.save() + return HttpResponseRedirect('/login/') + else: + return self.render_to_response({'form': form}) + + +class ForgetPasswordEmailCode(View): + + def post(self, request: HttpRequest): + form = ForgetPasswordCodeForm(request.POST) + if not form.is_valid(): + return HttpResponse("错误的邮箱") + to_email = form.cleaned_data["email"] + + code = generate_code() + utils.send_verify_email(to_email, code) + utils.set_code(to_email, code) + + return HttpResponse("ok") diff --git a/src/DjangoBlog-master/blog/__init__.py b/src/DjangoBlog-master/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/blog/admin.py new file mode 100644 index 0000000..46c3420 --- /dev/null +++ b/src/DjangoBlog-master/blog/admin.py @@ -0,0 +1,112 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth import get_user_model +from django.urls import reverse +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + +# Register your models here. +from .models import Article + + +class ArticleForm(forms.ModelForm): + # body = forms.CharField(widget=AdminPagedownWidget()) + + class Meta: + model = Article + fields = '__all__' + + +def makr_article_publish(modeladmin, request, queryset): + queryset.update(status='p') + + +def draft_article(modeladmin, request, queryset): + queryset.update(status='d') + + +def close_article_commentstatus(modeladmin, request, queryset): + queryset.update(comment_status='c') + + +def open_article_commentstatus(modeladmin, request, queryset): + queryset.update(comment_status='o') + + +makr_article_publish.short_description = _('Publish selected articles') +draft_article.short_description = _('Draft selected articles') +close_article_commentstatus.short_description = _('Close article comments') +open_article_commentstatus.short_description = _('Open article comments') + + +class ArticlelAdmin(admin.ModelAdmin): + list_per_page = 20 + search_fields = ('body', 'title') + form = ArticleForm + list_display = ( + 'id', + 'title', + 'author', + 'link_to_category', + 'creation_time', + 'views', + 'status', + 'type', + 'article_order') + list_display_links = ('id', 'title') + list_filter = ('status', 'type', 'category') + filter_horizontal = ('tags',) + exclude = ('creation_time', 'last_modify_time') + view_on_site = True + actions = [ + makr_article_publish, + draft_article, + close_article_commentstatus, + open_article_commentstatus] + + def link_to_category(self, obj): + info = (obj.category._meta.app_label, obj.category._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) + return format_html(u'%s' % (link, obj.category.name)) + + link_to_category.short_description = _('category') + + def get_form(self, request, obj=None, **kwargs): + form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) + form.base_fields['author'].queryset = get_user_model( + ).objects.filter(is_superuser=True) + return form + + def save_model(self, request, obj, form, change): + super(ArticlelAdmin, self).save_model(request, obj, form, change) + + def get_view_on_site_url(self, obj=None): + if obj: + url = obj.get_full_url() + return url + else: + from djangoblog.utils import get_current_site + site = get_current_site().domain + return site + + +class TagAdmin(admin.ModelAdmin): + exclude = ('slug', 'last_mod_time', 'creation_time') + + +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'parent_category', 'index') + exclude = ('slug', 'last_mod_time', 'creation_time') + + +class LinksAdmin(admin.ModelAdmin): + exclude = ('last_mod_time', 'creation_time') + + +class SideBarAdmin(admin.ModelAdmin): + list_display = ('name', 'content', 'is_enable', 'sequence') + exclude = ('last_mod_time', 'creation_time') + + +class BlogSettingsAdmin(admin.ModelAdmin): + pass diff --git a/src/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/blog/apps.py new file mode 100644 index 0000000..7930587 --- /dev/null +++ b/src/DjangoBlog-master/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = 'blog' diff --git a/src/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/blog/context_processors.py new file mode 100644 index 0000000..73e3088 --- /dev/null +++ b/src/DjangoBlog-master/blog/context_processors.py @@ -0,0 +1,43 @@ +import logging + +from django.utils import timezone + +from djangoblog.utils import cache, get_blog_setting +from .models import Category, Article + +logger = logging.getLogger(__name__) + + +def seo_processor(requests): + key = 'seo_processor' + value = cache.get(key) + if value: + return value + else: + logger.info('set processor cache.') + setting = get_blog_setting() + value = { + 'SITE_NAME': setting.site_name, + 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, + 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, + 'SITE_SEO_DESCRIPTION': setting.site_seo_description, + 'SITE_DESCRIPTION': setting.site_description, + 'SITE_KEYWORDS': setting.site_keywords, + 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', + 'ARTICLE_SUB_LENGTH': setting.article_sub_length, + 'nav_category_list': Category.objects.all(), + 'nav_pages': Article.objects.filter( + type='p', + status='p'), + 'OPEN_SITE_COMMENT': setting.open_site_comment, + 'BEIAN_CODE': setting.beian_code, + 'ANALYTICS_CODE': setting.analytics_code, + "BEIAN_CODE_GONGAN": setting.gongan_beiancode, + "SHOW_GONGAN_CODE": setting.show_gongan_code, + "CURRENT_YEAR": timezone.now().year, + "GLOBAL_HEADER": setting.global_header, + "GLOBAL_FOOTER": setting.global_footer, + "COMMENT_NEED_REVIEW": setting.comment_need_review, + } + cache.set(key, value, 60 * 60 * 10) + return value diff --git a/src/DjangoBlog-master/blog/documents.py b/src/DjangoBlog-master/blog/documents.py new file mode 100644 index 0000000..0f1db7b --- /dev/null +++ b/src/DjangoBlog-master/blog/documents.py @@ -0,0 +1,213 @@ +import time + +import elasticsearch.client +from django.conf import settings +from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean +from elasticsearch_dsl.connections import connections + +from blog.models import Article + +ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') + +if ELASTICSEARCH_ENABLED: + connections.create_connection( + hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) + from elasticsearch import Elasticsearch + + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + from elasticsearch.client import IngestClient + + c = IngestClient(es) + try: + c.get_pipeline('geoip') + except elasticsearch.exceptions.NotFoundError: + c.put_pipeline('geoip', body='''{ + "description" : "Add geoip info", + "processors" : [ + { + "geoip" : { + "field" : "ip" + } + } + ] + }''') + + +class GeoIp(InnerDoc): + continent_name = Keyword() + country_iso_code = Keyword() + country_name = Keyword() + location = GeoPoint() + + +class UserAgentBrowser(InnerDoc): + Family = Keyword() + Version = Keyword() + + +class UserAgentOS(UserAgentBrowser): + pass + + +class UserAgentDevice(InnerDoc): + Family = Keyword() + Brand = Keyword() + Model = Keyword() + + +class UserAgent(InnerDoc): + browser = Object(UserAgentBrowser, required=False) + os = Object(UserAgentOS, required=False) + device = Object(UserAgentDevice, required=False) + string = Text() + is_bot = Boolean() + + +class ElapsedTimeDocument(Document): + url = Keyword() + time_taken = Long() + log_datetime = Date() + ip = Keyword() + geoip = Object(GeoIp, required=False) + useragent = Object(UserAgent, required=False) + + class Index: + name = 'performance' + settings = { + "number_of_shards": 1, + "number_of_replicas": 0 + } + + class Meta: + doc_type = 'ElapsedTime' + + +class ElaspedTimeDocumentManager: + @staticmethod + def build_index(): + from elasticsearch import Elasticsearch + client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + res = client.indices.exists(index="performance") + if not res: + ElapsedTimeDocument.init() + + @staticmethod + def delete_index(): + from elasticsearch import Elasticsearch + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + es.indices.delete(index='performance', ignore=[400, 404]) + + @staticmethod + def create(url, time_taken, log_datetime, useragent, ip): + ElaspedTimeDocumentManager.build_index() + ua = UserAgent() + ua.browser = UserAgentBrowser() + ua.browser.Family = useragent.browser.family + ua.browser.Version = useragent.browser.version_string + + ua.os = UserAgentOS() + ua.os.Family = useragent.os.family + ua.os.Version = useragent.os.version_string + + ua.device = UserAgentDevice() + ua.device.Family = useragent.device.family + ua.device.Brand = useragent.device.brand + ua.device.Model = useragent.device.model + ua.string = useragent.ua_string + ua.is_bot = useragent.is_bot + + doc = ElapsedTimeDocument( + meta={ + 'id': int( + round( + time.time() * + 1000)) + }, + url=url, + time_taken=time_taken, + log_datetime=log_datetime, + useragent=ua, ip=ip) + doc.save(pipeline="geoip") + + +class ArticleDocument(Document): + body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') + title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') + author = Object(properties={ + 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), + 'id': Integer() + }) + category = Object(properties={ + 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), + 'id': Integer() + }) + tags = Object(properties={ + 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), + 'id': Integer() + }) + + pub_time = Date() + status = Text() + comment_status = Text() + type = Text() + views = Integer() + article_order = Integer() + + class Index: + name = 'blog' + settings = { + "number_of_shards": 1, + "number_of_replicas": 0 + } + + class Meta: + doc_type = 'Article' + + +class ArticleDocumentManager(): + + def __init__(self): + self.create_index() + + def create_index(self): + ArticleDocument.init() + + def delete_index(self): + from elasticsearch import Elasticsearch + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + es.indices.delete(index='blog', ignore=[400, 404]) + + def convert_to_doc(self, articles): + return [ + ArticleDocument( + meta={ + 'id': article.id}, + body=article.body, + title=article.title, + author={ + 'nickname': article.author.username, + 'id': article.author.id}, + category={ + 'name': article.category.name, + 'id': article.category.id}, + tags=[ + { + 'name': t.name, + 'id': t.id} for t in article.tags.all()], + pub_time=article.pub_time, + status=article.status, + comment_status=article.comment_status, + type=article.type, + views=article.views, + article_order=article.article_order) for article in articles] + + def rebuild(self, articles=None): + ArticleDocument.init() + articles = articles if articles else Article.objects.all() + docs = self.convert_to_doc(articles) + for doc in docs: + doc.save() + + def update_docs(self, docs): + for doc in docs: + doc.save() diff --git a/src/DjangoBlog-master/blog/forms.py b/src/DjangoBlog-master/blog/forms.py new file mode 100644 index 0000000..715be76 --- /dev/null +++ b/src/DjangoBlog-master/blog/forms.py @@ -0,0 +1,19 @@ +import logging + +from django import forms +from haystack.forms import SearchForm + +logger = logging.getLogger(__name__) + + +class BlogSearchForm(SearchForm): + querydata = forms.CharField(required=True) + + def search(self): + datas = super(BlogSearchForm, self).search() + if not self.is_valid(): + return self.no_query_found() + + if self.cleaned_data['querydata']: + logger.info(self.cleaned_data['querydata']) + return datas diff --git a/src/DjangoBlog-master/blog/management/__init__.py b/src/DjangoBlog-master/blog/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/blog/management/commands/__init__.py b/src/DjangoBlog-master/blog/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/blog/management/commands/build_index.py b/src/DjangoBlog-master/blog/management/commands/build_index.py new file mode 100644 index 0000000..3c4acd7 --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/build_index.py @@ -0,0 +1,18 @@ +from django.core.management.base import BaseCommand + +from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \ + ELASTICSEARCH_ENABLED + + +# TODO 参数化 +class Command(BaseCommand): + help = 'build search index' + + def handle(self, *args, **options): + if ELASTICSEARCH_ENABLED: + ElaspedTimeDocumentManager.build_index() + manager = ElapsedTimeDocument() + manager.init() + manager = ArticleDocumentManager() + manager.delete_index() + manager.rebuild() diff --git a/src/DjangoBlog-master/blog/management/commands/build_search_words.py b/src/DjangoBlog-master/blog/management/commands/build_search_words.py new file mode 100644 index 0000000..cfe7e0d --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/build_search_words.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand + +from blog.models import Tag, Category + + +# TODO 参数化 +class Command(BaseCommand): + help = 'build search words' + + def handle(self, *args, **options): + datas = set([t.name for t in Tag.objects.all()] + + [t.name for t in Category.objects.all()]) + print('\n'.join(datas)) diff --git a/src/DjangoBlog-master/blog/management/commands/clear_cache.py b/src/DjangoBlog-master/blog/management/commands/clear_cache.py new file mode 100644 index 0000000..0d66172 --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/clear_cache.py @@ -0,0 +1,11 @@ +from django.core.management.base import BaseCommand + +from djangoblog.utils import cache + + +class Command(BaseCommand): + help = 'clear the whole cache' + + def handle(self, *args, **options): + cache.clear() + self.stdout.write(self.style.SUCCESS('Cleared cache\n')) diff --git a/src/DjangoBlog-master/blog/management/commands/create_testdata.py b/src/DjangoBlog-master/blog/management/commands/create_testdata.py new file mode 100644 index 0000000..675d2ba --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/create_testdata.py @@ -0,0 +1,40 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.hashers import make_password +from django.core.management.base import BaseCommand + +from blog.models import Article, Tag, Category + + +class Command(BaseCommand): + help = 'create test datas' + + def handle(self, *args, **options): + user = get_user_model().objects.get_or_create( + email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] + + pcategory = Category.objects.get_or_create( + name='我是父类目', parent_category=None)[0] + + category = Category.objects.get_or_create( + name='子类目', parent_category=pcategory)[0] + + category.save() + basetag = Tag() + basetag.name = "标签" + basetag.save() + for i in range(1, 20): + article = Article.objects.get_or_create( + category=category, + title='nice title ' + str(i), + body='nice content ' + str(i), + author=user)[0] + tag = Tag() + tag.name = "标签" + str(i) + tag.save() + article.tags.add(tag) + article.tags.add(basetag) + article.save() + + from djangoblog.utils import cache + cache.clear() + self.stdout.write(self.style.SUCCESS('created test datas \n')) diff --git a/src/DjangoBlog-master/blog/management/commands/ping_baidu.py b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py new file mode 100644 index 0000000..2c7fbdd --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py @@ -0,0 +1,50 @@ +from django.core.management.base import BaseCommand + +from djangoblog.spider_notify import SpiderNotify +from djangoblog.utils import get_current_site +from blog.models import Article, Tag, Category + +site = get_current_site().domain + + +class Command(BaseCommand): + help = 'notify baidu url' + + def add_arguments(self, parser): + parser.add_argument( + 'data_type', + type=str, + choices=[ + 'all', + 'article', + 'tag', + 'category'], + help='article : all article,tag : all tag,category: all category,all: All of these') + + def get_full_url(self, path): + url = "https://{site}{path}".format(site=site, path=path) + return url + + def handle(self, *args, **options): + type = options['data_type'] + self.stdout.write('start get %s' % type) + + urls = [] + if type == 'article' or type == 'all': + for article in Article.objects.filter(status='p'): + urls.append(article.get_full_url()) + if type == 'tag' or type == 'all': + for tag in Tag.objects.all(): + url = tag.get_absolute_url() + urls.append(self.get_full_url(url)) + if type == 'category' or type == 'all': + for category in Category.objects.all(): + url = category.get_absolute_url() + urls.append(self.get_full_url(url)) + + self.stdout.write( + self.style.SUCCESS( + 'start notify %d urls' % + len(urls))) + SpiderNotify.baidu_notify(urls) + self.stdout.write(self.style.SUCCESS('finish notify')) diff --git a/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py new file mode 100644 index 0000000..d0f4612 --- /dev/null +++ b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py @@ -0,0 +1,47 @@ +import requests +from django.core.management.base import BaseCommand +from django.templatetags.static import static + +from djangoblog.utils import save_user_avatar +from oauth.models import OAuthUser +from oauth.oauthmanager import get_manager_by_type + + +class Command(BaseCommand): + help = 'sync user avatar' + + def test_picture(self, url): + try: + if requests.get(url, timeout=2).status_code == 200: + return True + except: + pass + + def handle(self, *args, **options): + static_url = static("../") + users = OAuthUser.objects.all() + self.stdout.write(f'开始同步{len(users)}个用户头像') + for u in users: + self.stdout.write(f'开始同步:{u.nickname}') + url = u.picture + if url: + if url.startswith(static_url): + if self.test_picture(url): + continue + else: + if u.metadata: + manage = get_manager_by_type(u.type) + url = manage.get_picture(u.metadata) + url = save_user_avatar(url) + else: + url = static('blog/img/avatar.png') + else: + url = save_user_avatar(url) + else: + url = static('blog/img/avatar.png') + if url: + self.stdout.write( + f'结束同步:{u.nickname}.url:{url}') + u.picture = url + u.save() + self.stdout.write('结束同步') diff --git a/src/DjangoBlog-master/blog/middleware.py b/src/DjangoBlog-master/blog/middleware.py new file mode 100644 index 0000000..94dd70c --- /dev/null +++ b/src/DjangoBlog-master/blog/middleware.py @@ -0,0 +1,42 @@ +import logging +import time + +from ipware import get_client_ip +from user_agents import parse + +from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager + +logger = logging.getLogger(__name__) + + +class OnlineMiddleware(object): + def __init__(self, get_response=None): + self.get_response = get_response + super().__init__() + + def __call__(self, request): + ''' page render time ''' + start_time = time.time() + response = self.get_response(request) + http_user_agent = request.META.get('HTTP_USER_AGENT', '') + ip, _ = get_client_ip(request) + user_agent = parse(http_user_agent) + if not response.streaming: + try: + cast_time = time.time() - start_time + if ELASTICSEARCH_ENABLED: + time_taken = round((cast_time) * 1000, 2) + url = request.path + from django.utils import timezone + ElaspedTimeDocumentManager.create( + url=url, + time_taken=time_taken, + log_datetime=timezone.now(), + useragent=user_agent, + ip=ip) + response.content = response.content.replace( + b'', str.encode(str(cast_time)[:5])) + except Exception as e: + logger.error("Error OnlineMiddleware: %s" % e) + + return response diff --git a/src/DjangoBlog-master/blog/migrations/0001_initial.py b/src/DjangoBlog-master/blog/migrations/0001_initial.py new file mode 100644 index 0000000..3d391b6 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0001_initial.py @@ -0,0 +1,137 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import mdeditor.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='BlogSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), + ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), + ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), + ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), + ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), + ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), + ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), + ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), + ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), + ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), + ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), + ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), + ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), + ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), + ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), + ], + options={ + 'verbose_name': '网站配置', + 'verbose_name_plural': '网站配置', + }, + ), + migrations.CreateModel( + name='Links', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), + ('link', models.URLField(verbose_name='链接地址')), + ('sequence', models.IntegerField(unique=True, verbose_name='排序')), + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), + ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ], + options={ + 'verbose_name': '友情链接', + 'verbose_name_plural': '友情链接', + 'ordering': ['sequence'], + }, + ), + migrations.CreateModel( + name='SideBar', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='标题')), + ('content', models.TextField(verbose_name='内容')), + ('sequence', models.IntegerField(unique=True, verbose_name='排序')), + ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ], + options={ + 'verbose_name': '侧边栏', + 'verbose_name_plural': '侧边栏', + 'ordering': ['sequence'], + }, + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), + ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), + ], + options={ + 'verbose_name': '标签', + 'verbose_name_plural': '标签', + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), + ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), + ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), + ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), + ], + options={ + 'verbose_name': '分类', + 'verbose_name_plural': '分类', + 'ordering': ['-index'], + }, + ), + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), + ('body', mdeditor.fields.MDTextField(verbose_name='正文')), + ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), + ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), + ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), + ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), + ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), + ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), + ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), + ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), + ], + options={ + 'verbose_name': '文章', + 'verbose_name_plural': '文章', + 'ordering': ['-article_order', '-pub_time'], + 'get_latest_by': 'id', + }, + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py new file mode 100644 index 0000000..adbaa36 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.7 on 2023-03-29 06:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='blogsettings', + name='global_footer', + field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), + ), + migrations.AddField( + model_name='blogsettings', + name='global_header', + field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py new file mode 100644 index 0000000..e9f5502 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2023-05-09 07:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('blog', '0002_blogsettings_global_footer_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='blogsettings', + name='comment_need_review', + field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py new file mode 100644 index 0000000..ceb1398 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.1 on 2023-05-09 07:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('blog', '0003_blogsettings_comment_need_review'), + ] + + operations = [ + migrations.RenameField( + model_name='blogsettings', + old_name='analyticscode', + new_name='analytics_code', + ), + migrations.RenameField( + model_name='blogsettings', + old_name='beiancode', + new_name='beian_code', + ), + migrations.RenameField( + model_name='blogsettings', + old_name='sitename', + new_name='site_name', + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py new file mode 100644 index 0000000..d08e853 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py @@ -0,0 +1,300 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:13 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import mdeditor.fields + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='article', + options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, + ), + migrations.AlterModelOptions( + name='category', + options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, + ), + migrations.AlterModelOptions( + name='links', + options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, + ), + migrations.AlterModelOptions( + name='sidebar', + options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, + ), + migrations.AlterModelOptions( + name='tag', + options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, + ), + migrations.RemoveField( + model_name='article', + name='created_time', + ), + migrations.RemoveField( + model_name='article', + name='last_mod_time', + ), + migrations.RemoveField( + model_name='category', + name='created_time', + ), + migrations.RemoveField( + model_name='category', + name='last_mod_time', + ), + migrations.RemoveField( + model_name='links', + name='created_time', + ), + migrations.RemoveField( + model_name='sidebar', + name='created_time', + ), + migrations.RemoveField( + model_name='tag', + name='created_time', + ), + migrations.RemoveField( + model_name='tag', + name='last_mod_time', + ), + migrations.AddField( + model_name='article', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='article', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + ), + migrations.AddField( + model_name='category', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='category', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + ), + migrations.AddField( + model_name='links', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='sidebar', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='tag', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='tag', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + ), + migrations.AlterField( + model_name='article', + name='article_order', + field=models.IntegerField(default=0, verbose_name='order'), + ), + migrations.AlterField( + model_name='article', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), + ), + migrations.AlterField( + model_name='article', + name='body', + field=mdeditor.fields.MDTextField(verbose_name='body'), + ), + migrations.AlterField( + model_name='article', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), + ), + migrations.AlterField( + model_name='article', + name='comment_status', + field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), + ), + migrations.AlterField( + model_name='article', + name='pub_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), + ), + migrations.AlterField( + model_name='article', + name='show_toc', + field=models.BooleanField(default=False, verbose_name='show toc'), + ), + migrations.AlterField( + model_name='article', + name='status', + field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), + ), + migrations.AlterField( + model_name='article', + name='tags', + field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), + ), + migrations.AlterField( + model_name='article', + name='title', + field=models.CharField(max_length=200, unique=True, verbose_name='title'), + ), + migrations.AlterField( + model_name='article', + name='type', + field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), + ), + migrations.AlterField( + model_name='article', + name='views', + field=models.PositiveIntegerField(default=0, verbose_name='views'), + ), + migrations.AlterField( + model_name='blogsettings', + name='article_comment_count', + field=models.IntegerField(default=5, verbose_name='article comment count'), + ), + migrations.AlterField( + model_name='blogsettings', + name='article_sub_length', + field=models.IntegerField(default=300, verbose_name='article sub length'), + ), + migrations.AlterField( + model_name='blogsettings', + name='google_adsense_codes', + field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), + ), + migrations.AlterField( + model_name='blogsettings', + name='open_site_comment', + field=models.BooleanField(default=True, verbose_name='open site comment'), + ), + migrations.AlterField( + model_name='blogsettings', + name='show_google_adsense', + field=models.BooleanField(default=False, verbose_name='show adsense'), + ), + migrations.AlterField( + model_name='blogsettings', + name='sidebar_article_count', + field=models.IntegerField(default=10, verbose_name='sidebar article count'), + ), + migrations.AlterField( + model_name='blogsettings', + name='sidebar_comment_count', + field=models.IntegerField(default=5, verbose_name='sidebar comment count'), + ), + migrations.AlterField( + model_name='blogsettings', + name='site_description', + field=models.TextField(default='', max_length=1000, verbose_name='site description'), + ), + migrations.AlterField( + model_name='blogsettings', + name='site_keywords', + field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), + ), + migrations.AlterField( + model_name='blogsettings', + name='site_name', + field=models.CharField(default='', max_length=200, verbose_name='site name'), + ), + migrations.AlterField( + model_name='blogsettings', + name='site_seo_description', + field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), + ), + migrations.AlterField( + model_name='category', + name='index', + field=models.IntegerField(default=0, verbose_name='index'), + ), + migrations.AlterField( + model_name='category', + name='name', + field=models.CharField(max_length=30, unique=True, verbose_name='category name'), + ), + migrations.AlterField( + model_name='category', + name='parent_category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'), + ), + migrations.AlterField( + model_name='links', + name='is_enable', + field=models.BooleanField(default=True, verbose_name='is show'), + ), + migrations.AlterField( + model_name='links', + name='last_mod_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + ), + migrations.AlterField( + model_name='links', + name='link', + field=models.URLField(verbose_name='link'), + ), + migrations.AlterField( + model_name='links', + name='name', + field=models.CharField(max_length=30, unique=True, verbose_name='link name'), + ), + migrations.AlterField( + model_name='links', + name='sequence', + field=models.IntegerField(unique=True, verbose_name='order'), + ), + migrations.AlterField( + model_name='links', + name='show_type', + field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'), + ), + migrations.AlterField( + model_name='sidebar', + name='content', + field=models.TextField(verbose_name='content'), + ), + migrations.AlterField( + model_name='sidebar', + name='is_enable', + field=models.BooleanField(default=True, verbose_name='is enable'), + ), + migrations.AlterField( + model_name='sidebar', + name='last_mod_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + ), + migrations.AlterField( + model_name='sidebar', + name='name', + field=models.CharField(max_length=100, verbose_name='title'), + ), + migrations.AlterField( + model_name='sidebar', + name='sequence', + field=models.IntegerField(unique=True, verbose_name='order'), + ), + migrations.AlterField( + model_name='tag', + name='name', + field=models.CharField(max_length=30, unique=True, verbose_name='tag name'), + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py new file mode 100644 index 0000000..e36feb4 --- /dev/null +++ b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2024-01-26 02:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0005_alter_article_options_alter_category_options_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='blogsettings', + options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, + ), + ] diff --git a/src/DjangoBlog-master/blog/migrations/__init__.py b/src/DjangoBlog-master/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/blog/models.py new file mode 100644 index 0000000..083788b --- /dev/null +++ b/src/DjangoBlog-master/blog/models.py @@ -0,0 +1,376 @@ +import logging +import re +from abc import abstractmethod + +from django.conf import settings +from django.core.exceptions import ValidationError +from django.db import models +from django.urls import reverse +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +from mdeditor.fields import MDTextField +from uuslug import slugify + +from djangoblog.utils import cache_decorator, cache +from djangoblog.utils import get_current_site + +logger = logging.getLogger(__name__) + + +class LinkShowType(models.TextChoices): + I = ('i', _('index')) + L = ('l', _('list')) + P = ('p', _('post')) + A = ('a', _('all')) + S = ('s', _('slide')) + + +class BaseModel(models.Model): + id = models.AutoField(primary_key=True) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('modify time'), default=now) + + def save(self, *args, **kwargs): + is_update_views = isinstance( + self, + Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] + if is_update_views: + Article.objects.filter(pk=self.pk).update(views=self.views) + else: + if 'slug' in self.__dict__: + slug = getattr( + self, 'title') if 'title' in self.__dict__ else getattr( + self, 'name') + setattr(self, 'slug', slugify(slug)) + super().save(*args, **kwargs) + + def get_full_url(self): + site = get_current_site().domain + url = "https://{site}{path}".format(site=site, + path=self.get_absolute_url()) + return url + + class Meta: + abstract = True + + @abstractmethod + def get_absolute_url(self): + pass + + +class Article(BaseModel): + """文章""" + STATUS_CHOICES = ( + ('d', _('Draft')), + ('p', _('Published')), + ) + COMMENT_STATUS = ( + ('o', _('Open')), + ('c', _('Close')), + ) + TYPE = ( + ('a', _('Article')), + ('p', _('Page')), + ) + title = models.CharField(_('title'), max_length=200, unique=True) + body = MDTextField(_('body')) + pub_time = models.DateTimeField( + _('publish time'), blank=False, null=False, default=now) + status = models.CharField( + _('status'), + max_length=1, + choices=STATUS_CHOICES, + default='p') + comment_status = models.CharField( + _('comment status'), + max_length=1, + choices=COMMENT_STATUS, + default='o') + type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') + views = models.PositiveIntegerField(_('views'), default=0) + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + blank=False, + null=False, + on_delete=models.CASCADE) + article_order = models.IntegerField( + _('order'), blank=False, null=False, default=0) + show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) + category = models.ForeignKey( + 'Category', + verbose_name=_('category'), + on_delete=models.CASCADE, + blank=False, + null=False) + tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) + + def body_to_string(self): + return self.body + + def __str__(self): + return self.title + + class Meta: + ordering = ['-article_order', '-pub_time'] + verbose_name = _('article') + verbose_name_plural = verbose_name + get_latest_by = 'id' + + def get_absolute_url(self): + return reverse('blog:detailbyid', kwargs={ + 'article_id': self.id, + 'year': self.creation_time.year, + 'month': self.creation_time.month, + 'day': self.creation_time.day + }) + + @cache_decorator(60 * 60 * 10) + def get_category_tree(self): + tree = self.category.get_category_tree() + names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) + + return names + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + + def viewed(self): + self.views += 1 + self.save(update_fields=['views']) + + def comment_list(self): + cache_key = 'article_comments_{id}'.format(id=self.id) + value = cache.get(cache_key) + if value: + logger.info('get article comments:{id}'.format(id=self.id)) + return value + else: + comments = self.comment_set.filter(is_enable=True).order_by('-id') + cache.set(cache_key, comments, 60 * 100) + logger.info('set article comments:{id}'.format(id=self.id)) + return comments + + def get_admin_url(self): + info = (self._meta.app_label, self._meta.model_name) + return reverse('admin:%s_%s_change' % info, args=(self.pk,)) + + @cache_decorator(expiration=60 * 100) + def next_article(self): + # 下一篇 + return Article.objects.filter( + id__gt=self.id, status='p').order_by('id').first() + + @cache_decorator(expiration=60 * 100) + def prev_article(self): + # 前一篇 + return Article.objects.filter(id__lt=self.id, status='p').first() + + def get_first_image_url(self): + """ + Get the first image url from article.body. + :return: + """ + match = re.search(r'!\[.*?\]\((.+?)\)', self.body) + if match: + return match.group(1) + return "" + + +class Category(BaseModel): + """文章分类""" + name = models.CharField(_('category name'), max_length=30, unique=True) + parent_category = models.ForeignKey( + 'self', + verbose_name=_('parent category'), + blank=True, + null=True, + on_delete=models.CASCADE) + slug = models.SlugField(default='no-slug', max_length=60, blank=True) + index = models.IntegerField(default=0, verbose_name=_('index')) + + class Meta: + ordering = ['-index'] + verbose_name = _('category') + verbose_name_plural = verbose_name + + def get_absolute_url(self): + return reverse( + 'blog:category_detail', kwargs={ + 'category_name': self.slug}) + + def __str__(self): + return self.name + + @cache_decorator(60 * 60 * 10) + def get_category_tree(self): + """ + 递归获得分类目录的父级 + :return: + """ + categorys = [] + + def parse(category): + categorys.append(category) + if category.parent_category: + parse(category.parent_category) + + parse(self) + return categorys + + @cache_decorator(60 * 60 * 10) + def get_sub_categorys(self): + """ + 获得当前分类目录所有子集 + :return: + """ + categorys = [] + all_categorys = Category.objects.all() + + def parse(category): + if category not in categorys: + categorys.append(category) + childs = all_categorys.filter(parent_category=category) + for child in childs: + if category not in categorys: + categorys.append(child) + parse(child) + + parse(self) + return categorys + + +class Tag(BaseModel): + """文章标签""" + name = models.CharField(_('tag name'), max_length=30, unique=True) + slug = models.SlugField(default='no-slug', max_length=60, blank=True) + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) + + @cache_decorator(60 * 60 * 10) + def get_article_count(self): + return Article.objects.filter(tags__name=self.name).distinct().count() + + class Meta: + ordering = ['name'] + verbose_name = _('tag') + verbose_name_plural = verbose_name + + +class Links(models.Model): + """友情链接""" + + name = models.CharField(_('link name'), max_length=30, unique=True) + link = models.URLField(_('link')) + sequence = models.IntegerField(_('order'), unique=True) + is_enable = models.BooleanField( + _('is show'), default=True, blank=False, null=False) + show_type = models.CharField( + _('show type'), + max_length=1, + choices=LinkShowType.choices, + default=LinkShowType.I) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_mod_time = models.DateTimeField(_('modify time'), default=now) + + class Meta: + ordering = ['sequence'] + verbose_name = _('link') + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class SideBar(models.Model): + """侧边栏,可以展示一些html内容""" + name = models.CharField(_('title'), max_length=100) + content = models.TextField(_('content')) + sequence = models.IntegerField(_('order'), unique=True) + is_enable = models.BooleanField(_('is enable'), default=True) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_mod_time = models.DateTimeField(_('modify time'), default=now) + + class Meta: + ordering = ['sequence'] + verbose_name = _('sidebar') + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class BlogSettings(models.Model): + """blog的配置""" + site_name = models.CharField( + _('site name'), + max_length=200, + null=False, + blank=False, + default='') + site_description = models.TextField( + _('site description'), + max_length=1000, + null=False, + blank=False, + default='') + site_seo_description = models.TextField( + _('site seo description'), max_length=1000, null=False, blank=False, default='') + site_keywords = models.TextField( + _('site keywords'), + max_length=1000, + null=False, + blank=False, + default='') + article_sub_length = models.IntegerField(_('article sub length'), default=300) + sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) + sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) + article_comment_count = models.IntegerField(_('article comment count'), default=5) + show_google_adsense = models.BooleanField(_('show adsense'), default=False) + google_adsense_codes = models.TextField( + _('adsense code'), max_length=2000, null=True, blank=True, default='') + open_site_comment = models.BooleanField(_('open site comment'), default=True) + global_header = models.TextField("公共头部", null=True, blank=True, default='') + global_footer = models.TextField("公共尾部", null=True, blank=True, default='') + beian_code = models.CharField( + '备案号', + max_length=2000, + null=True, + blank=True, + default='') + analytics_code = models.TextField( + "网站统计代码", + max_length=1000, + null=False, + blank=False, + default='') + show_gongan_code = models.BooleanField( + '是否显示公安备案号', default=False, null=False) + gongan_beiancode = models.TextField( + '公安备案号', + max_length=2000, + null=True, + blank=True, + default='') + comment_need_review = models.BooleanField( + '评论是否需要审核', default=False, null=False) + + class Meta: + verbose_name = _('Website configuration') + verbose_name_plural = verbose_name + + def __str__(self): + return self.site_name + + def clean(self): + if BlogSettings.objects.exclude(id=self.id).count(): + raise ValidationError(_('There can only be one configuration')) + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + from djangoblog.utils import cache + cache.clear() diff --git a/src/DjangoBlog-master/blog/search_indexes.py b/src/DjangoBlog-master/blog/search_indexes.py new file mode 100644 index 0000000..7f1dfac --- /dev/null +++ b/src/DjangoBlog-master/blog/search_indexes.py @@ -0,0 +1,13 @@ +from haystack import indexes + +from blog.models import Article + + +class ArticleIndex(indexes.SearchIndex, indexes.Indexable): + text = indexes.CharField(document=True, use_template=True) + + def get_model(self): + return Article + + def index_queryset(self, using=None): + return self.get_model().objects.filter(status='p') diff --git a/src/DjangoBlog-master/blog/templatetags/__init__.py b/src/DjangoBlog-master/blog/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/blog/templatetags/blog_tags.py new file mode 100644 index 0000000..d6cd5d5 --- /dev/null +++ b/src/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -0,0 +1,344 @@ +import hashlib +import logging +import random +import urllib + +from django import template +from django.conf import settings +from django.db.models import Q +from django.shortcuts import get_object_or_404 +from django.template.defaultfilters import stringfilter +from django.templatetags.static import static +from django.urls import reverse +from django.utils.safestring import mark_safe + +from blog.models import Article, Category, Tag, Links, SideBar, LinkShowType +from comments.models import Comment +from djangoblog.utils import CommonMarkdown, sanitize_html +from djangoblog.utils import cache +from djangoblog.utils import get_current_site +from oauth.models import OAuthUser +from djangoblog.plugin_manage import hooks + +logger = logging.getLogger(__name__) + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def head_meta(context): + return mark_safe(hooks.apply_filters('head_meta', '', context)) + + +@register.simple_tag +def timeformat(data): + try: + return data.strftime(settings.TIME_FORMAT) + except Exception as e: + logger.error(e) + return "" + + +@register.simple_tag +def datetimeformat(data): + try: + return data.strftime(settings.DATE_TIME_FORMAT) + except Exception as e: + logger.error(e) + return "" + + +@register.filter() +@stringfilter +def custom_markdown(content): + return mark_safe(CommonMarkdown.get_markdown(content)) + + +@register.simple_tag +def get_markdown_toc(content): + from djangoblog.utils import CommonMarkdown + body, toc = CommonMarkdown.get_markdown_with_toc(content) + return mark_safe(toc) + + +@register.filter() +@stringfilter +def comment_markdown(content): + content = CommonMarkdown.get_markdown(content) + return mark_safe(sanitize_html(content)) + + +@register.filter(is_safe=True) +@stringfilter +def truncatechars_content(content): + """ + 获得文章内容的摘要 + :param content: + :return: + """ + from django.template.defaultfilters import truncatechars_html + from djangoblog.utils import get_blog_setting + blogsetting = get_blog_setting() + return truncatechars_html(content, blogsetting.article_sub_length) + + +@register.filter(is_safe=True) +@stringfilter +def truncate(content): + from django.utils.html import strip_tags + + return strip_tags(content)[:150] + + +@register.inclusion_tag('blog/tags/breadcrumb.html') +def load_breadcrumb(article): + """ + 获得文章面包屑 + :param article: + :return: + """ + names = article.get_category_tree() + from djangoblog.utils import get_blog_setting + blogsetting = get_blog_setting() + site = get_current_site().domain + names.append((blogsetting.site_name, '/')) + names = names[::-1] + + return { + 'names': names, + 'title': article.title, + 'count': len(names) + 1 + } + + +@register.inclusion_tag('blog/tags/article_tag_list.html') +def load_articletags(article): + """ + 文章标签 + :param article: + :return: + """ + tags = article.tags.all() + tags_list = [] + for tag in tags: + url = tag.get_absolute_url() + count = tag.get_article_count() + tags_list.append(( + url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) + )) + return { + 'article_tags_list': tags_list + } + + +@register.inclusion_tag('blog/tags/sidebar.html') +def load_sidebar(user, linktype): + """ + 加载侧边栏 + :return: + """ + value = cache.get("sidebar" + linktype) + if value: + value['user'] = user + return value + else: + logger.info('load sidebar') + from djangoblog.utils import get_blog_setting + blogsetting = get_blog_setting() + recent_articles = Article.objects.filter( + status='p')[:blogsetting.sidebar_article_count] + sidebar_categorys = Category.objects.all() + extra_sidebars = SideBar.objects.filter( + is_enable=True).order_by('sequence') + most_read_articles = Article.objects.filter(status='p').order_by( + '-views')[:blogsetting.sidebar_article_count] + dates = Article.objects.datetimes('creation_time', 'month', order='DESC') + links = Links.objects.filter(is_enable=True).filter( + Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) + commment_list = Comment.objects.filter(is_enable=True).order_by( + '-id')[:blogsetting.sidebar_comment_count] + # 标签云 计算字体大小 + # 根据总数计算出平均值 大小为 (数目/平均值)*步长 + increment = 5 + tags = Tag.objects.all() + sidebar_tags = None + if tags and len(tags) > 0: + s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]] + count = sum([t[1] for t in s]) + dd = 1 if (count == 0 or not len(tags)) else count / len(tags) + import random + sidebar_tags = list( + map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) + random.shuffle(sidebar_tags) + + value = { + 'recent_articles': recent_articles, + 'sidebar_categorys': sidebar_categorys, + 'most_read_articles': most_read_articles, + 'article_dates': dates, + 'sidebar_comments': commment_list, + 'sidabar_links': links, + 'show_google_adsense': blogsetting.show_google_adsense, + 'google_adsense_codes': blogsetting.google_adsense_codes, + 'open_site_comment': blogsetting.open_site_comment, + 'show_gongan_code': blogsetting.show_gongan_code, + 'sidebar_tags': sidebar_tags, + 'extra_sidebars': extra_sidebars + } + cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) + logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) + value['user'] = user + return value + + +@register.inclusion_tag('blog/tags/article_meta_info.html') +def load_article_metas(article, user): + """ + 获得文章meta信息 + :param article: + :return: + """ + return { + 'article': article, + 'user': user + } + + +@register.inclusion_tag('blog/tags/article_pagination.html') +def load_pagination_info(page_obj, page_type, tag_name): + previous_url = '' + next_url = '' + if page_type == '': + if page_obj.has_next(): + next_number = page_obj.next_page_number() + next_url = reverse('blog:index_page', kwargs={'page': next_number}) + if page_obj.has_previous(): + previous_number = page_obj.previous_page_number() + previous_url = reverse( + 'blog:index_page', kwargs={ + 'page': previous_number}) + if page_type == '分类标签归档': + tag = get_object_or_404(Tag, name=tag_name) + if page_obj.has_next(): + next_number = page_obj.next_page_number() + next_url = reverse( + 'blog:tag_detail_page', + kwargs={ + 'page': next_number, + 'tag_name': tag.slug}) + if page_obj.has_previous(): + previous_number = page_obj.previous_page_number() + previous_url = reverse( + 'blog:tag_detail_page', + kwargs={ + 'page': previous_number, + 'tag_name': tag.slug}) + if page_type == '作者文章归档': + if page_obj.has_next(): + next_number = page_obj.next_page_number() + next_url = reverse( + 'blog:author_detail_page', + kwargs={ + 'page': next_number, + 'author_name': tag_name}) + if page_obj.has_previous(): + previous_number = page_obj.previous_page_number() + previous_url = reverse( + 'blog:author_detail_page', + kwargs={ + 'page': previous_number, + 'author_name': tag_name}) + + if page_type == '分类目录归档': + category = get_object_or_404(Category, name=tag_name) + if page_obj.has_next(): + next_number = page_obj.next_page_number() + next_url = reverse( + 'blog:category_detail_page', + kwargs={ + 'page': next_number, + 'category_name': category.slug}) + if page_obj.has_previous(): + previous_number = page_obj.previous_page_number() + previous_url = reverse( + 'blog:category_detail_page', + kwargs={ + 'page': previous_number, + 'category_name': category.slug}) + + return { + 'previous_url': previous_url, + 'next_url': next_url, + 'page_obj': page_obj + } + + +@register.inclusion_tag('blog/tags/article_info.html') +def load_article_detail(article, isindex, user): + """ + 加载文章详情 + :param article: + :param isindex:是否列表页,若是列表页只显示摘要 + :return: + """ + from djangoblog.utils import get_blog_setting + blogsetting = get_blog_setting() + + return { + 'article': article, + 'isindex': isindex, + 'user': user, + 'open_site_comment': blogsetting.open_site_comment, + } + + +# return only the URL of the gravatar +# TEMPLATE USE: {{ email|gravatar_url:150 }} +@register.filter +def gravatar_url(email, size=40): + """获得gravatar头像""" + cachekey = 'gravatat/' + email + url = cache.get(cachekey) + if url: + return url + else: + usermodels = OAuthUser.objects.filter(email=email) + if usermodels: + o = list(filter(lambda x: x.picture is not None, usermodels)) + if o: + return o[0].picture + email = email.encode('utf-8') + + default = static('blog/img/avatar.png') + + url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( + email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) + cache.set(cachekey, url, 60 * 60 * 10) + logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) + return url + + +@register.filter +def gravatar(email, size=40): + """获得gravatar头像""" + url = gravatar_url(email, size) + return mark_safe( + '' % + (url, size, size)) + + +@register.simple_tag +def query(qs, **kwargs): + """ template tag which allows queryset filtering. Usage: + {% query books author=author as mybooks %} + {% for book in mybooks %} + ... + {% endfor %} + """ + return qs.filter(**kwargs) + + +@register.filter +def addstr(arg1, arg2): + """concatenate arg1 & arg2""" + return str(arg1) + str(arg2) diff --git a/src/DjangoBlog-master/blog/tests.py b/src/DjangoBlog-master/blog/tests.py new file mode 100644 index 0000000..ee13505 --- /dev/null +++ b/src/DjangoBlog-master/blog/tests.py @@ -0,0 +1,232 @@ +import os + +from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.management import call_command +from django.core.paginator import Paginator +from django.templatetags.static import static +from django.test import Client, RequestFactory, TestCase +from django.urls import reverse +from django.utils import timezone + +from accounts.models import BlogUser +from blog.forms import BlogSearchForm +from blog.models import Article, Category, Tag, SideBar, Links +from blog.templatetags.blog_tags import load_pagination_info, load_articletags +from djangoblog.utils import get_current_site, get_sha256 +from oauth.models import OAuthUser, OAuthConfig + + +# Create your tests here. + +class ArticleTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + + def test_validate_article(self): + site = get_current_site().domain + user = BlogUser.objects.get_or_create( + email="liangliangyy@gmail.com", + username="liangliangyy")[0] + user.set_password("liangliangyy") + user.is_staff = True + user.is_superuser = True + user.save() + response = self.client.get(user.get_absolute_url()) + self.assertEqual(response.status_code, 200) + response = self.client.get('/admin/servermanager/emailsendlog/') + response = self.client.get('admin/admin/logentry/') + s = SideBar() + s.sequence = 1 + s.name = 'test' + s.content = 'test content' + s.is_enable = True + s.save() + + category = Category() + category.name = "category" + category.creation_time = timezone.now() + category.last_mod_time = timezone.now() + category.save() + + tag = Tag() + tag.name = "nicetag" + tag.save() + + article = Article() + article.title = "nicetitle" + article.body = "nicecontent" + article.author = user + article.category = category + article.type = 'a' + article.status = 'p' + + article.save() + self.assertEqual(0, article.tags.count()) + article.tags.add(tag) + article.save() + self.assertEqual(1, article.tags.count()) + + for i in range(20): + article = Article() + article.title = "nicetitle" + str(i) + article.body = "nicetitle" + str(i) + article.author = user + article.category = category + article.type = 'a' + article.status = 'p' + article.save() + article.tags.add(tag) + article.save() + from blog.documents import ELASTICSEARCH_ENABLED + if ELASTICSEARCH_ENABLED: + call_command("build_index") + response = self.client.get('/search', {'q': 'nicetitle'}) + self.assertEqual(response.status_code, 200) + + response = self.client.get(article.get_absolute_url()) + self.assertEqual(response.status_code, 200) + from djangoblog.spider_notify import SpiderNotify + SpiderNotify.notify(article.get_absolute_url()) + response = self.client.get(tag.get_absolute_url()) + self.assertEqual(response.status_code, 200) + + response = self.client.get(category.get_absolute_url()) + self.assertEqual(response.status_code, 200) + + response = self.client.get('/search', {'q': 'django'}) + self.assertEqual(response.status_code, 200) + s = load_articletags(article) + self.assertIsNotNone(s) + + self.client.login(username='liangliangyy', password='liangliangyy') + + response = self.client.get(reverse('blog:archives')) + self.assertEqual(response.status_code, 200) + + p = Paginator(Article.objects.all(), settings.PAGINATE_BY) + self.check_pagination(p, '', '') + + p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY) + self.check_pagination(p, '分类标签归档', tag.slug) + + p = Paginator( + Article.objects.filter( + author__username='liangliangyy'), settings.PAGINATE_BY) + self.check_pagination(p, '作者文章归档', 'liangliangyy') + + p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY) + self.check_pagination(p, '分类目录归档', category.slug) + + f = BlogSearchForm() + f.search() + # self.client.login(username='liangliangyy', password='liangliangyy') + from djangoblog.spider_notify import SpiderNotify + SpiderNotify.baidu_notify([article.get_full_url()]) + + from blog.templatetags.blog_tags import gravatar_url, gravatar + u = gravatar_url('liangliangyy@gmail.com') + u = gravatar('liangliangyy@gmail.com') + + link = Links( + sequence=1, + name="lylinux", + link='https://wwww.lylinux.net') + link.save() + response = self.client.get('/links.html') + self.assertEqual(response.status_code, 200) + + response = self.client.get('/feed/') + self.assertEqual(response.status_code, 200) + + response = self.client.get('/sitemap.xml') + self.assertEqual(response.status_code, 200) + + self.client.get("/admin/blog/article/1/delete/") + self.client.get('/admin/servermanager/emailsendlog/') + self.client.get('/admin/admin/logentry/') + self.client.get('/admin/admin/logentry/1/change/') + + def check_pagination(self, p, type, value): + for page in range(1, p.num_pages + 1): + s = load_pagination_info(p.page(page), type, value) + self.assertIsNotNone(s) + if s['previous_url']: + response = self.client.get(s['previous_url']) + self.assertEqual(response.status_code, 200) + if s['next_url']: + response = self.client.get(s['next_url']) + self.assertEqual(response.status_code, 200) + + def test_image(self): + import requests + rsp = requests.get( + 'https://www.python.org/static/img/python-logo.png') + imagepath = os.path.join(settings.BASE_DIR, 'python.png') + with open(imagepath, 'wb') as file: + file.write(rsp.content) + rsp = self.client.post('/upload') + self.assertEqual(rsp.status_code, 403) + sign = get_sha256(get_sha256(settings.SECRET_KEY)) + with open(imagepath, 'rb') as file: + imgfile = SimpleUploadedFile( + 'python.png', file.read(), content_type='image/jpg') + form_data = {'python.png': imgfile} + rsp = self.client.post( + '/upload?sign=' + sign, form_data, follow=True) + self.assertEqual(rsp.status_code, 200) + os.remove(imagepath) + from djangoblog.utils import save_user_avatar, send_email + send_email(['qq@qq.com'], 'testTitle', 'testContent') + save_user_avatar( + 'https://www.python.org/static/img/python-logo.png') + + def test_errorpage(self): + rsp = self.client.get('/eee') + self.assertEqual(rsp.status_code, 404) + + def test_commands(self): + user = BlogUser.objects.get_or_create( + email="liangliangyy@gmail.com", + username="liangliangyy")[0] + user.set_password("liangliangyy") + user.is_staff = True + user.is_superuser = True + user.save() + + c = OAuthConfig() + c.type = 'qq' + c.appkey = 'appkey' + c.appsecret = 'appsecret' + c.save() + + u = OAuthUser() + u.type = 'qq' + u.openid = 'openid' + u.user = user + u.picture = static("/blog/img/avatar.png") + u.metadata = ''' +{ +"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" +}''' + u.save() + + u = OAuthUser() + u.type = 'qq' + u.openid = 'openid1' + u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30' + u.metadata = ''' + { + "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" + }''' + u.save() + + from blog.documents import ELASTICSEARCH_ENABLED + if ELASTICSEARCH_ENABLED: + call_command("build_index") + call_command("ping_baidu", "all") + call_command("create_testdata") + call_command("clear_cache") + call_command("sync_user_avatar") + call_command("build_search_words") diff --git a/src/DjangoBlog-master/blog/urls.py b/src/DjangoBlog-master/blog/urls.py new file mode 100644 index 0000000..adf2703 --- /dev/null +++ b/src/DjangoBlog-master/blog/urls.py @@ -0,0 +1,62 @@ +from django.urls import path +from django.views.decorators.cache import cache_page + +from . import views + +app_name = "blog" +urlpatterns = [ + path( + r'', + views.IndexView.as_view(), + name='index'), + path( + r'page//', + views.IndexView.as_view(), + name='index_page'), + path( + r'article////.html', + views.ArticleDetailView.as_view(), + name='detailbyid'), + path( + r'category/.html', + views.CategoryDetailView.as_view(), + name='category_detail'), + path( + r'category//.html', + views.CategoryDetailView.as_view(), + name='category_detail_page'), + path( + r'author/.html', + views.AuthorDetailView.as_view(), + name='author_detail'), + path( + r'author//.html', + views.AuthorDetailView.as_view(), + name='author_detail_page'), + path( + r'tag/.html', + views.TagDetailView.as_view(), + name='tag_detail'), + path( + r'tag//.html', + views.TagDetailView.as_view(), + name='tag_detail_page'), + path( + 'archives.html', + cache_page( + 60 * 60)( + views.ArchivesView.as_view()), + name='archives'), + path( + 'links.html', + views.LinkListView.as_view(), + name='links'), + path( + r'upload', + views.fileupload, + name='upload'), + path( + r'clean', + views.clean_cache_view, + name='clean'), +] diff --git a/src/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/blog/views.py new file mode 100644 index 0000000..d5dc7ec --- /dev/null +++ b/src/DjangoBlog-master/blog/views.py @@ -0,0 +1,379 @@ +import logging +import os +import uuid + +from django.conf import settings +from django.core.paginator import Paginator +from django.http import HttpResponse, HttpResponseForbidden +from django.shortcuts import get_object_or_404 +from django.shortcuts import render +from django.templatetags.static import static +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.views.generic.detail import DetailView +from django.views.generic.list import ListView +from haystack.views import SearchView + +from blog.models import Article, Category, LinkShowType, Links, Tag +from comments.forms import CommentForm +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME +from djangoblog.utils import cache, get_blog_setting, get_sha256 + +logger = logging.getLogger(__name__) + + +class ArticleListView(ListView): + # template_name属性用于指定使用哪个模板进行渲染 + template_name = 'blog/article_index.html' + + # context_object_name属性用于给上下文变量取名(在模板中使用该名字) + context_object_name = 'article_list' + + # 页面类型,分类目录或标签列表等 + page_type = '' + paginate_by = settings.PAGINATE_BY + page_kwarg = 'page' + link_type = LinkShowType.L + + def get_view_cache_key(self): + return self.request.get['pages'] + + @property + def page_number(self): + page_kwarg = self.page_kwarg + page = self.kwargs.get( + page_kwarg) or self.request.GET.get(page_kwarg) or 1 + return page + + def get_queryset_cache_key(self): + """ + 子类重写.获得queryset的缓存key + """ + raise NotImplementedError() + + def get_queryset_data(self): + """ + 子类重写.获取queryset的数据 + """ + raise NotImplementedError() + + def get_queryset_from_cache(self, cache_key): + ''' + 缓存页面数据 + :param cache_key: 缓存key + :return: + ''' + value = cache.get(cache_key) + if value: + logger.info('get view cache.key:{key}'.format(key=cache_key)) + return value + else: + article_list = self.get_queryset_data() + cache.set(cache_key, article_list) + logger.info('set view cache.key:{key}'.format(key=cache_key)) + return article_list + + def get_queryset(self): + ''' + 重写默认,从缓存获取数据 + :return: + ''' + key = self.get_queryset_cache_key() + value = self.get_queryset_from_cache(key) + return value + + def get_context_data(self, **kwargs): + kwargs['linktype'] = self.link_type + return super(ArticleListView, self).get_context_data(**kwargs) + + +class IndexView(ArticleListView): + ''' + 首页 + ''' + # 友情链接类型 + link_type = LinkShowType.I + + def get_queryset_data(self): + article_list = Article.objects.filter(type='a', status='p') + return article_list + + def get_queryset_cache_key(self): + cache_key = 'index_{page}'.format(page=self.page_number) + return cache_key + + +class ArticleDetailView(DetailView): + ''' + 文章详情页面 + ''' + template_name = 'blog/article_detail.html' + model = Article + pk_url_kwarg = 'article_id' + context_object_name = "article" + + def get_context_data(self, **kwargs): + comment_form = CommentForm() + + article_comments = self.object.comment_list() + parent_comments = article_comments.filter(parent_comment=None) + blog_setting = get_blog_setting() + paginator = Paginator(parent_comments, blog_setting.article_comment_count) + page = self.request.GET.get('comment_page', '1') + if not page.isnumeric(): + page = 1 + else: + page = int(page) + if page < 1: + page = 1 + if page > paginator.num_pages: + page = paginator.num_pages + + p_comments = paginator.page(page) + next_page = p_comments.next_page_number() if p_comments.has_next() else None + prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None + + if next_page: + kwargs[ + 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container' + if prev_page: + kwargs[ + 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container' + kwargs['form'] = comment_form + kwargs['article_comments'] = article_comments + kwargs['p_comments'] = p_comments + kwargs['comment_count'] = len( + article_comments) if article_comments else 0 + + kwargs['next_article'] = self.object.next_article + kwargs['prev_article'] = self.object.prev_article + + context = super(ArticleDetailView, self).get_context_data(**kwargs) + article = self.object + # Action Hook, 通知插件"文章详情已获取" + hooks.run_action('after_article_body_get', article=article, request=self.request) + # # Filter Hook, 允许插件修改文章正文 + article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article, + request=self.request) + + return context + + +class CategoryDetailView(ArticleListView): + ''' + 分类目录列表 + ''' + page_type = "分类目录归档" + + def get_queryset_data(self): + slug = self.kwargs['category_name'] + category = get_object_or_404(Category, slug=slug) + + categoryname = category.name + self.categoryname = categoryname + categorynames = list( + map(lambda c: c.name, category.get_sub_categorys())) + article_list = Article.objects.filter( + category__name__in=categorynames, status='p') + return article_list + + def get_queryset_cache_key(self): + slug = self.kwargs['category_name'] + category = get_object_or_404(Category, slug=slug) + categoryname = category.name + self.categoryname = categoryname + cache_key = 'category_list_{categoryname}_{page}'.format( + categoryname=categoryname, page=self.page_number) + return cache_key + + def get_context_data(self, **kwargs): + + categoryname = self.categoryname + try: + categoryname = categoryname.split('/')[-1] + except BaseException: + pass + kwargs['page_type'] = CategoryDetailView.page_type + kwargs['tag_name'] = categoryname + return super(CategoryDetailView, self).get_context_data(**kwargs) + + +class AuthorDetailView(ArticleListView): + ''' + 作者详情页 + ''' + page_type = '作者文章归档' + + def get_queryset_cache_key(self): + from uuslug import slugify + author_name = slugify(self.kwargs['author_name']) + cache_key = 'author_{author_name}_{page}'.format( + author_name=author_name, page=self.page_number) + return cache_key + + def get_queryset_data(self): + author_name = self.kwargs['author_name'] + article_list = Article.objects.filter( + author__username=author_name, type='a', status='p') + return article_list + + def get_context_data(self, **kwargs): + author_name = self.kwargs['author_name'] + kwargs['page_type'] = AuthorDetailView.page_type + kwargs['tag_name'] = author_name + return super(AuthorDetailView, self).get_context_data(**kwargs) + + +class TagDetailView(ArticleListView): + ''' + 标签列表页面 + ''' + page_type = '分类标签归档' + + def get_queryset_data(self): + slug = self.kwargs['tag_name'] + tag = get_object_or_404(Tag, slug=slug) + tag_name = tag.name + self.name = tag_name + article_list = Article.objects.filter( + tags__name=tag_name, type='a', status='p') + return article_list + + def get_queryset_cache_key(self): + slug = self.kwargs['tag_name'] + tag = get_object_or_404(Tag, slug=slug) + tag_name = tag.name + self.name = tag_name + cache_key = 'tag_{tag_name}_{page}'.format( + tag_name=tag_name, page=self.page_number) + return cache_key + + def get_context_data(self, **kwargs): + # tag_name = self.kwargs['tag_name'] + tag_name = self.name + kwargs['page_type'] = TagDetailView.page_type + kwargs['tag_name'] = tag_name + return super(TagDetailView, self).get_context_data(**kwargs) + + +class ArchivesView(ArticleListView): + ''' + 文章归档页面 + ''' + page_type = '文章归档' + paginate_by = None + page_kwarg = None + template_name = 'blog/article_archives.html' + + def get_queryset_data(self): + return Article.objects.filter(status='p').all() + + def get_queryset_cache_key(self): + cache_key = 'archives' + return cache_key + + +class LinkListView(ListView): + model = Links + template_name = 'blog/links_list.html' + + def get_queryset(self): + return Links.objects.filter(is_enable=True) + + +class EsSearchView(SearchView): + def get_context(self): + paginator, page = self.build_page() + context = { + "query": self.query, + "form": self.form, + "page": page, + "paginator": paginator, + "suggestion": None, + } + if hasattr(self.results, "query") and self.results.query.backend.include_spelling: + context["suggestion"] = self.results.query.get_spelling_suggestion() + context.update(self.extra_context()) + + return context + + +@csrf_exempt +def fileupload(request): + """ + 该方法需自己写调用端来上传图片,该方法仅提供图床功能 + :param request: + :return: + """ + if request.method == 'POST': + sign = request.GET.get('sign', None) + if not sign: + return HttpResponseForbidden() + if not sign == get_sha256(get_sha256(settings.SECRET_KEY)): + return HttpResponseForbidden() + response = [] + for filename in request.FILES: + timestr = timezone.now().strftime('%Y/%m/%d') + imgextensions = ['jpg', 'png', 'jpeg', 'bmp'] + fname = u''.join(str(filename)) + isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0 + base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr) + if not os.path.exists(base_dir): + os.makedirs(base_dir) + savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) + if not savepath.startswith(base_dir): + return HttpResponse("only for post") + with open(savepath, 'wb+') as wfile: + for chunk in request.FILES[filename].chunks(): + wfile.write(chunk) + if isimage: + from PIL import Image + image = Image.open(savepath) + image.save(savepath, quality=20, optimize=True) + url = static(savepath) + response.append(url) + return HttpResponse(response) + + else: + return HttpResponse("only for post") + + +def page_not_found_view( + request, + exception, + template_name='blog/error_page.html'): + if exception: + logger.error(exception) + url = request.get_full_path() + return render(request, + template_name, + {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'), + 'statuscode': '404'}, + status=404) + + +def server_error_view(request, template_name='blog/error_page.html'): + return render(request, + template_name, + {'message': _('Sorry, the server is busy, please click the home page to see other?'), + 'statuscode': '500'}, + status=500) + + +def permission_denied_view( + request, + exception, + template_name='blog/error_page.html'): + if exception: + logger.error(exception) + return render( + request, template_name, { + 'message': _('Sorry, you do not have permission to access this page?'), + 'statuscode': '403'}, status=403) + + +def clean_cache_view(request): + cache.clear() + return HttpResponse('ok') diff --git a/src/DjangoBlog-master/comments/__init__.py b/src/DjangoBlog-master/comments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py new file mode 100644 index 0000000..a814f3f --- /dev/null +++ b/src/DjangoBlog-master/comments/admin.py @@ -0,0 +1,47 @@ +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 _ + + +def disable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=False) + + +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): + list_per_page = 20 + list_display = ( + 'id', + 'body', + 'link_to_userinfo', + 'link_to_article', + 'is_enable', + 'creation_time') + list_display_links = ('id', 'body', 'is_enable') + list_filter = ('is_enable',) + exclude = ('creation_time', 'last_modify_time') + actions = [disable_commentstatus, enable_commentstatus] + + def link_to_userinfo(self, obj): + info = (obj.author._meta.app_label, obj.author._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + def link_to_article(self, obj): + info = (obj.article._meta.app_label, obj.article._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + return format_html( + u'%s' % (link, obj.article.title)) + + link_to_userinfo.short_description = _('User') + link_to_article.short_description = _('Article') diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py new file mode 100644 index 0000000..ff01b77 --- /dev/null +++ b/src/DjangoBlog-master/comments/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CommentsConfig(AppConfig): + name = 'comments' diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py new file mode 100644 index 0000000..e83737d --- /dev/null +++ b/src/DjangoBlog-master/comments/forms.py @@ -0,0 +1,13 @@ +from django import forms +from django.forms import ModelForm + +from .models import Comment + + +class CommentForm(ModelForm): + parent_comment_id = forms.IntegerField( + widget=forms.HiddenInput, required=False) + + class Meta: + model = Comment + fields = ['body'] diff --git a/src/DjangoBlog-master/comments/migrations/0001_initial.py b/src/DjangoBlog-master/comments/migrations/0001_initial.py new file mode 100644 index 0000000..61d1e53 --- /dev/null +++ b/src/DjangoBlog-master/comments/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('blog', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('body', models.TextField(max_length=300, verbose_name='正文')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), + ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), + ], + options={ + 'verbose_name': '评论', + 'verbose_name_plural': '评论', + 'ordering': ['-id'], + 'get_latest_by': 'id', + }, + ), + ] diff --git a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py new file mode 100644 index 0000000..17c44db --- /dev/null +++ b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-04-24 13:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='is_enable', + field=models.BooleanField(default=False, verbose_name='是否显示'), + ), + ] diff --git a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py new file mode 100644 index 0000000..a1ca970 --- /dev/null +++ b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:13 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('blog', '0005_alter_article_options_alter_category_options_and_more'), + ('comments', '0002_alter_comment_is_enable'), + ] + + operations = [ + migrations.AlterModelOptions( + name='comment', + options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, + ), + migrations.RemoveField( + model_name='comment', + name='created_time', + ), + migrations.RemoveField( + model_name='comment', + name='last_mod_time', + ), + migrations.AddField( + model_name='comment', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='comment', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), + ), + migrations.AlterField( + model_name='comment', + name='article', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), + ), + 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'), + ), + migrations.AlterField( + model_name='comment', + name='is_enable', + field=models.BooleanField(default=False, verbose_name='enable'), + ), + 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'), + ), + ] diff --git a/src/DjangoBlog-master/comments/migrations/__init__.py b/src/DjangoBlog-master/comments/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py new file mode 100644 index 0000000..7c3bbc8 --- /dev/null +++ b/src/DjangoBlog-master/comments/models.py @@ -0,0 +1,39 @@ +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): + body = models.TextField('正文', max_length=300) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + on_delete=models.CASCADE) + article = models.ForeignKey( + Article, + verbose_name=_('article'), + on_delete=models.CASCADE) + parent_comment = models.ForeignKey( + 'self', + verbose_name=_('parent comment'), + blank=True, + null=True, + on_delete=models.CASCADE) + is_enable = models.BooleanField(_('enable'), + default=False, blank=False, null=False) + + class Meta: + ordering = ['-id'] + verbose_name = _('comment') + verbose_name_plural = verbose_name + get_latest_by = 'id' + + def __str__(self): + return self.body diff --git a/src/DjangoBlog-master/comments/templatetags/__init__.py b/src/DjangoBlog-master/comments/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/comments/templatetags/comments_tags.py b/src/DjangoBlog-master/comments/templatetags/comments_tags.py new file mode 100644 index 0000000..fde02b4 --- /dev/null +++ b/src/DjangoBlog-master/comments/templatetags/comments_tags.py @@ -0,0 +1,30 @@ +from django import template + +register = template.Library() + + +@register.simple_tag +def parse_commenttree(commentlist, comment): + """获得当前评论子评论的列表 + 用法: {% parse_commenttree article_comments comment as childcomments %} + """ + datas = [] + + def parse(c): + childs = commentlist.filter(parent_comment=c, is_enable=True) + for child in childs: + datas.append(child) + parse(child) + + parse(comment) + return datas + + +@register.inclusion_tag('comments/tags/comment_item.html') +def show_comment_item(comment, ischild): + """评论""" + depth = 1 if ischild else 2 + return { + 'comment_item': comment, + 'depth': depth + } diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py new file mode 100644 index 0000000..2a7f55f --- /dev/null +++ b/src/DjangoBlog-master/comments/tests.py @@ -0,0 +1,109 @@ +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): + self.client = Client() + self.factory = RequestFactory() + from blog.models import BlogSettings + value = BlogSettings() + value.comment_need_review = True + value.save() + + self.user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="liangliangyy1") + + def update_article_comment_status(self, article): + comments = article.comment_set.all() + for comment in comments: + comment.is_enable = True + comment.save() + + def test_validate_comment(self): + self.client.login(username='liangliangyy1', password='liangliangyy1') + + 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() + + comment_url = reverse( + 'comments:postcomment', kwargs={ + 'article_id': article.id}) + + response = self.client.post(comment_url, + { + 'body': '123ffffffffff' + }) + + self.assertEqual(response.status_code, 302) + + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 0) + self.update_article_comment_status(article) + + self.assertEqual(len(article.comment_list()), 1) + + response = self.client.post(comment_url, + { + 'body': '123ffffffffff', + }) + + self.assertEqual(response.status_code, 302) + + article = Article.objects.get(pk=article.pk) + self.update_article_comment_status(article) + self.assertEqual(len(article.comment_list()), 2) + parent_comment_id = article.comment_list()[0].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 + }) + + 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) + comment = Comment.objects.get(id=parent_comment_id) + tree = parse_commenttree(article.comment_list(), comment) + self.assertEqual(len(tree), 1) + data = show_comment_item(comment, True) + self.assertIsNotNone(data) + s = get_max_articleid_commentid() + self.assertIsNotNone(s) + + from comments.utils import send_comment_email + send_comment_email(comment) diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py new file mode 100644 index 0000000..7df3fab --- /dev/null +++ b/src/DjangoBlog-master/comments/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +app_name = "comments" +urlpatterns = [ + path( + 'article//postcomment', + views.CommentPostView.as_view(), + name='postcomment'), +] diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py new file mode 100644 index 0000000..f01dba7 --- /dev/null +++ b/src/DjangoBlog-master/comments/utils.py @@ -0,0 +1,38 @@ +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): + site = get_current_site().domain + subject = _('Thanks for your comment') + article_url = f"https://{site}{comment.article.get_absolute_url()}" + 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} + tomail = comment.author.email + send_email([tomail], subject, html_content) + try: + if comment.parent_comment: + 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} + tomail = comment.parent_comment.author.email + send_email([tomail], subject, html_content) + except Exception as e: + logger.error(e) diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py new file mode 100644 index 0000000..ad9b2b9 --- /dev/null +++ b/src/DjangoBlog-master/comments/views.py @@ -0,0 +1,63 @@ +# 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): + form_class = CommentForm + template_name = 'blog/article_detail.html' + + @method_decorator(csrf_protect) + def dispatch(self, *args, **kwargs): + return super(CommentPostView, self).dispatch(*args, **kwargs) + + def get(self, request, *args, **kwargs): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + url = article.get_absolute_url() + return HttpResponseRedirect(url + "#comments") + + def form_invalid(self, form): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + return self.render_to_response({ + 'form': form, + 'article': article + }) + + def form_valid(self, form): + """提交的数据验证合法后的逻辑""" + user = self.request.user + author = BlogUser.objects.get(pk=user.pk) + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + if article.comment_status == 'c' or article.status == 'c': + raise ValidationError("该文章评论已关闭.") + comment = form.save(False) + comment.article = article + from djangoblog.utils import get_blog_setting + settings = get_blog_setting() + if not settings.comment_need_review: + comment.is_enable = True + comment.author = author + + if form.cleaned_data['parent_comment_id']: + parent_comment = Comment.objects.get( + pk=form.cleaned_data['parent_comment_id']) + comment.parent_comment = parent_comment + + comment.save(True) + return HttpResponseRedirect( + "%s#div-comment-%d" % + (article.get_absolute_url(), comment.pk)) diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml new file mode 100644 index 0000000..83e35ff --- /dev/null +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml @@ -0,0 +1,48 @@ +version: '3' + +services: + es: + image: liangliangyy/elasticsearch-analysis-ik:8.6.1 + container_name: es + restart: always + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ports: + - 9200:9200 + volumes: + - ./bin/datas/es/:/usr/share/elasticsearch/data/ + + kibana: + image: kibana:8.6.1 + restart: always + container_name: kibana + ports: + - 5601:5601 + environment: + - ELASTICSEARCH_HOSTS=http://es:9200 + + djangoblog: + build: . + restart: always + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' + ports: + - "8000:8000" + volumes: + - ./collectedstatic:/code/djangoblog/collectedstatic + - ./uploads:/code/djangoblog/uploads + environment: + - DJANGO_MYSQL_DATABASE=djangoblog + - DJANGO_MYSQL_USER=root + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E + - DJANGO_MYSQL_HOST=db + - DJANGO_MYSQL_PORT=3306 + - DJANGO_MEMCACHED_LOCATION=memcached:11211 + - DJANGO_ELASTICSEARCH_HOST=es:9200 + links: + - db + - memcached + depends_on: + - db + container_name: djangoblog + diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml new file mode 100644 index 0000000..9609af3 --- /dev/null +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml @@ -0,0 +1,60 @@ +version: '3' + +services: + db: + image: mysql:latest + restart: always + environment: + - MYSQL_DATABASE=djangoblog + - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E + ports: + - 3306:3306 + volumes: + - ./bin/datas/mysql/:/var/lib/mysql + depends_on: + - redis + container_name: db + + djangoblog: + build: + context: ../../ + restart: always + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' + ports: + - "8000:8000" + volumes: + - ./collectedstatic:/code/djangoblog/collectedstatic + - ./logs:/code/djangoblog/logs + - ./uploads:/code/djangoblog/uploads + environment: + - DJANGO_MYSQL_DATABASE=djangoblog + - DJANGO_MYSQL_USER=root + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E + - DJANGO_MYSQL_HOST=db + - DJANGO_MYSQL_PORT=3306 + - DJANGO_REDIS_URL=redis:6379 + links: + - db + - redis + depends_on: + - db + container_name: djangoblog + nginx: + restart: always + image: nginx:latest + ports: + - "80:80" + - "443:443" + volumes: + - ./bin/nginx.conf:/etc/nginx/nginx.conf + - ./collectedstatic:/code/djangoblog/collectedstatic + links: + - djangoblog:djangoblog + container_name: nginx + + redis: + restart: always + image: redis:latest + container_name: redis + ports: + - "6379:6379" diff --git a/src/DjangoBlog-master/deploy/entrypoint.sh b/src/DjangoBlog-master/deploy/entrypoint.sh new file mode 100644 index 0000000..2fb6491 --- /dev/null +++ b/src/DjangoBlog-master/deploy/entrypoint.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +NAME="djangoblog" +DJANGODIR=/code/djangoblog +USER=root +GROUP=root +NUM_WORKERS=1 +DJANGO_WSGI_MODULE=djangoblog.wsgi + + +echo "Starting $NAME as `whoami`" + +cd $DJANGODIR + +export PYTHONPATH=$DJANGODIR:$PYTHONPATH + +python manage.py makemigrations && \ + python manage.py migrate && \ + python manage.py collectstatic --noinput && \ + python manage.py compress --force && \ + python manage.py build_index && \ + python manage.py compilemessages || exit 1 + +exec gunicorn ${DJANGO_WSGI_MODULE}:application \ +--name $NAME \ +--workers $NUM_WORKERS \ +--user=$USER --group=$GROUP \ +--bind 0.0.0.0:8000 \ +--log-level=debug \ +--log-file=- \ +--worker-class gevent \ +--threads 4 diff --git a/src/DjangoBlog-master/deploy/k8s/configmap.yaml b/src/DjangoBlog-master/deploy/k8s/configmap.yaml new file mode 100644 index 0000000..835d4ad --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/configmap.yaml @@ -0,0 +1,119 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: web-nginx-config + namespace: djangoblog +data: + nginx.conf: | + user nginx; + worker_processes auto; + error_log /var/log/nginx/error.log notice; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + multi_accept on; + use epoll; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + gzip on; + gzip_disable "msie6"; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 8; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; + + # Include server configurations + include /etc/nginx/conf.d/*.conf; + } + djangoblog.conf: | + server { + server_name lylinux.net; + root /code/djangoblog/collectedstatic/; + listen 80; + keepalive_timeout 70; + location /static/ { + expires max; + alias /code/djangoblog/collectedstatic/; + } + + location ~* (robots\.txt|ads\.txt|favicon\.ico|favion\.ico|crossdomain\.xml|google93fd32dbd906620a\.html|BingSiteAuth\.xml|baidu_verify_Ijeny6KrmS\.html)$ { + root /resource/djangopub; + expires 1d; + access_log off; + error_log off; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_redirect off; + if (!-f $request_filename) { + proxy_pass http://djangoblog:8000; + break; + } + } + } + server { + server_name www.lylinux.net; + listen 80; + return 301 https://lylinux.net$request_uri; + } + resource.lylinux.net.conf: | + server { + index index.html index.htm; + server_name resource.lylinux.net; + root /resource/; + + location /djangoblog/ { + alias /code/djangoblog/collectedstatic/; + } + + access_log off; + error_log off; + include lylinux/resource.conf; + } + lylinux.resource.conf: | + expires max; + access_log off; + log_not_found off; + add_header Pragma public; + add_header Cache-Control "public"; + add_header "Access-Control-Allow-Origin" "*"; + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: djangoblog-env + namespace: djangoblog +data: + DJANGO_MYSQL_DATABASE: djangoblog + DJANGO_MYSQL_USER: db_user + DJANGO_MYSQL_PASSWORD: db_password + DJANGO_MYSQL_HOST: db_host + DJANGO_MYSQL_PORT: db_port + DJANGO_REDIS_URL: "redis:6379" + DJANGO_DEBUG: "False" + MYSQL_ROOT_PASSWORD: db_password + MYSQL_DATABASE: djangoblog + MYSQL_PASSWORD: db_password + DJANGO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + diff --git a/src/DjangoBlog-master/deploy/k8s/deployment.yaml b/src/DjangoBlog-master/deploy/k8s/deployment.yaml new file mode 100644 index 0000000..414fdcc --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/deployment.yaml @@ -0,0 +1,274 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: djangoblog + namespace: djangoblog + labels: + app: djangoblog +spec: + replicas: 3 + selector: + matchLabels: + app: djangoblog + template: + metadata: + labels: + app: djangoblog + spec: + containers: + - name: djangoblog + image: liangliangyy/djangoblog:latest + imagePullPolicy: Always + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: djangoblog-env + readinessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 30 + livenessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 30 + resources: + requests: + cpu: 10m + memory: 100Mi + limits: + cpu: "2" + memory: 2Gi + volumeMounts: + - name: djangoblog + mountPath: /code/djangoblog/collectedstatic + - name: resource + mountPath: /resource + volumes: + - name: djangoblog + persistentVolumeClaim: + claimName: djangoblog-pvc + - name: resource + persistentVolumeClaim: + claimName: resource-pvc + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: djangoblog + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 6379 + resources: + requests: + cpu: 10m + memory: 100Mi + limits: + cpu: 200m + memory: 2Gi + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: db + namespace: djangoblog + labels: + app: db +spec: + replicas: 1 + selector: + matchLabels: + app: db + template: + metadata: + labels: + app: db + spec: + containers: + - name: db + image: mysql:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3306 + envFrom: + - configMapRef: + name: djangoblog-env + readinessProbe: + exec: + command: + - mysqladmin + - ping + - "-h" + - "127.0.0.1" + - "-u" + - "root" + - "-p$MYSQL_ROOT_PASSWORD" + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + exec: + command: + - mysqladmin + - ping + - "-h" + - "127.0.0.1" + - "-u" + - "root" + - "-p$MYSQL_ROOT_PASSWORD" + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: 10m + memory: 100Mi + limits: + cpu: "2" + memory: 2Gi + volumeMounts: + - name: db-data + mountPath: /var/lib/mysql + volumes: + - name: db-data + persistentVolumeClaim: + claimName: db-pvc + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + namespace: djangoblog + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + resources: + requests: + cpu: 10m + memory: 100Mi + limits: + cpu: "2" + memory: 2Gi + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: djangoblog.conf + - name: nginx-config + mountPath: /etc/nginx/conf.d/resource.lylinux.net.conf + subPath: resource.lylinux.net.conf + - name: nginx-config + mountPath: /etc/nginx/lylinux/resource.conf + subPath: lylinux.resource.conf + - name: djangoblog-pvc + mountPath: /code/djangoblog/collectedstatic + - name: resource-pvc + mountPath: /resource + volumes: + - name: nginx-config + configMap: + name: web-nginx-config + - name: djangoblog-pvc + persistentVolumeClaim: + claimName: djangoblog-pvc + - name: resource-pvc + persistentVolumeClaim: + claimName: resource-pvc + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elasticsearch + namespace: djangoblog + labels: + app: elasticsearch +spec: + replicas: 1 + selector: + matchLabels: + app: elasticsearch + template: + metadata: + labels: + app: elasticsearch + spec: + containers: + - name: elasticsearch + image: liangliangyy/elasticsearch-analysis-ik:8.6.1 + imagePullPolicy: IfNotPresent + env: + - name: discovery.type + value: single-node + - name: ES_JAVA_OPTS + value: "-Xms256m -Xmx256m" + - name: xpack.security.enabled + value: "false" + - name: xpack.monitoring.templates.enabled + value: "false" + ports: + - containerPort: 9200 + resources: + requests: + cpu: 10m + memory: 100Mi + limits: + cpu: "2" + memory: 2Gi + readinessProbe: + httpGet: + path: / + port: 9200 + initialDelaySeconds: 15 + periodSeconds: 30 + livenessProbe: + httpGet: + path: / + port: 9200 + initialDelaySeconds: 15 + periodSeconds: 30 + volumeMounts: + - name: elasticsearch-data + mountPath: /usr/share/elasticsearch/data/ + volumes: + - name: elasticsearch-data + persistentVolumeClaim: + claimName: elasticsearch-pvc diff --git a/src/DjangoBlog-master/deploy/k8s/gateway.yaml b/src/DjangoBlog-master/deploy/k8s/gateway.yaml new file mode 100644 index 0000000..a8de073 --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/gateway.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: nginx + namespace: djangoblog +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nginx + port: + number: 80 \ No newline at end of file diff --git a/src/DjangoBlog-master/deploy/k8s/pv.yaml b/src/DjangoBlog-master/deploy/k8s/pv.yaml new file mode 100644 index 0000000..874b72f --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/pv.yaml @@ -0,0 +1,94 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: local-pv-db +spec: + capacity: + storage: 10Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-storage + local: + path: /mnt/local-storage-db + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - master +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: local-pv-djangoblog +spec: + capacity: + storage: 5Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-storage + local: + path: /mnt/local-storage-djangoblog + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - master + + +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: local-pv-resource +spec: + capacity: + storage: 5Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-storage + local: + path: /mnt/resource/ + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - master + +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: local-pv-elasticsearch +spec: + capacity: + storage: 5Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-storage + local: + path: /mnt/local-storage-elasticsearch + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - master \ No newline at end of file diff --git a/src/DjangoBlog-master/deploy/k8s/pvc.yaml b/src/DjangoBlog-master/deploy/k8s/pvc.yaml new file mode 100644 index 0000000..ef238c5 --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/pvc.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: db-pvc + namespace: djangoblog +spec: + storageClassName: local-storage + volumeName: local-pv-db + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: djangoblog-pvc + namespace: djangoblog +spec: + volumeName: local-pv-djangoblog + storageClassName: local-storage + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: resource-pvc + namespace: djangoblog +spec: + volumeName: local-pv-resource + storageClassName: local-storage + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: elasticsearch-pvc + namespace: djangoblog +spec: + volumeName: local-pv-elasticsearch + storageClassName: local-storage + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + \ No newline at end of file diff --git a/src/DjangoBlog-master/deploy/k8s/service.yaml b/src/DjangoBlog-master/deploy/k8s/service.yaml new file mode 100644 index 0000000..4ef2931 --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/service.yaml @@ -0,0 +1,80 @@ +apiVersion: v1 +kind: Service +metadata: + name: djangoblog + namespace: djangoblog + labels: + app: djangoblog +spec: + selector: + app: djangoblog + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + namespace: djangoblog + labels: + app: nginx +spec: + selector: + app: nginx + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: djangoblog + labels: + app: redis +spec: + selector: + app: redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: db + namespace: djangoblog + labels: + app: db +spec: + selector: + app: db + ports: + - protocol: TCP + port: 3306 + targetPort: 3306 + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: elasticsearch + namespace: djangoblog + labels: + app: elasticsearch +spec: + selector: + app: elasticsearch + ports: + - protocol: TCP + port: 9200 + targetPort: 9200 + type: ClusterIP + diff --git a/src/DjangoBlog-master/deploy/k8s/storageclass.yaml b/src/DjangoBlog-master/deploy/k8s/storageclass.yaml new file mode 100644 index 0000000..5d5a14c --- /dev/null +++ b/src/DjangoBlog-master/deploy/k8s/storageclass.yaml @@ -0,0 +1,10 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: local-storage + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: Immediate + + diff --git a/src/DjangoBlog-master/deploy/nginx.conf b/src/DjangoBlog-master/deploy/nginx.conf new file mode 100644 index 0000000..32161d8 --- /dev/null +++ b/src/DjangoBlog-master/deploy/nginx.conf @@ -0,0 +1,50 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + server { + root /code/djangoblog/collectedstatic/; + listen 80; + keepalive_timeout 70; + location /static/ { + expires max; + alias /code/djangoblog/collectedstatic/; + } + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + proxy_redirect off; + if (!-f $request_filename) { + proxy_pass http://djangoblog:8000; + break; + } + } + } +} diff --git a/src/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/djangoblog/__init__.py new file mode 100644 index 0000000..1e205f4 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/__init__.py @@ -0,0 +1 @@ +default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/djangoblog/admin_site.py new file mode 100644 index 0000000..f120405 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/admin_site.py @@ -0,0 +1,64 @@ +from django.contrib.admin import AdminSite +from django.contrib.admin.models import LogEntry +from django.contrib.sites.admin import SiteAdmin +from django.contrib.sites.models import Site + +from accounts.admin import * +from blog.admin import * +from blog.models import * +from comments.admin import * +from comments.models import * +from djangoblog.logentryadmin import LogEntryAdmin +from oauth.admin import * +from oauth.models import * +from owntracks.admin import * +from owntracks.models import * +from servermanager.admin import * +from servermanager.models import * + + +class DjangoBlogAdminSite(AdminSite): + site_header = 'djangoblog administration' + site_title = 'djangoblog site admin' + + def __init__(self, name='admin'): + super().__init__(name) + + def has_permission(self, request): + return request.user.is_superuser + + # def get_urls(self): + # urls = super().get_urls() + # from django.urls import path + # from blog.views import refresh_memcache + # + # my_urls = [ + # path('refresh/', self.admin_view(refresh_memcache), name="refresh"), + # ] + # return urls + my_urls + + +admin_site = DjangoBlogAdminSite(name='admin') + +admin_site.register(Article, ArticlelAdmin) +admin_site.register(Category, CategoryAdmin) +admin_site.register(Tag, TagAdmin) +admin_site.register(Links, LinksAdmin) +admin_site.register(SideBar, SideBarAdmin) +admin_site.register(BlogSettings, BlogSettingsAdmin) + +admin_site.register(commands, CommandsAdmin) +admin_site.register(EmailSendLog, EmailSendLogAdmin) + +admin_site.register(BlogUser, BlogUserAdmin) + +admin_site.register(Comment, CommentAdmin) + +admin_site.register(OAuthUser, OAuthUserAdmin) +admin_site.register(OAuthConfig, OAuthConfigAdmin) + +admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) + +admin_site.register(Site, SiteAdmin) + +admin_site.register(LogEntry, LogEntryAdmin) diff --git a/src/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/djangoblog/apps.py new file mode 100644 index 0000000..d29e318 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + +class DjangoblogAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'djangoblog' + + def ready(self): + super().ready() + # Import and load plugins here + from .plugin_manage.loader import load_plugins + load_plugins() \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/djangoblog/blog_signals.py new file mode 100644 index 0000000..393f441 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/blog_signals.py @@ -0,0 +1,122 @@ +import _thread +import logging + +import django.dispatch +from django.conf import settings +from django.contrib.admin.models import LogEntry +from django.contrib.auth.signals import user_logged_in, user_logged_out +from django.core.mail import EmailMultiAlternatives +from django.db.models.signals import post_save +from django.dispatch import receiver + +from comments.models import Comment +from comments.utils import send_comment_email +from djangoblog.spider_notify import SpiderNotify +from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache +from djangoblog.utils import get_current_site +from oauth.models import OAuthUser + +logger = logging.getLogger(__name__) + +oauth_user_login_signal = django.dispatch.Signal(['id']) +send_email_signal = django.dispatch.Signal( + ['emailto', 'title', 'content']) + + +@receiver(send_email_signal) +def send_email_signal_handler(sender, **kwargs): + emailto = kwargs['emailto'] + title = kwargs['title'] + content = kwargs['content'] + + msg = EmailMultiAlternatives( + title, + content, + from_email=settings.DEFAULT_FROM_EMAIL, + to=emailto) + msg.content_subtype = "html" + + from servermanager.models import EmailSendLog + log = EmailSendLog() + log.title = title + log.content = content + log.emailto = ','.join(emailto) + + try: + result = msg.send() + log.send_result = result > 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): + 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): + 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): + 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): + if user and user.username: + logger.info(user) + delete_sidebar_cache() + # cache.clear() diff --git a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py new file mode 100644 index 0000000..4afe498 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py @@ -0,0 +1,183 @@ +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): + 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): + 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): + 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): + + def search(self): + # 是否建议搜索 + self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" + sqs = super().search() + return sqs + + +class ElasticSearchEngine(BaseEngine): + backend = ElasticSearchBackend + query = ElasticSearchQuery diff --git a/src/DjangoBlog-master/djangoblog/feeds.py b/src/DjangoBlog-master/djangoblog/feeds.py new file mode 100644 index 0000000..8c4e851 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/feeds.py @@ -0,0 +1,40 @@ +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): + 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): + 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): + 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 diff --git a/src/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/djangoblog/logentryadmin.py new file mode 100644 index 0000000..2f6a535 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/logentryadmin.py @@ -0,0 +1,91 @@ +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): + 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): + 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): + 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 diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py new file mode 100644 index 0000000..2b4be5c --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py @@ -0,0 +1,41 @@ +import logging + +logger = logging.getLogger(__name__) + + +class BasePlugin: + # 插件元数据 + PLUGIN_NAME = None + PLUGIN_DESCRIPTION = None + PLUGIN_VERSION = None + + def __init__(self): + if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): + raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") + self.init_plugin() + self.register_hooks() + + def init_plugin(self): + """ + 插件初始化逻辑 + 子类可以重写此方法来实现特定的初始化操作 + """ + logger.info(f'{self.PLUGIN_NAME} initialized.') + + def register_hooks(self): + """ + 注册插件钩子 + 子类可以重写此方法来注册特定的钩子 + """ + pass + + def get_plugin_info(self): + """ + 获取插件信息 + :return: 包含插件元数据的字典 + """ + return { + 'name': self.PLUGIN_NAME, + 'description': self.PLUGIN_DESCRIPTION, + 'version': self.PLUGIN_VERSION + } diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py new file mode 100644 index 0000000..6685b7c --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py @@ -0,0 +1,7 @@ +ARTICLE_DETAIL_LOAD = 'article_detail_load' +ARTICLE_CREATE = 'article_create' +ARTICLE_UPDATE = 'article_update' +ARTICLE_DELETE = 'article_delete' + +ARTICLE_CONTENT_HOOK_NAME = "the_content" + diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py new file mode 100644 index 0000000..d712540 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py @@ -0,0 +1,44 @@ +import logging + +logger = logging.getLogger(__name__) + +_hooks = {} + + +def register(hook_name: str, callback: callable): + """ + 注册一个钩子回调。 + """ + if hook_name not in _hooks: + _hooks[hook_name] = [] + _hooks[hook_name].append(callback) + logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") + + +def run_action(hook_name: str, *args, **kwargs): + """ + 执行一个 Action Hook。 + 它会按顺序执行所有注册到该钩子上的回调函数。 + """ + if hook_name in _hooks: + logger.debug(f"Running action hook '{hook_name}'") + for callback in _hooks[hook_name]: + try: + callback(*args, **kwargs) + except Exception as e: + logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) + + +def apply_filters(hook_name: str, value, *args, **kwargs): + """ + 执行一个 Filter Hook。 + 它会把 value 依次传递给所有注册的回调函数进行处理。 + """ + if hook_name in _hooks: + logger.debug(f"Applying filter hook '{hook_name}'") + for callback in _hooks[hook_name]: + try: + value = callback(value, *args, **kwargs) + except Exception as e: + logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) + return value diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py b/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py new file mode 100644 index 0000000..12e824b --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py @@ -0,0 +1,19 @@ +import os +import logging +from django.conf import settings + +logger = logging.getLogger(__name__) + +def load_plugins(): + """ + Dynamically loads and initializes plugins from the 'plugins' directory. + This function is intended to be called when the Django app registry is ready. + """ + for plugin_name in settings.ACTIVE_PLUGINS: + plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) + if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): + try: + __import__(f'plugins.{plugin_name}.plugin') + logger.info(f"Successfully loaded plugin: {plugin_name}") + except ImportError as e: + logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/djangoblog/settings.py new file mode 100644 index 0000000..d076bb6 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/settings.py @@ -0,0 +1,343 @@ +""" +Django settings for djangoblog project. + +Generated by 'django-admin startproject' using Django 1.10.2. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" +import os +import sys +from pathlib import Path + +from django.utils.translation import gettext_lazy as _ + + +def env_to_bool(env, default): + str_val = os.environ.get(env) + return default if str_val is None else str_val == 'True' + + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get( + 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = env_to_bool('DJANGO_DEBUG', True) +# DEBUG = False +TESTING = len(sys.argv) > 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': os.environ.get('DJANGO_MYSQL_DATABASE') or 'djangoblog', + 'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root', + 'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or 'root', + 'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1', + 'PORT': int( + os.environ.get('DJANGO_MYSQL_PORT') or 3306), + 'OPTIONS': { + 'charset': 'utf8mb4'}, + }} + +# 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' +] \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/sitemap.py b/src/DjangoBlog-master/djangoblog/sitemap.py new file mode 100644 index 0000000..8b7d446 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/sitemap.py @@ -0,0 +1,59 @@ +from django.contrib.sitemaps import Sitemap +from django.urls import reverse + +from blog.models import Article, Category, Tag + + +class StaticViewSitemap(Sitemap): + priority = 0.5 + changefreq = 'daily' + + def items(self): + return ['blog:index', ] + + def location(self, item): + return reverse(item) + + +class ArticleSiteMap(Sitemap): + 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): + changefreq = "Weekly" + priority = "0.6" + + def items(self): + return Category.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class TagSiteMap(Sitemap): + changefreq = "Weekly" + priority = "0.3" + + def items(self): + return Tag.objects.all() + + def lastmod(self, obj): + return obj.last_modify_time + + +class UserSiteMap(Sitemap): + 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 diff --git a/src/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/djangoblog/spider_notify.py new file mode 100644 index 0000000..7b909e9 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/spider_notify.py @@ -0,0 +1,21 @@ +import logging + +import requests +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class SpiderNotify(): + @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: + logger.error(e) + + @staticmethod + def notify(url): + SpiderNotify.baidu_notify(url) diff --git a/src/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/djangoblog/tests.py new file mode 100644 index 0000000..01237d9 --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/tests.py @@ -0,0 +1,32 @@ +from django.test import TestCase + +from djangoblog.utils import * + + +class DjangoBlogTest(TestCase): + def setUp(self): + pass + + def test_utils(self): + 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) diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py new file mode 100644 index 0000000..4aae58a --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/urls.py @@ -0,0 +1,64 @@ +"""djangoblog URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +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 + +sitemaps = { + + 'blog': ArticleSiteMap, + 'Category': CategorySiteMap, + 'Tag': TagSiteMap, + 'User': UserSiteMap, + 'static': StaticViewSitemap +} + +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')), +] +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) +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT) diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py new file mode 100644 index 0000000..57f63dc --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/utils.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +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(): + from blog.models import Article + from comments.models import Comment + return (Article.objects.latest().pk, Comment.objects.latest().pk) + + +def get_sha256(str): + m = sha256(str.encode('utf-8')) + return m.hexdigest() + + +def cache_decorator(expiration=3 * 60): + 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(): + site = Site.objects.get_current() + return site + + +class CommonMarkdown: + @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): + body, toc = CommonMarkdown._convert_markdown(value) + return body, toc + + @staticmethod + def get_markdown(value): + body, toc = CommonMarkdown._convert_markdown(value) + return body + + +def send_email(emailto, title, content): + 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): + 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(): + value = cache.get('get_blog_setting') + if value: + return value + else: + from blog.models import BlogSettings + if not BlogSettings.objects.count(): + 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(): + 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): + from django.core.cache.utils import make_template_fragment_key + key = make_template_fragment_key(prefix, keys) + cache.delete(key) + + +def get_resource_url(): + if settings.STATIC_URL: + return settings.STATIC_URL + else: + site = get_current_site() + return 'http://' + site.domain + '/static/' + + +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): + return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) diff --git a/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py new file mode 100644 index 0000000..04e3f7f --- /dev/null +++ b/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py @@ -0,0 +1,1044 @@ +# encoding: utf-8 + +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): + # 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): + 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) + 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): + 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): + 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): + 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): + # 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): + 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): + # 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): + 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): + 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): + 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].split('.') + additional_fields = {} + model = haystack_get_model(app_label, model_name) + + if model and model in indexed_models: + for key, value in raw_result.items(): + index = unified_index.get_index(model) + string_key = str(key) + + if string_key in index.fields and hasattr( + index.fields[string_key], 'convert'): + # Special-cased due to the nature of KEYWORD fields. + if index.fields[string_key].is_multivalued: + if value is None or len(value) == 0: + additional_fields[string_key] = [] + else: + additional_fields[string_key] = value.split( + ',') + else: + additional_fields[string_key] = index.fields[string_key].convert( + value) + else: + additional_fields[string_key] = self._to_python(value) + + del (additional_fields[DJANGO_CT]) + del (additional_fields[DJANGO_ID]) + + if highlight: + sa = StemmingAnalyzer() + formatter = WhooshHtmlFormatter('em') + terms = [token.text for token in sa(query_string)] + + whoosh_result = whoosh_highlight( + additional_fields.get(self.content_field_name), + terms, + sa, + ContextFragmenter(), + formatter + ) + additional_fields['highlighted'] = { + self.content_field_name: [whoosh_result], + } + + result = result_class( + app_label, + model_name, + raw_result[DJANGO_ID], + score, + **additional_fields) + results.append(result) + else: + hits -= 1 + + if self.include_spelling: + if spelling_query: + spelling_suggestion = self.create_spelling_suggestion( + spelling_query) + else: + spelling_suggestion = self.create_spelling_suggestion( + query_string) + + return { + 'results': results, + 'hits': hits, + 'facets': facets, + 'spelling_suggestion': spelling_suggestion, + } + + def create_spelling_suggestion(self, query_string): + spelling_suggestion = None + reader = self.index.reader() + corrector = reader.corrector(self.content_field_name) + cleaned_query = force_str(query_string) + + if not query_string: + return spelling_suggestion + + # Clean the string. + for rev_word in self.RESERVED_WORDS: + cleaned_query = cleaned_query.replace(rev_word, '') + + for rev_char in self.RESERVED_CHARACTERS: + cleaned_query = cleaned_query.replace(rev_char, '') + + # Break it down. + query_words = cleaned_query.split() + suggested_words = [] + + for word in query_words: + suggestions = corrector.suggest(word, limit=1) + + if len(suggestions) > 0: + suggested_words.append(suggestions[0]) + + spelling_suggestion = ' '.join(suggested_words) + return spelling_suggestion + + def _from_python(self, value): + """ + Converts Python values to a string for Whoosh. + + Code courtesy of pysolr. + """ + if hasattr(value, 'strftime'): + if not hasattr(value, 'hour'): + value = datetime(value.year, value.month, value.day, 0, 0, 0) + elif isinstance(value, bool): + if value: + value = 'true' + else: + value = 'false' + elif isinstance(value, (list, tuple)): + value = u','.join([force_str(v) for v in value]) + elif isinstance(value, (six.integer_types, float)): + # Leave it alone. + pass + else: + value = force_str(value) + return value + + def _to_python(self, value): + """ + Converts values from Whoosh to native Python values. + + A port of the same method in pysolr, as they deal with data the same way. + """ + if value == 'true': + return True + elif value == 'false': + return False + + if value and isinstance(value, six.string_types): + possible_datetime = DATETIME_REGEX.search(value) + + if possible_datetime: + date_values = possible_datetime.groupdict() + + for dk, dv in date_values.items(): + date_values[dk] = int(dv) + + return datetime( + date_values['year'], + date_values['month'], + date_values['day'], + date_values['hour'], + date_values['minute'], + date_values['second']) + + try: + # Attempt to use json to load the values. + converted_value = json.loads(value) + + # Try to handle most built-in types. + if isinstance( + converted_value, + (list, + tuple, + set, + dict, + six.integer_types, + float, + complex)): + return converted_value + except BaseException: + # If it fails (SyntaxError or its ilk) or we don't trust it, + # continue on. + pass + + return value + + +class WhooshSearchQuery(BaseSearchQuery): + 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): + from haystack import connections + query_frag = '' + is_datetime = False + + if not hasattr(value, 'input_type_name'): + # Handle when we've got a ``ValuesListQuerySet``... + if hasattr(value, 'values_list'): + value = list(value) + + if hasattr(value, 'strftime'): + is_datetime = True + + if isinstance(value, six.string_types) and value != ' ': + # It's not an ``InputType``. Assume ``Clean``. + value = Clean(value) + else: + value = PythonData(value) + + # Prepare the query using the InputType. + prepared_value = value.prepare(self) + + if not isinstance(prepared_value, (set, list, tuple)): + # Then convert whatever we get back to what pysolr wants if needed. + prepared_value = self.backend._from_python(prepared_value) + + # 'content' is a special reserved word, much like 'pk' in + # Django's ORM layer. It indicates 'no special field'. + if field == 'content': + index_fieldname = '' + else: + index_fieldname = u'%s:' % connections[self._using].get_unified_index( + ).get_index_fieldname(field) + + filter_types = { + 'content': '%s', + 'contains': '*%s*', + 'endswith': "*%s", + 'startswith': "%s*", + 'exact': '%s', + 'gt': "{%s to}", + 'gte': "[%s to]", + 'lt': "{to %s}", + 'lte': "[to %s]", + 'fuzzy': u'%s~', + } + + if value.post_process is False: + query_frag = prepared_value + else: + if filter_type in [ + 'content', + 'contains', + 'startswith', + 'endswith', + 'fuzzy']: + if value.input_type_name == 'exact': + query_frag = prepared_value + else: + # Iterate over terms & incorportate the converted form of + # each into the query. + terms = [] + + if isinstance(prepared_value, six.string_types): + possible_values = prepared_value.split(' ') + else: + if is_datetime is True: + prepared_value = self._convert_datetime( + prepared_value) + + possible_values = [prepared_value] + + for possible_value in possible_values: + terms.append( + filter_types[filter_type] % + self.backend._from_python(possible_value)) + + if len(terms) == 1: + query_frag = terms[0] + else: + query_frag = u"(%s)" % " AND ".join(terms) + elif filter_type == 'in': + in_options = [] + + for possible_value in prepared_value: + is_datetime = False + + if hasattr(possible_value, 'strftime'): + is_datetime = True + + pv = self.backend._from_python(possible_value) + + if is_datetime is True: + pv = self._convert_datetime(pv) + + if isinstance(pv, six.string_types) and not is_datetime: + in_options.append('"%s"' % pv) + else: + in_options.append('%s' % pv) + + query_frag = "(%s)" % " OR ".join(in_options) + elif filter_type == 'range': + start = self.backend._from_python(prepared_value[0]) + end = self.backend._from_python(prepared_value[1]) + + if hasattr(prepared_value[0], 'strftime'): + start = self._convert_datetime(start) + + if hasattr(prepared_value[1], 'strftime'): + end = self._convert_datetime(end) + + query_frag = u"[%s to %s]" % (start, end) + elif filter_type == 'exact': + if value.input_type_name == 'exact': + query_frag = prepared_value + else: + prepared_value = Exact(prepared_value).prepare(self) + query_frag = filter_types[filter_type] % prepared_value + else: + if is_datetime is True: + prepared_value = self._convert_datetime(prepared_value) + + query_frag = filter_types[filter_type] % prepared_value + + if len(query_frag) and not isinstance(value, Raw): + if not query_frag.startswith('(') and not query_frag.endswith(')'): + query_frag = "(%s)" % query_frag + + return u"%s%s" % (index_fieldname, query_frag) + + # if not filter_type in ('in', 'range'): + # # 'in' is a bit of a special case, as we don't want to + # # convert a valid list/tuple to string. Defer handling it + # # until later... + # value = self.backend._from_python(value) + + +class WhooshEngine(BaseEngine): + backend = WhooshSearchBackend + query = WhooshSearchQuery diff --git a/src/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/djangoblog/wsgi.py new file mode 100644 index 0000000..2295efd --- /dev/null +++ b/src/DjangoBlog-master/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() diff --git a/src/DjangoBlog-master/docs/README-en.md b/src/DjangoBlog-master/docs/README-en.md new file mode 100644 index 0000000..37ea069 --- /dev/null +++ b/src/DjangoBlog-master/docs/README-en.md @@ -0,0 +1,158 @@ +# DjangoBlog + +

    + Django CI + CodeQL + codecov + license +

    + +

    + A powerful, elegant, and modern blog system. +
    + English简体中文 +

    + +--- + +DjangoBlog is a high-performance blog platform built with Python 3.10 and Django 4.0. It not only provides all the core functionalities of a traditional blog but also features a flexible plugin system, allowing you to easily extend and customize your website. Whether you are a personal blogger, a tech enthusiast, or a content creator, DjangoBlog aims to provide a stable, efficient, and easy-to-maintain environment for writing and publishing. + +## ✨ Features + +- **Powerful Content Management**: Full support for managing articles, standalone pages, categories, and tags. Comes with a powerful built-in Markdown editor with syntax highlighting. +- **Full-Text Search**: Integrated search engine for fast and accurate content searching. +- **Interactive Comment System**: Supports replies, email notifications, and Markdown formatting in comments. +- **Flexible Sidebar**: Customizable modules for displaying recent articles, most viewed posts, tag cloud, and more. +- **Social Login**: Built-in OAuth support, with integrations for Google, GitHub, Facebook, Weibo, QQ, and other major platforms. +- **High-Performance Caching**: Native support for Redis caching with an automatic refresh mechanism to ensure high-speed website responses. +- **SEO Friendly**: Basic SEO features are included, with automatic notifications to Google and Baidu upon new content publication. +- **Extensible Plugin System**: Extend blog functionalities by creating standalone plugins, ensuring decoupled and maintainable code. We have already implemented features like view counting and SEO optimization through plugins! +- **Integrated Image Hosting**: A simple, built-in image hosting feature for easy uploads and management. +- **Automated Frontend**: Integrated with `django-compressor` to automatically compress and optimize CSS and JavaScript files. +- **Robust Operations**: Built-in email notifications for website exceptions and management capabilities through a WeChat Official Account. + +## 🛠️ Tech Stack + +- **Backend**: Python 3.10, Django 4.0 +- **Database**: MySQL, SQLite (configurable) +- **Cache**: Redis +- **Frontend**: HTML5, CSS3, JavaScript +- **Search**: Whoosh, Elasticsearch (configurable) +- **Editor**: Markdown (mdeditor) + +## 🚀 Getting Started + +### 1. Prerequisites + +Ensure you have Python 3.10+ and MySQL/MariaDB installed on your system. + +### 2. Clone & Installation + +```bash +# Clone the project to your local machine +git clone https://github.com/liangliangyy/DjangoBlog.git +cd DjangoBlog + +# Install dependencies +pip install -r requirements.txt +``` + +### 3. Project Configuration + +- **Database**: + Open `djangoblog/settings.py`, locate the `DATABASES` section, and update it with your MySQL connection details. + + ```python + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'djangoblog', + 'USER': 'root', + 'PASSWORD': 'your_password', + 'HOST': '127.0.0.1', + 'PORT': 3306, + } + } + ``` + Create the database in MySQL: + ```sql + CREATE DATABASE `djangoblog` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + ``` + +- **More Configurations**: + For advanced settings such as email, OAuth, caching, and more, please refer to our [Detailed Configuration Guide](/docs/config-en.md). + +### 4. Database Initialization + +```bash +python manage.py makemigrations +python manage.py migrate + +# Create a superuser account +python manage.py createsuperuser +``` + +### 5. Running the Project + +```bash +# (Optional) Generate some test data +python manage.py create_testdata + +# (Optional) Collect and compress static files +python manage.py collectstatic --noinput +python manage.py compress --force + +# Start the development server +python manage.py runserver +``` + +Now, open your browser and navigate to `http://127.0.0.1:8000/`. You should see the DjangoBlog homepage! + +## Deployment + +- **Traditional Deployment**: A detailed guide for server deployment is available here: [Deployment Tutorial](https://www.lylinux.net/article/2019/8/5/58.html) (in Chinese). +- **Docker Deployment**: This project fully supports Docker. If you are familiar with containerization, please refer to the [Docker Deployment Guide](/docs/docker-en.md) for a quick start. +- **Kubernetes Deployment**: We also provide a complete [Kubernetes Deployment Guide](/docs/k8s-en.md) to help you go cloud-native easily. + +## 🧩 Plugin System + +The plugin system is a core feature of DjangoBlog. It allows you to add new functionalities to your blog without modifying the core codebase by writing standalone plugins. + +- **How it Works**: Plugins operate by registering callback functions to predefined "hooks". For instance, when an article is rendered, the `after_article_body_get` hook is triggered, and all functions registered to this hook are executed. +- **Existing Plugins**: Features like `view_count` and `seo_optimizer` are implemented through this plugin system. +- **Develop Your Own Plugin**: Simply create a new folder under the `plugins` directory and write your `plugin.py`. We welcome you to explore and contribute your creative ideas to the DjangoBlog community! + +## 🤝 Contributing + +We warmly welcome contributions of any kind! If you have great ideas or have found a bug, please feel free to open an issue or submit a pull request. + +## 📄 License + +This project is open-sourced under the [MIT License](LICENSE). + +--- + +## ❤️ Support & Sponsorship + +If you find this project helpful and wish to support its continued maintenance and development, please consider buying me a coffee! Your support is my greatest motivation. + +

    + Alipay Sponsorship + WeChat Sponsorship +

    +

    + (Left) Alipay / (Right) WeChat +

    + +## 🙏 Acknowledgements + +A special thanks to **JetBrains** for providing a free open-source license for this project. + +

    + + JetBrains Logo + +

    + +--- +> If this project has helped you, please leave your website URL [here](https://github.com/liangliangyy/DjangoBlog/issues/214) to let more people see it. Your feedback is the driving force for my continued updates and maintenance. diff --git a/src/DjangoBlog-master/docs/config-en.md b/src/DjangoBlog-master/docs/config-en.md new file mode 100644 index 0000000..b877efb --- /dev/null +++ b/src/DjangoBlog-master/docs/config-en.md @@ -0,0 +1,64 @@ +# Introduction to main features settings + +## Cache: +Cache using `memcache` for default. If you don't have `memcache` environment, you can remove the `default` setting in `CACHES` and change `locmemcache` to `default`. +```python +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + 'KEY_PREFIX': 'django_test' if TESTING else 'djangoblog', + 'TIMEOUT': 60 * 60 * 10 + }, + 'locmemcache': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'TIMEOUT': 10800, + 'LOCATION': 'unique-snowflake', + } +} +``` + +## OAuth Login: +QQ, Weibo, Google, GitHub and Facebook are now supported for OAuth login. Fetch OAuth login permissions from the corresponding open platform, and save them with `appkey`, `appsecret` and callback address in **Backend->OAuth** configuration. + +### Callback address examples: +QQ: http://your-domain-name/oauth/authorize?type=qq +Weibo: http://your-domain-name/oauth/authorize?type=weibo +type is in the type field of `oauthmanager`. + +## owntracks: +owntracks is a location tracking application. It will send your locaiton to the server by timing.Simple support owntracks features. Just install owntracks app and set api address as `your-domain-name/owntracks/logtracks`. Visit `your-domain-name/owntracks/show_dates` and you will see the date with latitude and langitude, click it and see the motion track. The map is drawn by AMap. + +## Email feature: +Same as before, Configure your own error msg recvie email information with`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]` in `settings.py`. And modify: +```python +EMAIL_HOST = 'smtp.zoho.com' +EMAIL_PORT = 587 +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 = os.environ.get('DJANGO_EMAIL_USER') +``` +with your email account information. + +## WeChat Official Account +Simple wechat official account features integrated. Set token as `your-domain-name/robot` in wechat backend. Default token is `lylinux`, you can change it to your own in `servermanager/robot.py`. Add a new command in `Backend->Servermanager->command`, in this way, you can manage the system through wechat official account. + +## Introduction to website configuration +You can add website configuration in **Backend->BLOG->WebSiteConfiguration**. Such as: keywords, description, Google Ad, website stats code, case number, etc. +OAuth user avatar path is saved in *StaticFileSavedAddress*. Please input absolute path, code directory for default. + +## Source code highlighting +If the code block in your article didn't show hightlight, please write the code blocks as following: + +![](https://resource.lylinux.net/image/codelang.png) + +That is, you should add the corresponding language name before the code block. + +## Update +If you get errors as following while executing database migrations: +```python +django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) +``` +This problem may cause by the mysql version under 5.6, a new version( >= 5.6 ) mysql is needed. + diff --git a/src/DjangoBlog-master/docs/config.md b/src/DjangoBlog-master/docs/config.md new file mode 100644 index 0000000..24673a3 --- /dev/null +++ b/src/DjangoBlog-master/docs/config.md @@ -0,0 +1,58 @@ +# 主要功能配置介绍: + +## 缓存: +缓存默认使用`localmem`缓存,如果你有`redis`环境,可以设置`DJANGO_REDIS_URL`环境变量,则会自动使用该redis来作为缓存,或者你也可以直接修改如下代码来使用。 +https://github.com/liangliangyy/DjangoBlog/blob/ffcb2c3711de805f2067dd3c1c57449cd24d84ee/djangoblog/settings.py#L185-L199 + + +## oauth登录: + +现在已经支持QQ,微博,Google,GitHub,Facebook登录,需要在其对应的开放平台申请oauth登录权限,然后在 +**后台->Oauth** 配置中新增配置,填写对应的`appkey`和`appsecret`以及回调地址。 +### 回调地址示例: +qq:http://你的域名/oauth/authorize?type=qq +微博:http://你的域名/oauth/authorize?type=weibo +type对应在`oauthmanager`中的type字段。 + +## owntracks: +owntracks是一个位置追踪软件,可以定时的将你的坐标提交到你的服务器上,现在简单的支持owntracks功能,需要安装owntracks的app,然后将api地址设置为: +`你的域名/owntracks/logtracks`就可以了。然后访问`你的域名/owntracks/show_dates`就可以看到有经纬度记录的日期,点击之后就可以看到运动轨迹了。地图是使用高德地图绘制。 + +## 邮件功能: +同样,将`settings.py`中的`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]`配置为你自己的错误接收邮箱,另外修改: +```python +EMAIL_HOST = 'smtp.zoho.com' +EMAIL_PORT = 587 +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 = os.environ.get('DJANGO_EMAIL_USER') +``` +为你自己的邮箱配置。 + +## 微信公众号 +集成了简单的微信公众号功能,在微信后台将token地址设置为:`你的域名/robot` 即可,默认token为`lylinux`,当然你可以修改为你自己的,在`servermanager/robot.py`中。 +然后在**后台->Servermanager->命令**中新增命令,这样就可以使用微信公众号来管理了。 +## 网站配置介绍 +在**后台->BLOG->网站配置**中,可以新增网站配置,比如关键字,描述等,以及谷歌广告,网站统计代码及备案号等等。 +其中的*静态文件保存地址*是保存oauth用户登录的头像路径,填写绝对路径,默认是代码目录。 +## 代码高亮 +如果你发现你文章的代码没有高亮,请这样书写代码块: + +![](https://resource.lylinux.net/image/codelang.png) + + +也就是说,需要在代码块开始位置加入这段代码对应的语言。 + +## update +如果你发现执行数据库迁移的时候出现如下报错: +```python +django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) +``` +可能是因为你的mysql版本低于5.6,需要升级mysql版本>=5.6即可。 + + +django 4.0登录可能会报错CSRF,需要配置下`settings.py`中的`CSRF_TRUSTED_ORIGINS` + +https://github.com/liangliangyy/DjangoBlog/blob/master/djangoblog/settings.py#L39 + diff --git a/src/DjangoBlog-master/docs/docker-en.md b/src/DjangoBlog-master/docs/docker-en.md new file mode 100644 index 0000000..8d5d59e --- /dev/null +++ b/src/DjangoBlog-master/docs/docker-en.md @@ -0,0 +1,114 @@ +# Deploying DjangoBlog with Docker + +![Docker Pulls](https://img.shields.io/docker/pulls/liangliangyy/djangoblog) +![Docker Image Version (latest by date)](https://img.shields.io/docker/v/liangliangyy/djangoblog?sort=date) +![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/liangliangyy/djangoblog) + +This project fully supports containerized deployment using Docker, providing you with a fast, consistent, and isolated runtime environment. We recommend using `docker-compose` to launch the entire blog service stack with a single command. + +## 1. Prerequisites + +Before you begin, please ensure you have the following software installed on your system: +- [Docker Engine](https://docs.docker.com/engine/install/) +- [Docker Compose](https://docs.docker.com/compose/install/) (Included with Docker Desktop for Mac and Windows) + +## 2. Recommended Method: Using `docker-compose` (One-Click Deployment) + +This is the simplest and most recommended way to deploy. It automatically creates and manages the Django application, a MySQL database, and an optional Elasticsearch service for you. + +### Step 1: Start the Basic Services + +From the project's root directory, run the following command: + +```bash +# Build and start the containers in detached mode (includes Django app and MySQL) +docker-compose up -d --build +``` + +`docker-compose` will read the `docker-compose.yml` file, pull the necessary images, build the project image, and start all services. + +- **Access Your Blog**: Once the services are up, you can access the blog by navigating to `http://127.0.0.1` in your browser. +- **Data Persistence**: MySQL data files will be stored in the `data/mysql` directory within the project root, ensuring that your data persists across container restarts. + +### Step 2: (Optional) Enable Elasticsearch for Full-Text Search + +If you want to use Elasticsearch for more powerful full-text search capabilities, you can include the `docker-compose.es.yml` configuration file: + +```bash +# Build and start all services in detached mode (Django, MySQL, Elasticsearch) +docker-compose -f docker-compose.yml -f deploy/docker-compose/docker-compose.es.yml up -d --build +``` +- **Data Persistence**: Elasticsearch data will be stored in the `data/elasticsearch` directory. + +### Step 3: First-Time Initialization + +After the containers start for the first time, you'll need to execute some initialization commands inside the application container. + +```bash +# Get a shell inside the djangoblog application container (named 'web') +docker-compose exec web bash + +# Inside the container, run the following commands: +# Create a superuser account (follow the prompts to set username, email, and password) +python manage.py createsuperuser + +# (Optional) Create some test data +python manage.py create_testdata + +# (Optional, if ES is enabled) Create the search index +python manage.py rebuild_index + +# Exit the container +exit +``` + +## 3. Alternative Method: Using the Standalone Docker Image + +If you already have an external MySQL database running, you can run the DjangoBlog application image by itself. + +```bash +# Pull the latest image from Docker Hub +docker pull liangliangyy/djangoblog:latest + +# Run the container and connect it to your external database +docker run -d \ + -p 8000:8000 \ + -e DJANGO_SECRET_KEY='your-strong-secret-key' \ + -e DJANGO_MYSQL_HOST='your-mysql-host' \ + -e DJANGO_MYSQL_USER='your-mysql-user' \ + -e DJANGO_MYSQL_PASSWORD='your-mysql-password' \ + -e DJANGO_MYSQL_DATABASE='djangoblog' \ + --name djangoblog \ + liangliangyy/djangoblog:latest +``` + +- **Access Your Blog**: After startup, visit `http://127.0.0.1:8000`. +- **Create Superuser**: `docker exec -it djangoblog python manage.py createsuperuser` + +## 4. Configuration (Environment Variables) + +Most of the project's configuration is managed through environment variables. You can modify them in the `docker-compose.yml` file or pass them using the `-e` flag with the `docker run` command. + +| Environment Variable | Default/Example Value | Notes | +|---------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------| +| `DJANGO_SECRET_KEY` | `your-strong-secret-key` | **Must be changed to a random, complex string!** | +| `DJANGO_DEBUG` | `False` | Toggles Django's debug mode. | +| `DJANGO_MYSQL_HOST` | `mysql` | Database hostname. | +| `DJANGO_MYSQL_PORT` | `3306` | Database port. | +| `DJANGO_MYSQL_DATABASE` | `djangoblog` | Database name. | +| `DJANGO_MYSQL_USER` | `root` | Database username. | +| `DJANGO_MYSQL_PASSWORD` | `djangoblog_123` | Database password. | +| `DJANGO_REDIS_URL` | `redis:6379/0` | Redis connection URL (for caching). | +| `DJANGO_ELASTICSEARCH_HOST`| `elasticsearch:9200` | Elasticsearch host address. | +| `DJANGO_EMAIL_HOST` | `smtp.example.org` | Email server address. | +| `DJANGO_EMAIL_PORT` | `465` | Email server port. | +| `DJANGO_EMAIL_USER` | `user@example.org` | Email account username. | +| `DJANGO_EMAIL_PASSWORD` | `your-email-password` | Email account password. | +| `DJANGO_EMAIL_USE_SSL` | `True` | Whether to use SSL. | +| `DJANGO_EMAIL_USE_TLS` | `False` | Whether to use TLS. | +| `DJANGO_ADMIN_EMAIL` | `admin@example.org` | Admin email for receiving error reports. | +| `DJANGO_BAIDU_NOTIFY_URL` | `http://data.zz.baidu.com/...` | Push API from [Baidu Webmaster Tools](https://ziyuan.baidu.com/linksubmit/index). | + +--- + +After deployment, please review and adjust these environment variables according to your needs, especially `DJANGO_SECRET_KEY` and the database and email settings. \ No newline at end of file diff --git a/src/DjangoBlog-master/docs/docker.md b/src/DjangoBlog-master/docs/docker.md new file mode 100644 index 0000000..e7c255a --- /dev/null +++ b/src/DjangoBlog-master/docs/docker.md @@ -0,0 +1,114 @@ +# 使用 Docker 部署 DjangoBlog + +![Docker Pulls](https://img.shields.io/docker/pulls/liangliangyy/djangoblog) +![Docker Image Version (latest by date)](https://img.shields.io/docker/v/liangliangyy/djangoblog?sort=date) +![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/liangliangyy/djangoblog) + +本项目全面支持使用 Docker 进行容器化部署,为您提供了快速、一致且隔离的运行环境。我们推荐使用 `docker-compose` 来一键启动整个博客服务栈。 + +## 1. 环境准备 + +在开始之前,请确保您的系统中已经安装了以下软件: +- [Docker Engine](https://docs.docker.com/engine/install/) +- [Docker Compose](https://docs.docker.com/compose/install/) (对于 Docker Desktop 用户,它已内置) + +## 2. 推荐方式:使用 `docker-compose` (一键部署) + +这是最简单、最推荐的部署方式。它会自动为您创建并管理 Django 应用、MySQL 数据库,以及可选的 Elasticsearch 服务。 + +### 步骤 1: 启动基础服务 + +在项目根目录下,执行以下命令: + +```bash +# 构建并以后台模式启动容器 (包含 Django 应用和 MySQL) +docker-compose up -d --build +``` + +`docker-compose` 会读取 `docker-compose.yml` 文件,自动拉取所需镜像、构建项目镜像,并启动所有服务。 + +- **访问您的博客**: 服务启动后,在浏览器中访问 `http://127.0.0.1` 即可看到博客首页。 +- **数据持久化**: MySQL 的数据文件将存储在项目根目录下的 `data/mysql` 文件夹中,确保数据在容器重启后不丢失。 + +### 步骤 2: (可选) 启用 Elasticsearch 全文搜索 + +如果您希望使用 Elasticsearch 提供更强大的全文搜索功能,可以额外加载 `docker-compose.es.yml` 配置文件: + +```bash +# 构建并以后台模式启动所有服务 (Django, MySQL, Elasticsearch) +docker-compose -f docker-compose.yml -f deploy/docker-compose/docker-compose.es.yml up -d --build +``` +- **数据持久化**: Elasticsearch 的数据将存储在 `data/elasticsearch` 文件夹中。 + +### 步骤 3: 首次运行的初始化操作 + +当容器首次启动后,您需要进入容器来执行一些初始化命令。 + +```bash +# 进入 djangoblog 应用容器 +docker-compose exec web bash + +# 在容器内执行以下命令: +# 创建超级管理员账户 (请按照提示设置用户名、邮箱和密码) +python manage.py createsuperuser + +# (可选) 创建一些测试数据 +python manage.py create_testdata + +# (可选,如果启用了 ES) 创建索引 +python manage.py rebuild_index + +# 退出容器 +exit +``` + +## 3. 备选方式:使用独立的 Docker 镜像 + +如果您已经拥有一个正在运行的外部 MySQL 数据库,您也可以只运行 DjangoBlog 的应用镜像。 + +```bash +# 从 Docker Hub 拉取最新镜像 +docker pull liangliangyy/djangoblog:latest + +# 运行容器,并链接到您的外部数据库 +docker run -d \ + -p 8000:8000 \ + -e DJANGO_SECRET_KEY='your-strong-secret-key' \ + -e DJANGO_MYSQL_HOST='your-mysql-host' \ + -e DJANGO_MYSQL_USER='your-mysql-user' \ + -e DJANGO_MYSQL_PASSWORD='your-mysql-password' \ + -e DJANGO_MYSQL_DATABASE='djangoblog' \ + --name djangoblog \ + liangliangyy/djangoblog:latest +``` + +- **访问您的博客**: 启动完成后,访问 `http://127.0.0.1:8000`。 +- **创建管理员**: `docker exec -it djangoblog python manage.py createsuperuser` + +## 4. 配置说明 (环境变量) + +本项目的大部分配置都通过环境变量来管理。您可以在 `docker-compose.yml` 文件中修改它们,或者在使用 `docker run` 命令时通过 `-e` 参数传入。 + +| 环境变量名称 | 默认值/示例 | 备注 | +|-------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------| +| `DJANGO_SECRET_KEY` | `your-strong-secret-key` | **请务必修改为一个随机且复杂的字符串!** | +| `DJANGO_DEBUG` | `False` | 是否开启 Django 的调试模式 | +| `DJANGO_MYSQL_HOST` | `mysql` | 数据库主机名 | +| `DJANGO_MYSQL_PORT` | `3306` | 数据库端口 | +| `DJANGO_MYSQL_DATABASE` | `djangoblog` | 数据库名称 | +| `DJANGO_MYSQL_USER` | `root` | 数据库用户名 | +| `DJANGO_MYSQL_PASSWORD` | `djangoblog_123` | 数据库密码 | +| `DJANGO_REDIS_URL` | `redis:6379/0` | Redis 连接地址 (用于缓存) | +| `DJANGO_ELASTICSEARCH_HOST` | `elasticsearch:9200` | Elasticsearch 主机地址 | +| `DJANGO_EMAIL_HOST` | `smtp.example.org` | 邮件服务器地址 | +| `DJANGO_EMAIL_PORT` | `465` | 邮件服务器端口 | +| `DJANGO_EMAIL_USER` | `user@example.org` | 邮件账户 | +| `DJANGO_EMAIL_PASSWORD` | `your-email-password` | 邮件密码 | +| `DJANGO_EMAIL_USE_SSL` | `True` | 是否使用 SSL | +| `DJANGO_EMAIL_USE_TLS` | `False` | 是否使用 TLS | +| `DJANGO_ADMIN_EMAIL` | `admin@example.org` | 接收异常报告的管理员邮箱 | +| `DJANGO_BAIDU_NOTIFY_URL` | `http://data.zz.baidu.com/...` | [百度站长平台](https://ziyuan.baidu.com/linksubmit/index) 的推送接口 | + +--- + +部署完成后,请务必检查并根据您的实际需求调整这些环境变量,特别是 `DJANGO_SECRET_KEY` 和数据库、邮件相关的配置。 diff --git a/src/DjangoBlog-master/docs/es.md b/src/DjangoBlog-master/docs/es.md new file mode 100644 index 0000000..97226c5 --- /dev/null +++ b/src/DjangoBlog-master/docs/es.md @@ -0,0 +1,28 @@ +# 集成Elasticsearch +如果你已经有了`Elasticsearch`环境,那么可以将搜索从`Whoosh`换成`Elasticsearch`,集成方式也很简单, +首先需要注意如下几点: +1. 你的`Elasticsearch`支持`ik`中文分词 +2. 你的`Elasticsearch`版本>=7.3.0 + +接下来在`settings.py`做如下改动即可: +- 增加es链接,如下所示: +```python +ELASTICSEARCH_DSL = { + 'default': { + 'hosts': '127.0.0.1:9200' + }, +} +``` +- 修改`HAYSTACK`配置: +```python +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', + }, +} +``` +然后终端执行: +```shell script +./manage.py build_index +``` +这将会在你的es中创建两个索引,分别是`blog`和`performance`,其中`blog`索引就是搜索所使用的,而`performance`会记录每个请求的响应时间,以供将来优化使用。 \ No newline at end of file diff --git a/src/DjangoBlog-master/docs/imgs/alipay.jpg b/src/DjangoBlog-master/docs/imgs/alipay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..424d70a2ffbb629b481e0c27d72d6076727e8041 GIT binary patch literal 17961 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⁡O0bI!3Bi@jSTgKn>Bg&Q53II7XEA`NhoOY$25L(eK?<1>~>hujoRs}yUHVX zQTt}~MN1Zw0!xe7-aUbnm3fxd*KjwLLobx-SSZdGT`vUtB-=hS^~#Rs9K<#Wao{Zp z{kmB)_tVwwglj-W2qla4$Dx zz53|611jp`Ro8}#TnY+1OdA}SN;lGY+!=)=QY;q0b}yA~i0O^|s1#r34{5DET&Qh` z+yAF+h#H($?p6{{X zz@F@jlhUjuyW%|!{Q!~$q>$)y1Hsl$FMYS;#r=c7U7SN-8Uv-lv<@mZOXKOlXLy4C z3v?;Yh1KbtsO!ynC%cyr-`4MR8{Y~e28GXK7yRZ9{3d5(xoP=XYdX*Uy?#pU$a5>& z`JUOV_ zRl}`OH8`y;U%XD+MFBD!HxL@B`JSH_G!;0V9-A+_4Hhf%=wJ%txE{qNaHkfkLsS+h zfe@JrYreKrFKhU2`1kD)K_#LCd!v$sI$is2ee^7Am=@kq2Z-`cU(l{<8*ON_J?@@& zOR+4E3%sN-vIK-%81R}RdPK$bCrBoGF zRu$>N^0s*R^!6R919|j@|cY|A}(o`$`Onha=;(7(EQNg;0 z^sK+j)DQ>i-wCKpkXH^lH{QNw|FWJ%bUle^xtPfLdv#(gNpA7lv59GyIOXp*E+vz_!y|AkM=lB7mYXSWGfxr6#WlV5UW{v7tVn1!e zwg=v}kG!o+0qXfY-FyUozk}%&PS$ZRL^NoDuz%nVw(p)FVMPG6Ph*mgs4z0~C)P`9 zWsg_X!-)T2FkH=VqaRBu8Zkq=AX6oLSWpg(yQ^NIftdmxjx literal 0 HcmV?d00001 diff --git a/src/DjangoBlog-master/docs/imgs/pycharm_logo.png b/src/DjangoBlog-master/docs/imgs/pycharm_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2a4b0ea66469bd218774de8cb3027a9c18b84d GIT binary patch literal 132045 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 z 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

    *$hI3G#u%WdV|9<`IE_VBVx2~ei~?p& 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


    AewDOjh!MMu(vjQJBsiX3ZCmAui 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 literal 0 HcmV?d00001 diff --git a/src/DjangoBlog-master/docs/k8s-en.md b/src/DjangoBlog-master/docs/k8s-en.md new file mode 100644 index 0000000..20e9527 --- /dev/null +++ b/src/DjangoBlog-master/docs/k8s-en.md @@ -0,0 +1,141 @@ +# Deploying DjangoBlog with Kubernetes + +This document guides you through deploying the DjangoBlog application on a Kubernetes (K8s) cluster. We provide a complete set of `.yaml` configuration files in the `deploy/k8s` directory to deploy a full service stack, including the DjangoBlog application, Nginx, MySQL, Redis, and Elasticsearch. + +## Architecture Overview + +This deployment utilizes a microservices-based, cloud-native architecture: + +- **Core Components**: Each core service (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) runs as a separate `Deployment`. +- **Configuration Management**: Nginx configurations and Django application environment variables are managed via `ConfigMap`. **Note: For sensitive information like passwords, using `Secret` is highly recommended.** +- **Service Discovery**: All services are exposed internally within the cluster as `ClusterIP` type `Service`, enabling communication via service names. +- **External Access**: An `Ingress` resource is used to route external HTTP traffic to the Nginx service, which acts as the single entry point for the entire blog application. +- **Data Persistence**: A `local-storage` solution based on node-local paths is used. This requires you to manually create storage directories on a specific K8s node and statically bind them using `PersistentVolume` (PV) and `PersistentVolumeClaim` (PVC). + +## 1. Prerequisites + +Before you begin, please ensure you have the following: + +- A running Kubernetes cluster. +- The `kubectl` command-line tool configured to connect to your cluster. +- An [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) installed and configured in your cluster. +- Filesystem access to one of the nodes in your cluster (defaulted to `master` in the configs) to create local storage directories. + +## 2. Deployment Steps + +### Step 1: Create a Namespace + +We recommend deploying all DjangoBlog-related resources in a dedicated namespace for better management. + +```bash +# Create a namespace named 'djangoblog' +kubectl create namespace djangoblog +``` + +### Step 2: Configure Persistent Storage + +This setup uses Local Persistent Volumes. You need to create the data storage directories on a node within your cluster (the default is the `master` node in `pv.yaml`). + +```bash +# Log in to your master node +ssh user@master-node + +# Create the required storage directories +sudo mkdir -p /mnt/local-storage-db +sudo mkdir -p /mnt/local-storage-djangoblog +sudo mkdir -p /mnt/resource/ +sudo mkdir -p /mnt/local-storage-elasticsearch + +# Log out from the node +exit +``` +**Note**: If you wish to store data on a different node or use different paths, you must modify the `nodeAffinity` and `local.path` settings in the `deploy/k8s/pv.yaml` file. + +After creating the directories, apply the storage-related configurations: + +```bash +# Apply the StorageClass +kubectl apply -f deploy/k8s/storageclass.yaml + +# Apply the PersistentVolumes (PVs) +kubectl apply -f deploy/k8s/pv.yaml + +# Apply the PersistentVolumeClaims (PVCs) +kubectl apply -f deploy/k8s/pvc.yaml +``` + +### Step 3: Configure the Application + +Before deploying the application, you need to edit the `deploy/k8s/configmap.yaml` file to modify sensitive information and custom settings. + +**It is strongly recommended to change the following fields:** +- `DJANGO_SECRET_KEY`: Change to a random, complex string. +- `DJANGO_MYSQL_PASSWORD` and `MYSQL_ROOT_PASSWORD`: Change to your own secure database password. + +```bash +# Edit the ConfigMap file +vim deploy/k8s/configmap.yaml + +# Apply the configuration +kubectl apply -f deploy/k8s/configmap.yaml +``` + +### Step 4: Deploy the Application Stack + +Now, we can deploy all the core services. + +```bash +# Deploy the Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) +kubectl apply -f deploy/k8s/deployment.yaml + +# Deploy the Services (to create internal endpoints for the Deployments) +kubectl apply -f deploy/k8s/service.yaml +``` + +The deployment may take some time. You can run the following command to check if all Pods are running successfully (STATUS should be `Running`): + +```bash +kubectl get pods -n djangoblog -w +``` + +### Step 5: Expose the Application Externally + +Finally, expose the Nginx service to external traffic by applying the `Ingress` rule. + +```bash +# Apply the Ingress rule +kubectl apply -f deploy/k8s/gateway.yaml +``` + +Once deployed, you can access your blog via the external IP address of your Ingress Controller. Use the following command to find the address: + +```bash +kubectl get ingress -n djangoblog +``` + +### Step 6: First-Time Initialization + +Similar to the Docker deployment, you need to get a shell into the DjangoBlog application Pod to perform database initialization and create a superuser on the first run. + +```bash +# First, get the name of a djangoblog pod +kubectl get pods -n djangoblog | grep djangoblog + +# Exec into one of the Pods (replace [pod-name] with the name from the previous step) +kubectl exec -it [pod-name] -n djangoblog -- bash + +# Inside the Pod, run the following commands: +# Create a superuser account (follow the prompts) +python manage.py createsuperuser + +# (Optional) Create some test data +python manage.py create_testdata + +# (Optional, if ES is enabled) Create the search index +python manage.py rebuild_index + +# Exit the Pod +exit +``` + +Congratulations! You have successfully deployed DjangoBlog on your Kubernetes cluster. \ No newline at end of file diff --git a/src/DjangoBlog-master/docs/k8s.md b/src/DjangoBlog-master/docs/k8s.md new file mode 100644 index 0000000..9da3c28 --- /dev/null +++ b/src/DjangoBlog-master/docs/k8s.md @@ -0,0 +1,141 @@ +# 使用 Kubernetes 部署 DjangoBlog + +本文档将指导您如何在 Kubernetes (K8s) 集群上部署 DjangoBlog 应用。我们提供了一套完整的 `.yaml` 配置文件,位于 `deploy/k8s` 目录下,用于部署一个包含 DjangoBlog 应用、Nginx、MySQL、Redis 和 Elasticsearch 的完整服务栈。 + +## 架构概览 + +本次部署采用的是微服务化的云原生架构: + +- **核心组件**: 每个核心服务 (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) 都将作为独立的 `Deployment` 运行。 +- **配置管理**: Nginx 的配置文件和 Django 应用的环境变量通过 `ConfigMap` 进行管理。**注意:敏感信息(如密码)建议使用 `Secret` 进行管理。** +- **服务发现**: 所有服务都通过 `ClusterIP` 类型的 `Service` 在集群内部暴露,并通过服务名相互通信。 +- **外部访问**: 使用 `Ingress` 资源将外部的 HTTP 流量路由到 Nginx 服务,作为整个博客应用的统一入口。 +- **数据持久化**: 采用基于节点本地路径的 `local-storage` 方案。这需要您在指定的 K8s 节点上手动创建存储目录,并通过 `PersistentVolume` (PV) 和 `PersistentVolumeClaim` (PVC) 进行静态绑定。 + +## 1. 环境准备 + +在开始之前,请确保您已具备以下环境: + +- 一个正在运行的 Kubernetes 集群。 +- `kubectl` 命令行工具已配置并能够连接到您的集群。 +- 集群中已安装并配置好 [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/)。 +- 对集群中的一个节点(默认为 `master`)拥有文件系统访问权限,用于创建本地存储目录。 + +## 2. 部署步骤 + +### 步骤 1: 创建命名空间 + +我们建议将 DjangoBlog 相关的所有资源都部署在一个独立的命名空间中,便于管理。 + +```bash +# 创建一个名为 djangoblog 的命名空间 +kubectl create namespace djangoblog +``` + +### 步骤 2: 配置持久化存储 + +此方案使用本地持久卷 (Local Persistent Volume)。您需要在集群的一个节点上(在 `pv.yaml` 文件中默认为 `master` 节点)创建用于数据存储的目录。 + +```bash +# 登录到您的 master 节点 +ssh user@master-node + +# 创建所需的存储目录 +sudo mkdir -p /mnt/local-storage-db +sudo mkdir -p /mnt/local-storage-djangoblog +sudo mkdir -p /mnt/resource/ +sudo mkdir -p /mnt/local-storage-elasticsearch + +# 退出节点 +exit +``` +**注意**: 如果您希望将数据存储在其他节点或使用不同的路径,请务必修改 `deploy/k8s/pv.yaml` 文件中 `nodeAffinity` 和 `local.path` 的配置。 + +创建目录后,应用存储相关的配置文件: + +```bash +# 应用 StorageClass +kubectl apply -f deploy/k8s/storageclass.yaml + +# 应用 PersistentVolume (PV) +kubectl apply -f deploy/k8s/pv.yaml + +# 应用 PersistentVolumeClaim (PVC) +kubectl apply -f deploy/k8s/pvc.yaml +``` + +### 步骤 3: 配置应用 + +在部署应用之前,您需要编辑 `deploy/k8s/configmap.yaml` 文件,修改其中的敏感信息和个性化配置。 + +**强烈建议修改以下字段:** +- `DJANGO_SECRET_KEY`: 修改为一个随机且复杂的字符串。 +- `DJANGO_MYSQL_PASSWORD` 和 `MYSQL_ROOT_PASSWORD`: 修改为您自己的数据库密码。 + +```bash +# 编辑 ConfigMap 文件 +vim deploy/k8s/configmap.yaml + +# 应用配置 +kubectl apply -f deploy/k8s/configmap.yaml +``` + +### 步骤 4: 部署应用服务栈 + +现在,我们可以部署所有的核心服务了。 + +```bash +# 部署 Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) +kubectl apply -f deploy/k8s/deployment.yaml + +# 部署 Services (为 Deployments 创建内部访问端点) +kubectl apply -f deploy/k8s/service.yaml +``` + +部署需要一些时间,您可以运行以下命令检查所有 Pod 是否都已成功运行 (STATUS 为 `Running`): + +```bash +kubectl get pods -n djangoblog -w +``` + +### 步骤 5: 暴露应用到外部 + +最后,通过应用 `Ingress` 规则来将外部流量引导至我们的 Nginx 服务。 + +```bash +# 应用 Ingress 规则 +kubectl apply -f deploy/k8s/gateway.yaml +``` + +部署完成后,您可以通过 Ingress Controller 的外部 IP 地址来访问您的博客。执行以下命令获取地址: + +```bash +kubectl get ingress -n djangoblog +``` + +### 步骤 6: 首次运行的初始化操作 + +与 Docker 部署类似,首次运行时,您需要进入 DjangoBlog 应用的 Pod 来执行数据库初始化和创建管理员账户。 + +```bash +# 首先,获取 djangoblog pod 的名称 +kubectl get pods -n djangoblog | grep djangoblog + +# 进入其中一个 Pod (将 [pod-name] 替换为上一步获取到的名称) +kubectl exec -it [pod-name] -n djangoblog -- bash + +# 在 Pod 内部执行以下命令: +# 创建超级管理员账户 (请按照提示操作) +python manage.py createsuperuser + +# (可选) 创建测试数据 +python manage.py create_testdata + +# (可选,如果启用了 ES) 创建索引 +python manage.py rebuild_index + +# 退出 Pod +exit +``` + +至此,您已成功在 Kubernetes 集群上完成了 DjangoBlog 的部署! \ No newline at end of file diff --git a/src/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo b/src/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..f63669f46b3283a84e04098a7338b55f204e7b9d GIT binary patch literal 11097 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 literal 0 HcmV?d00001 diff --git a/src/DjangoBlog-master/locale/en/LC_MESSAGES/django.po b/src/DjangoBlog-master/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..c80b30a --- /dev/null +++ b/src/DjangoBlog-master/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,685 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-09-13 16:02+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: .\accounts\admin.py:12 +msgid "password" +msgstr "password" + +#: .\accounts\admin.py:13 +msgid "Enter password again" +msgstr "Enter password again" + +#: .\accounts\admin.py:24 .\accounts\forms.py:89 +msgid "passwords do not match" +msgstr "passwords do not match" + +#: .\accounts\forms.py:36 +msgid "email already exists" +msgstr "email already exists" + +#: .\accounts\forms.py:46 .\accounts\forms.py:50 +msgid "New password" +msgstr "New password" + +#: .\accounts\forms.py:60 +msgid "Confirm password" +msgstr "Confirm password" + +#: .\accounts\forms.py:70 .\accounts\forms.py:116 +msgid "Email" +msgstr "Email" + +#: .\accounts\forms.py:76 .\accounts\forms.py:80 +msgid "Code" +msgstr "Code" + +#: .\accounts\forms.py:100 .\accounts\tests.py:194 +msgid "email does not exist" +msgstr "email does not exist" + +#: .\accounts\models.py:12 .\oauth\models.py:17 +msgid "nick name" +msgstr "nick name" + +#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 +#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 +#: .\oauth\models.py:53 +msgid "creation time" +msgstr "creation time" + +#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 +#: .\oauth\models.py:54 +msgid "last modify time" +msgstr "last modify time" + +#: .\accounts\models.py:15 +msgid "create source" +msgstr "create source" + +#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 +msgid "user" +msgstr "user" + +#: .\accounts\tests.py:216 .\accounts\utils.py:39 +msgid "Verification code error" +msgstr "Verification code error" + +#: .\accounts\utils.py:13 +msgid "Verify Email" +msgstr "Verify Email" + +#: .\accounts\utils.py:21 +#, python-format +msgid "" +"You are resetting the password, the verification code is:%(code)s, valid " +"within 5 minutes, please keep it properly" +msgstr "" +"You are resetting the password, the verification code is:%(code)s, valid " +"within 5 minutes, please keep it properly" + +#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 +#: .\oauth\models.py:12 +msgid "author" +msgstr "author" + +#: .\blog\admin.py:53 +msgid "Publish selected articles" +msgstr "Publish selected articles" + +#: .\blog\admin.py:54 +msgid "Draft selected articles" +msgstr "Draft selected articles" + +#: .\blog\admin.py:55 +msgid "Close article comments" +msgstr "Close article comments" + +#: .\blog\admin.py:56 +msgid "Open article comments" +msgstr "Open article comments" + +#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 +#: .\templates\blog\tags\sidebar.html:40 +msgid "category" +msgstr "category" + +#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 +msgid "index" +msgstr "index" + +#: .\blog\models.py:21 +msgid "list" +msgstr "list" + +#: .\blog\models.py:22 +msgid "post" +msgstr "post" + +#: .\blog\models.py:23 +msgid "all" +msgstr "all" + +#: .\blog\models.py:24 +msgid "slide" +msgstr "slide" + +#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 +msgid "modify time" +msgstr "modify time" + +#: .\blog\models.py:63 +msgid "Draft" +msgstr "Draft" + +#: .\blog\models.py:64 +msgid "Published" +msgstr "Published" + +#: .\blog\models.py:67 +msgid "Open" +msgstr "Open" + +#: .\blog\models.py:68 +msgid "Close" +msgstr "Close" + +#: .\blog\models.py:71 .\comments\admin.py:47 +msgid "Article" +msgstr "Article" + +#: .\blog\models.py:72 +msgid "Page" +msgstr "Page" + +#: .\blog\models.py:74 .\blog\models.py:280 +msgid "title" +msgstr "title" + +#: .\blog\models.py:75 +msgid "body" +msgstr "body" + +#: .\blog\models.py:77 +msgid "publish time" +msgstr "publish time" + +#: .\blog\models.py:79 +msgid "status" +msgstr "status" + +#: .\blog\models.py:84 +msgid "comment status" +msgstr "comment status" + +#: .\blog\models.py:88 .\oauth\models.py:43 +msgid "type" +msgstr "type" + +#: .\blog\models.py:89 +msgid "views" +msgstr "views" + +#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 +msgid "order" +msgstr "order" + +#: .\blog\models.py:98 +msgid "show toc" +msgstr "show toc" + +#: .\blog\models.py:105 .\blog\models.py:249 +msgid "tag" +msgstr "tag" + +#: .\blog\models.py:115 .\comments\models.py:21 +msgid "article" +msgstr "article" + +#: .\blog\models.py:171 +msgid "category name" +msgstr "category name" + +#: .\blog\models.py:174 +msgid "parent category" +msgstr "parent category" + +#: .\blog\models.py:234 +msgid "tag name" +msgstr "tag name" + +#: .\blog\models.py:256 +msgid "link name" +msgstr "link name" + +#: .\blog\models.py:257 .\blog\models.py:271 +msgid "link" +msgstr "link" + +#: .\blog\models.py:260 +msgid "is show" +msgstr "is show" + +#: .\blog\models.py:262 +msgid "show type" +msgstr "show type" + +#: .\blog\models.py:281 +msgid "content" +msgstr "content" + +#: .\blog\models.py:283 .\oauth\models.py:52 +msgid "is enable" +msgstr "is enable" + +#: .\blog\models.py:289 +msgid "sidebar" +msgstr "sidebar" + +#: .\blog\models.py:299 +msgid "site name" +msgstr "site name" + +#: .\blog\models.py:305 +msgid "site description" +msgstr "site description" + +#: .\blog\models.py:311 +msgid "site seo description" +msgstr "site seo description" + +#: .\blog\models.py:313 +msgid "site keywords" +msgstr "site keywords" + +#: .\blog\models.py:318 +msgid "article sub length" +msgstr "article sub length" + +#: .\blog\models.py:319 +msgid "sidebar article count" +msgstr "sidebar article count" + +#: .\blog\models.py:320 +msgid "sidebar comment count" +msgstr "sidebar comment count" + +#: .\blog\models.py:321 +msgid "article comment count" +msgstr "article comment count" + +#: .\blog\models.py:322 +msgid "show adsense" +msgstr "show adsense" + +#: .\blog\models.py:324 +msgid "adsense code" +msgstr "adsense code" + +#: .\blog\models.py:325 +msgid "open site comment" +msgstr "open site comment" + +#: .\blog\models.py:352 +msgid "Website configuration" +msgstr "Website configuration" + +#: .\blog\models.py:360 +msgid "There can only be one configuration" +msgstr "There can only be one configuration" + +#: .\blog\views.py:348 +msgid "" +"Sorry, the page you requested is not found, please click the home page to " +"see other?" +msgstr "" +"Sorry, the page you requested is not found, please click the home page to " +"see other?" + +#: .\blog\views.py:356 +msgid "Sorry, the server is busy, please click the home page to see other?" +msgstr "Sorry, the server is busy, please click the home page to see other?" + +#: .\blog\views.py:369 +msgid "Sorry, you do not have permission to access this page?" +msgstr "Sorry, you do not have permission to access this page?" + +#: .\comments\admin.py:15 +msgid "Disable comments" +msgstr "Disable comments" + +#: .\comments\admin.py:16 +msgid "Enable comments" +msgstr "Enable comments" + +#: .\comments\admin.py:46 +msgid "User" +msgstr "User" + +#: .\comments\models.py:25 +msgid "parent comment" +msgstr "parent comment" + +#: .\comments\models.py:29 +msgid "enable" +msgstr "enable" + +#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 +msgid "comment" +msgstr "comment" + +#: .\comments\utils.py:13 +msgid "Thanks for your comment" +msgstr "Thanks for your comment" + +#: .\comments\utils.py:15 +#, python-format +msgid "" +"

    Thank you very much for your comments on this site

    \n" +" You can visit
    %(article_title)s\n" +" to review your comments,\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s" +msgstr "" +"

    Thank you very much for your comments on this site

    \n" +" You can visit %(article_title)s\n" +" to review your comments,\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s" + +#: .\comments\utils.py:26 +#, python-format +msgid "" +"Your comment on " +"%(article_title)s
    has \n" +" received a reply.
    %(comment_body)s\n" +"
    \n" +" go check it out!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s\n" +" " +msgstr "" +"Your comment on " +"%(article_title)s
    has \n" +" received a reply.
    %(comment_body)s\n" +"
    \n" +" go check it out!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s\n" +" " + +#: .\djangoblog\logentryadmin.py:63 +msgid "object" +msgstr "object" + +#: .\djangoblog\settings.py:140 +msgid "English" +msgstr "English" + +#: .\djangoblog\settings.py:141 +msgid "Simplified Chinese" +msgstr "Simplified Chinese" + +#: .\djangoblog\settings.py:142 +msgid "Traditional Chinese" +msgstr "Traditional Chinese" + +#: .\oauth\models.py:30 +msgid "oauth user" +msgstr "oauth user" + +#: .\oauth\models.py:37 +msgid "weibo" +msgstr "weibo" + +#: .\oauth\models.py:38 +msgid "google" +msgstr "google" + +#: .\oauth\models.py:48 +msgid "callback url" +msgstr "callback url" + +#: .\oauth\models.py:59 +msgid "already exists" +msgstr "already exists" + +#: .\oauth\views.py:154 +#, python-format +msgid "" +"\n" +"

    Congratulations, you have successfully bound your email address. You " +"can use\n" +" %(oauthuser_type)s to directly log in to this website without a " +"password.

    \n" +" You are welcome to continue to follow this site, the address is\n" +" %(site)s\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link to your " +"browser.\n" +" %(site)s\n" +" " +msgstr "" +"\n" +"

    Congratulations, you have successfully bound your email address. You " +"can use\n" +" %(oauthuser_type)s to directly log in to this website without a " +"password.

    \n" +" You are welcome to continue to follow this site, the address is\n" +" %(site)s\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link to your " +"browser.\n" +" %(site)s\n" +" " + +#: .\oauth\views.py:165 +msgid "Congratulations on your successful binding!" +msgstr "Congratulations on your successful binding!" + +#: .\oauth\views.py:217 +#, python-format +msgid "" +"\n" +"

    Please click the link below to bind your email

    \n" +"\n" +" %(url)s\n" +"\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link " +"to your browser.\n" +"
    \n" +" %(url)s\n" +" " +msgstr "" +"\n" +"

    Please click the link below to bind your email

    \n" +"\n" +" %(url)s\n" +"\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link " +"to your browser.\n" +"
    \n" +" %(url)s\n" +" " + +#: .\oauth\views.py:228 .\oauth\views.py:240 +msgid "Bind your email" +msgstr "Bind your email" + +#: .\oauth\views.py:242 +msgid "" +"Congratulations, the binding is just one step away. Please log in to your " +"email to check the email to complete the binding. Thank you." +msgstr "" +"Congratulations, the binding is just one step away. Please log in to your " +"email to check the email to complete the binding. Thank you." + +#: .\oauth\views.py:245 +msgid "Binding successful" +msgstr "Binding successful" + +#: .\oauth\views.py:247 +#, python-format +msgid "" +"Congratulations, you have successfully bound your email address. You can use " +"%(oauthuser_type)s to directly log in to this website without a password. " +"You are welcome to continue to follow this site." +msgstr "" +"Congratulations, you have successfully bound your email address. You can use " +"%(oauthuser_type)s to directly log in to this website without a password. " +"You are welcome to continue to follow this site." + +#: .\templates\account\forget_password.html:7 +msgid "forget the password" +msgstr "forget the password" + +#: .\templates\account\forget_password.html:18 +msgid "get verification code" +msgstr "get verification code" + +#: .\templates\account\forget_password.html:19 +msgid "submit" +msgstr "submit" + +#: .\templates\account\login.html:36 +msgid "Create Account" +msgstr "Create Account" + +#: .\templates\account\login.html:42 +#, fuzzy +#| msgid "forget the password" +msgid "Forget Password" +msgstr "forget the password" + +#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 +msgid "login" +msgstr "login" + +#: .\templates\account\result.html:22 +msgid "back to the homepage" +msgstr "back to the homepage" + +#: .\templates\blog\article_archives.html:7 +#: .\templates\blog\article_archives.html:24 +msgid "article archive" +msgstr "article archive" + +#: .\templates\blog\article_archives.html:32 +msgid "year" +msgstr "year" + +#: .\templates\blog\article_archives.html:36 +msgid "month" +msgstr "month" + +#: .\templates\blog\tags\article_info.html:12 +msgid "pin to top" +msgstr "pin to top" + +#: .\templates\blog\tags\article_info.html:28 +msgid "comments" +msgstr "comments" + +#: .\templates\blog\tags\article_info.html:58 +msgid "toc" +msgstr "toc" + +#: .\templates\blog\tags\article_meta_info.html:6 +msgid "posted in" +msgstr "posted in" + +#: .\templates\blog\tags\article_meta_info.html:14 +msgid "and tagged" +msgstr "and tagged" + +#: .\templates\blog\tags\article_meta_info.html:25 +msgid "by " +msgstr "by" + +#: .\templates\blog\tags\article_meta_info.html:29 +#, python-format +msgid "" +"\n" +" title=\"View all articles published by " +"%(article.author.username)s\"\n" +" " +msgstr "" +"\n" +" title=\"View all articles published by " +"%(article.author.username)s\"\n" +" " + +#: .\templates\blog\tags\article_meta_info.html:44 +msgid "on" +msgstr "on" + +#: .\templates\blog\tags\article_meta_info.html:54 +msgid "edit" +msgstr "edit" + +#: .\templates\blog\tags\article_pagination.html:4 +msgid "article navigation" +msgstr "article navigation" + +#: .\templates\blog\tags\article_pagination.html:9 +msgid "earlier articles" +msgstr "earlier articles" + +#: .\templates\blog\tags\article_pagination.html:12 +msgid "newer articles" +msgstr "newer articles" + +#: .\templates\blog\tags\article_tag_list.html:5 +msgid "tags" +msgstr "tags" + +#: .\templates\blog\tags\sidebar.html:7 +msgid "search" +msgstr "search" + +#: .\templates\blog\tags\sidebar.html:50 +msgid "recent comments" +msgstr "recent comments" + +#: .\templates\blog\tags\sidebar.html:57 +msgid "published on" +msgstr "published on" + +#: .\templates\blog\tags\sidebar.html:65 +msgid "recent articles" +msgstr "recent articles" + +#: .\templates\blog\tags\sidebar.html:77 +msgid "bookmark" +msgstr "bookmark" + +#: .\templates\blog\tags\sidebar.html:96 +msgid "Tag Cloud" +msgstr "Tag Cloud" + +#: .\templates\blog\tags\sidebar.html:107 +msgid "Welcome to star or fork the source code of this site" +msgstr "Welcome to star or fork the source code of this site" + +#: .\templates\blog\tags\sidebar.html:118 +msgid "Function" +msgstr "Function" + +#: .\templates\blog\tags\sidebar.html:120 +msgid "management site" +msgstr "management site" + +#: .\templates\blog\tags\sidebar.html:122 +msgid "logout" +msgstr "logout" + +#: .\templates\blog\tags\sidebar.html:129 +msgid "Track record" +msgstr "Track record" + +#: .\templates\blog\tags\sidebar.html:135 +msgid "Click me to return to the top" +msgstr "Click me to return to the top" + +#: .\templates\oauth\oauth_applications.html:5 +#| msgid "login" +msgid "quick login" +msgstr "quick login" + +#: .\templates\share_layout\nav.html:26 +msgid "Article archive" +msgstr "Article archive" diff --git a/src/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo b/src/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..a2d36e98a180a2d9f413841d0cfa0c5f85654f63 GIT binary patch literal 10321 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, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-09-13 16:02+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: .\accounts\admin.py:12 +msgid "password" +msgstr "密码" + +#: .\accounts\admin.py:13 +msgid "Enter password again" +msgstr "再次输入密码" + +#: .\accounts\admin.py:24 .\accounts\forms.py:89 +msgid "passwords do not match" +msgstr "密码不匹配" + +#: .\accounts\forms.py:36 +msgid "email already exists" +msgstr "邮箱已存在" + +#: .\accounts\forms.py:46 .\accounts\forms.py:50 +msgid "New password" +msgstr "新密码" + +#: .\accounts\forms.py:60 +msgid "Confirm password" +msgstr "确认密码" + +#: .\accounts\forms.py:70 .\accounts\forms.py:116 +msgid "Email" +msgstr "邮箱" + +#: .\accounts\forms.py:76 .\accounts\forms.py:80 +msgid "Code" +msgstr "验证码" + +#: .\accounts\forms.py:100 .\accounts\tests.py:194 +msgid "email does not exist" +msgstr "邮箱不存在" + +#: .\accounts\models.py:12 .\oauth\models.py:17 +msgid "nick name" +msgstr "昵称" + +#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 +#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 +#: .\oauth\models.py:53 +msgid "creation time" +msgstr "创建时间" + +#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 +#: .\oauth\models.py:54 +msgid "last modify time" +msgstr "最后修改时间" + +#: .\accounts\models.py:15 +msgid "create source" +msgstr "来源" + +#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 +msgid "user" +msgstr "用户" + +#: .\accounts\tests.py:216 .\accounts\utils.py:39 +msgid "Verification code error" +msgstr "验证码错误" + +#: .\accounts\utils.py:13 +msgid "Verify Email" +msgstr "验证邮箱" + +#: .\accounts\utils.py:21 +#, python-format +msgid "" +"You are resetting the password, the verification code is:%(code)s, valid " +"within 5 minutes, please keep it properly" +msgstr "您正在重置密码,验证码为:%(code)s,5分钟内有效 请妥善保管." + +#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 +#: .\oauth\models.py:12 +msgid "author" +msgstr "作者" + +#: .\blog\admin.py:53 +msgid "Publish selected articles" +msgstr "发布选中的文章" + +#: .\blog\admin.py:54 +msgid "Draft selected articles" +msgstr "选中文章设为草稿" + +#: .\blog\admin.py:55 +msgid "Close article comments" +msgstr "关闭文章评论" + +#: .\blog\admin.py:56 +msgid "Open article comments" +msgstr "打开文章评论" + +#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 +#: .\templates\blog\tags\sidebar.html:40 +msgid "category" +msgstr "分类目录" + +#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 +msgid "index" +msgstr "首页" + +#: .\blog\models.py:21 +msgid "list" +msgstr "列表" + +#: .\blog\models.py:22 +msgid "post" +msgstr "文章" + +#: .\blog\models.py:23 +msgid "all" +msgstr "所有" + +#: .\blog\models.py:24 +msgid "slide" +msgstr "侧边栏" + +#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 +msgid "modify time" +msgstr "修改时间" + +#: .\blog\models.py:63 +msgid "Draft" +msgstr "草稿" + +#: .\blog\models.py:64 +msgid "Published" +msgstr "发布" + +#: .\blog\models.py:67 +msgid "Open" +msgstr "打开" + +#: .\blog\models.py:68 +msgid "Close" +msgstr "关闭" + +#: .\blog\models.py:71 .\comments\admin.py:47 +msgid "Article" +msgstr "文章" + +#: .\blog\models.py:72 +msgid "Page" +msgstr "页面" + +#: .\blog\models.py:74 .\blog\models.py:280 +msgid "title" +msgstr "标题" + +#: .\blog\models.py:75 +msgid "body" +msgstr "内容" + +#: .\blog\models.py:77 +msgid "publish time" +msgstr "发布时间" + +#: .\blog\models.py:79 +msgid "status" +msgstr "状态" + +#: .\blog\models.py:84 +msgid "comment status" +msgstr "评论状态" + +#: .\blog\models.py:88 .\oauth\models.py:43 +msgid "type" +msgstr "类型" + +#: .\blog\models.py:89 +msgid "views" +msgstr "阅读量" + +#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 +msgid "order" +msgstr "排序" + +#: .\blog\models.py:98 +msgid "show toc" +msgstr "显示目录" + +#: .\blog\models.py:105 .\blog\models.py:249 +msgid "tag" +msgstr "标签" + +#: .\blog\models.py:115 .\comments\models.py:21 +msgid "article" +msgstr "文章" + +#: .\blog\models.py:171 +msgid "category name" +msgstr "分类名" + +#: .\blog\models.py:174 +msgid "parent category" +msgstr "上级分类" + +#: .\blog\models.py:234 +msgid "tag name" +msgstr "标签名" + +#: .\blog\models.py:256 +msgid "link name" +msgstr "链接名" + +#: .\blog\models.py:257 .\blog\models.py:271 +msgid "link" +msgstr "链接" + +#: .\blog\models.py:260 +msgid "is show" +msgstr "是否显示" + +#: .\blog\models.py:262 +msgid "show type" +msgstr "显示类型" + +#: .\blog\models.py:281 +msgid "content" +msgstr "内容" + +#: .\blog\models.py:283 .\oauth\models.py:52 +msgid "is enable" +msgstr "是否启用" + +#: .\blog\models.py:289 +msgid "sidebar" +msgstr "侧边栏" + +#: .\blog\models.py:299 +msgid "site name" +msgstr "站点名称" + +#: .\blog\models.py:305 +msgid "site description" +msgstr "站点描述" + +#: .\blog\models.py:311 +msgid "site seo description" +msgstr "站点SEO描述" + +#: .\blog\models.py:313 +msgid "site keywords" +msgstr "关键字" + +#: .\blog\models.py:318 +msgid "article sub length" +msgstr "文章摘要长度" + +#: .\blog\models.py:319 +msgid "sidebar article count" +msgstr "侧边栏文章数目" + +#: .\blog\models.py:320 +msgid "sidebar comment count" +msgstr "侧边栏评论数目" + +#: .\blog\models.py:321 +msgid "article comment count" +msgstr "文章页面默认显示评论数目" + +#: .\blog\models.py:322 +msgid "show adsense" +msgstr "是否显示广告" + +#: .\blog\models.py:324 +msgid "adsense code" +msgstr "广告内容" + +#: .\blog\models.py:325 +msgid "open site comment" +msgstr "公共头部" + +#: .\blog\models.py:352 +msgid "Website configuration" +msgstr "网站配置" + +#: .\blog\models.py:360 +msgid "There can only be one configuration" +msgstr "只能有一个配置" + +#: .\blog\views.py:348 +msgid "" +"Sorry, the page you requested is not found, please click the home page to " +"see other?" +msgstr "抱歉,你所访问的页面找不到,请点击首页看看别的?" + +#: .\blog\views.py:356 +msgid "Sorry, the server is busy, please click the home page to see other?" +msgstr "抱歉,服务出错了,请点击首页看看别的?" + +#: .\blog\views.py:369 +msgid "Sorry, you do not have permission to access this page?" +msgstr "抱歉,你没用权限访问此页面。" + +#: .\comments\admin.py:15 +msgid "Disable comments" +msgstr "禁用评论" + +#: .\comments\admin.py:16 +msgid "Enable comments" +msgstr "启用评论" + +#: .\comments\admin.py:46 +msgid "User" +msgstr "用户" + +#: .\comments\models.py:25 +msgid "parent comment" +msgstr "上级评论" + +#: .\comments\models.py:29 +msgid "enable" +msgstr "启用" + +#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 +msgid "comment" +msgstr "评论" + +#: .\comments\utils.py:13 +msgid "Thanks for your comment" +msgstr "感谢你的评论" + +#: .\comments\utils.py:15 +#, python-format +msgid "" +"

    Thank you very much for your comments on this site

    \n" +" You can visit %(article_title)s\n" +" to review your comments,\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s" +msgstr "" +"

    非常感谢您对此网站的评论

    \n" +" 您可以访问%(article_title)s\n" +"查看您的评论,\n" +"再次感谢您!\n" +"
    \n" +" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n" +"%(article_url)s" + +#: .\comments\utils.py:26 +#, python-format +msgid "" +"Your comment on " +"%(article_title)s
    has \n" +" received a reply.
    %(comment_body)s\n" +"
    \n" +" go check it out!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s\n" +" " +msgstr "" +"您对 %(article_title)s
    " +"的评论有\n" +" 收到回复。
    %(comment_body)s\n" +"
    \n" +"快去看看吧!\n" +"
    \n" +" 如果上面的链接打不开,请复制此链接链接到您的浏览器。\n" +" %(article_url)s\n" +" " + +#: .\djangoblog\logentryadmin.py:63 +msgid "object" +msgstr "对象" + +#: .\djangoblog\settings.py:140 +msgid "English" +msgstr "英文" + +#: .\djangoblog\settings.py:141 +msgid "Simplified Chinese" +msgstr "简体中文" + +#: .\djangoblog\settings.py:142 +msgid "Traditional Chinese" +msgstr "繁体中文" + +#: .\oauth\models.py:30 +msgid "oauth user" +msgstr "第三方用户" + +#: .\oauth\models.py:37 +msgid "weibo" +msgstr "微博" + +#: .\oauth\models.py:38 +msgid "google" +msgstr "谷歌" + +#: .\oauth\models.py:48 +msgid "callback url" +msgstr "回调地址" + +#: .\oauth\models.py:59 +msgid "already exists" +msgstr "已经存在" + +#: .\oauth\views.py:154 +#, python-format +msgid "" +"\n" +"

    Congratulations, you have successfully bound your email address. You " +"can use\n" +" %(oauthuser_type)s to directly log in to this website without a " +"password.

    \n" +" You are welcome to continue to follow this site, the address is\n" +" %(site)s\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link to your " +"browser.\n" +" %(site)s\n" +" " +msgstr "" +"\n" +"

    恭喜你已经绑定成功 你可以使用\n" +" %(oauthuser_type)s 来免密登录本站

    \n" +" 欢迎继续关注本站, 地址是\n" +" %(site)s\n" +" 再次感谢你\n" +"
    \n" +" 如果上面链接无法打开,请复制此链接到你的浏览器 \n" +" %(site)s\n" +" " + +#: .\oauth\views.py:165 +msgid "Congratulations on your successful binding!" +msgstr "恭喜你绑定成功" + +#: .\oauth\views.py:217 +#, python-format +msgid "" +"\n" +"

    Please click the link below to bind your email

    \n" +"\n" +" %(url)s\n" +"\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link " +"to your browser.\n" +"
    \n" +" %(url)s\n" +" " +msgstr "" +"\n" +"

    请点击下面的链接绑定您的邮箱

    \n" +"\n" +" %(url)s\n" +"\n" +"再次感谢您!\n" +"
    \n" +"如果上面的链接打不开,请复制此链接到您的浏览器。\n" +"%(url)s\n" +" " + +#: .\oauth\views.py:228 .\oauth\views.py:240 +msgid "Bind your email" +msgstr "绑定邮箱" + +#: .\oauth\views.py:242 +msgid "" +"Congratulations, the binding is just one step away. Please log in to your " +"email to check the email to complete the binding. Thank you." +msgstr "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。" + +#: .\oauth\views.py:245 +msgid "Binding successful" +msgstr "绑定成功" + +#: .\oauth\views.py:247 +#, python-format +msgid "" +"Congratulations, you have successfully bound your email address. You can use " +"%(oauthuser_type)s to directly log in to this website without a password. " +"You are welcome to continue to follow this site." +msgstr "" +"恭喜您绑定成功,您以后可以使用%(oauthuser_type)s来直接免密码登录本站啦,感谢" +"您对本站对关注。" + +#: .\templates\account\forget_password.html:7 +msgid "forget the password" +msgstr "忘记密码" + +#: .\templates\account\forget_password.html:18 +msgid "get verification code" +msgstr "获取验证码" + +#: .\templates\account\forget_password.html:19 +msgid "submit" +msgstr "提交" + +#: .\templates\account\login.html:36 +msgid "Create Account" +msgstr "创建账号" + +#: .\templates\account\login.html:42 +#| msgid "forget the password" +msgid "Forget Password" +msgstr "忘记密码" + +#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 +msgid "login" +msgstr "登录" + +#: .\templates\account\result.html:22 +msgid "back to the homepage" +msgstr "返回首页吧" + +#: .\templates\blog\article_archives.html:7 +#: .\templates\blog\article_archives.html:24 +msgid "article archive" +msgstr "文章归档" + +#: .\templates\blog\article_archives.html:32 +msgid "year" +msgstr "年" + +#: .\templates\blog\article_archives.html:36 +msgid "month" +msgstr "月" + +#: .\templates\blog\tags\article_info.html:12 +msgid "pin to top" +msgstr "置顶" + +#: .\templates\blog\tags\article_info.html:28 +msgid "comments" +msgstr "评论" + +#: .\templates\blog\tags\article_info.html:58 +msgid "toc" +msgstr "目录" + +#: .\templates\blog\tags\article_meta_info.html:6 +msgid "posted in" +msgstr "发布于" + +#: .\templates\blog\tags\article_meta_info.html:14 +msgid "and tagged" +msgstr "并标记为" + +#: .\templates\blog\tags\article_meta_info.html:25 +msgid "by " +msgstr "由" + +#: .\templates\blog\tags\article_meta_info.html:29 +#, python-format +msgid "" +"\n" +" title=\"View all articles published by " +"%(article.author.username)s\"\n" +" " +msgstr "" +"\n" +" title=\"查看所有由 %(article.author.username)s\"发布的文章\n" +" " + +#: .\templates\blog\tags\article_meta_info.html:44 +msgid "on" +msgstr "在" + +#: .\templates\blog\tags\article_meta_info.html:54 +msgid "edit" +msgstr "编辑" + +#: .\templates\blog\tags\article_pagination.html:4 +msgid "article navigation" +msgstr "文章导航" + +#: .\templates\blog\tags\article_pagination.html:9 +msgid "earlier articles" +msgstr "早期文章" + +#: .\templates\blog\tags\article_pagination.html:12 +msgid "newer articles" +msgstr "较新文章" + +#: .\templates\blog\tags\article_tag_list.html:5 +msgid "tags" +msgstr "标签" + +#: .\templates\blog\tags\sidebar.html:7 +msgid "search" +msgstr "搜索" + +#: .\templates\blog\tags\sidebar.html:50 +msgid "recent comments" +msgstr "近期评论" + +#: .\templates\blog\tags\sidebar.html:57 +msgid "published on" +msgstr "发表于" + +#: .\templates\blog\tags\sidebar.html:65 +msgid "recent articles" +msgstr "近期文章" + +#: .\templates\blog\tags\sidebar.html:77 +msgid "bookmark" +msgstr "书签" + +#: .\templates\blog\tags\sidebar.html:96 +msgid "Tag Cloud" +msgstr "标签云" + +#: .\templates\blog\tags\sidebar.html:107 +msgid "Welcome to star or fork the source code of this site" +msgstr "欢迎您STAR或者FORK本站源代码" + +#: .\templates\blog\tags\sidebar.html:118 +msgid "Function" +msgstr "功能" + +#: .\templates\blog\tags\sidebar.html:120 +msgid "management site" +msgstr "管理站点" + +#: .\templates\blog\tags\sidebar.html:122 +msgid "logout" +msgstr "登出" + +#: .\templates\blog\tags\sidebar.html:129 +msgid "Track record" +msgstr "运动轨迹记录" + +#: .\templates\blog\tags\sidebar.html:135 +msgid "Click me to return to the top" +msgstr "点我返回顶部" + +#: .\templates\oauth\oauth_applications.html:5 +#| msgid "login" +msgid "quick login" +msgstr "快捷登录" + +#: .\templates\share_layout\nav.html:26 +msgid "Article archive" +msgstr "文章归档" diff --git a/src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo b/src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..fe2ea17dc2636742b9c3b8d4eddb6293ee3b7290 GIT binary patch literal 10268 zcmcIodvp}ndB3h-iBc!F>%?(VCj&7yG)UM@oVem)%OEf@AOga!oz~9m?r1e;wX@93 zBGh0<5+EcbArX*3ARgi&1V}={fP^Ga|A>$O@#$%rq&ZC==|eNSo2KQQK73A2dQSWM z?)_$Wg@jx8*mLylZ|>{6-~FC<^mh;5)gkaSfPNRW<}M-L25$cZe(=2TDIs3FLx|PD zDB69$EySOqegAzz>;eAh(?Z;f{y(~3h|d5cj4tp&w08m@1s(+64ZH&U9B>?nt;Amd z@h5(UpRWSbK$7L52ZVSRcm#+oL>SCyo(&{EJAe-Zo4DNrr2WqVNuCklL%>NO9pxuL zh%WvXNc;X0_*vjhZr}YoLOg6F}lu2&DB(fuz4RK$t{q0urAXkmRcd694T$(rW|bejv$n0!aM& z87F|W|9e21|6?HW|9c?u$pA?Y{|+R5{f6;@FDSh{0VF#-1El#Yfu#5KK$718693JN zHjwOI1EhW41d`khK$_po#h&h4K8>0G}Al79XJNP4(K6QTn6Ft85z7Ldlp z9EJA*iO&~+FtwP&?P4A;;r2Qp?YoKFl|b6h;&u&>*K&J5koG^!?I9rTI|6(G_(R|y z0PmYC#P0!D0g0atqPh95qUja!jA4vQE8c6$#d8&OsSE|cJ6Ni(!TACC%AtINc)X&{|~wUCyam14oLE8j88BY0g3<9jL!q@PX@c3>XKfvu{K$7bM_m2aK|2vHDG5(mxf6nbG#(!q~H^yHxiup>8dx6C70U+t? z^FX+*tcd_o5BeVHdC(5fAA^FY7B590%CB2Mq#t^ggW#sw2dPCo3vxkgK+l01KrzrV z5anHpMS4gl^eh6wMdaD3zPtf?8MFaJPgH^UKI3X&IlmLU$?rOF0q8CeQnP%XVO$I> z1(krp-1iC)DiD?2-UM6+BAZbjUj>STsJ0-L%I78gZs&KTSy`v3S;KtsshrG?CRM60oqwq{i6 zW^Bo##Ik~0xoFxFU8}T>iluYs%}?5~1IZ_a)P)*7R6R({0xb#|%eHBttRNsWc*5 zs79MNpZkmSq+4m(MM=mL*Q*TZamF5GKi&t%TW-apw(cflx@%f-XQ9+#r4BVX$#B?k zoQh;DR-=Wi#=E|@Xt7KP`p{}_~?&psP%N-Ls7#bkhR({QD^)ez55Pkgd% z#-n+GC6>#asodE%+@#HMK|YyHwb(U$t=AN3oa=KjEV;|XE<>`?;2MzYvZ|0i z5#UrAn0ryq#6>g3088!vnSqcAKcd_tL*hdu9Gt^8bR4rJ97e8m#R}8WLorm7z+fwE zy#n3zt2sst$q>n#de5LQ9b6lJJc z@+aV(lVH6bHN<*VIA#%nM~sNrXu@V@g$V(dSDJAHUbxY+?Hc7G39yl6!Zx-f4LC=H zyoE|;1yzY$xy)V&Dk)>jWyk}Lfx-%Bu%FFmieiB*MvOv92O9+?_y5X+oF-yPDWvZt z47HN;mkB4oC4W@JwE8>lx_+N?NLKuC>PK+&l%qe+xZ z(tNqC!{1TZP_C#EF{xXRW!1I`=Mpa&Hk28bOTZvvXohWD++U+9)4$AhaQX%>=gOVq z>b8dTjufx1Sq|=0VUh?8ExN^U?GvwXtwez~T$ISDjJ?1^lo6rqTVyhwn-hoU%_nvX za1Yq3$IOVV$|%-PYgMSUu7Q3w%w_{Q0ktAwPpcno$)3L6?&ZX)z-nbt{Fsgo+Tw27-?gKV<+4JVA!RkGjVjOYS}f}7mcp5g3THrDiW@&PSB(mBuFIPu*$(IGE_@u024e6-Hw?^ zF)Xkn3`DC?b?O%VsLqImdp4ytsWoB9+$<1&QNx`UQGz;V1eAzcRy1Y^6jWoIKx{Gt ziqcA}TEuje*D5PwR^X$81fK^#P13eS~E_+a7Iz3$Du;H*2z1r$X{Fq4OEJ_ zQJsInIDL^x1-bhYbpkJgh%t)Z_VUMgWg0_k+46FNj_QeS@ zZIs<<>SEHS|5=U=dnU zpYqKy@L7{T85tA%D&IsrRKdRxW<+WgUK}B>%7SF2k0T*%LN{YaR2yc<5;ZuUSZ`Zo zrNY&bLR1ebW5wF~lI1U!tSZ%BD&4Sgb=kUt4MxJU-NLm_)Ql89pNu+%zqsaSiZv}8l!+OonGCE5#R#cNl8_0_U5h5Zt z2NJAZy|y$b_ESYq7T{V+@mW}o6Blc)vCUnSh~YbZF|w_0qe?B^RKBwCncO@QtHQ7g zOXJ94s9?p~Ga=J0SRX^>jTNr6>?)^Nizj5$S^CsslqDKj`sCsQeB0vVD1D(hGk(n9 zzh`Q+Idk-6>hR7?Tcf}COzLW#H+a~u@1vd_S5kw+{11t|dUd-xCHvJsAiRcFzrV-d zc|6^J6wGc;)L~ckvG4WP`^UPcM)%}z>o@J68g0#VHs73RfoR^1bLmsp{eja;D6ery zNtCK>OZQ&)y88a9w!R?yPXhj0&lNHLuYiC2Osc!tZ>sgXn^FgcbG2cXe*>htCcM!d zIJDonJ9Xj2>~%x+LVJGQfY;eQb^U}law&CnKP#=V-P?0qqu#cQQ{!i*u1}^8^f9Md zF5Xi&8oXVt-o=Jg*LbF7r{CS5y3nQNe}no1J?Y84scRFdv6jri?sVU{nzT^!x`(`@ zwf>=tSwZvcL&5i9KHT$-1?jUaxs>PsTPWXn>@Yif{I@$`zYj1Tm_oBz<7wF)W_+_} zjvn(yN3(w84Yk4vQdciQGvJUu*O9)^sWb$KP-;&1o%cH0@{Z4maoRVze`GW@be;Z7 z7L4Y9k+Y*nr&oKx4GZ$sa*GdZ`Ffy8k+Ji^PW-{H9Ec@jNGK;{@CQcy^9O`-G8L2D z@TJEI3gpIp>B$4$kz<(?^_d-g0^XA8Y+_K{)WJmN+`;rfQ|jb|NSz!@pO^lJM%JVA zeVKdZ;af8GJH3&?snJ@0;EXpkoIh`BtU<*sdvad(P^yL0Q{z{?!4^oY7SjCwqxeH4 z=eEgbiW%4#apc`-$q%TRk%T}>9l4C;#o=^vB0r+Mj$UFFq#|-D8EJS?F+~!1O;^0} zvGm*h>8|4<)mw+aWqqYvn^Jv~B2zayH9EkW>>Ehcj|BrVNeZvyc;1@kVaycDfzlJB z-mWtg=1?;dM0&?{7YL(_Ujkru~q& z>nzUTcQk5L(|XT%oja!{kERBDiijs1@FV#Ogw>SVuvdZRAGiu5BNL#8LeIDP@KaxC z-sA;u?>L*k{VY{!{&f3@^INX;2A^fZ8$adk+XD@GgV*5xRHKEerry9I=vqvT_WFHY zursvFnUH}KqKX#fQJL{W$Yq(Hi>cFNtc;G))R946;SKFgH=Ylc?>m(4t;=+bcw@a{ z>UuYHKfj{}6YIyCfB1jpsOr@G@{awvL zA2UR%f?sjE@w@6Xo%@A<@SNX$T%AUwM*7kd7lrax=!2D^+7!S_IV#-@8xx>q0}Y(?pmbSsmVeAz%>?0)hfXy3n%j$52kzi zkap;%LV%Z&YT#a^5yUSfj8C$7ub`!d|2t18$p8LbSi<=(sB(kEfAe3fPuHD)aiGk z)XCc&9}W$ig}UMYe(MMyDm}R`X9m^a&HVQ6)a8?`L8Ub1y+SqE4^#tw;T>xhUftzP z{T{y`?h(8xRrty;v%S;2Rq4`iYfDcK$>QC5#Tz&%R5v_K>QL%?V`&*r%W@LzaeiC- u)Yxf(;6Z|AAn(j|WO`aeR%j`1kUBeu(}T5{-EG3VG3d1(7T&eX;(q`nM4XiX literal 0 HcmV?d00001 diff --git a/src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po b/src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po new file mode 100644 index 0000000..a2920ce --- /dev/null +++ b/src/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po @@ -0,0 +1,668 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-09-13 16:02+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: .\accounts\admin.py:12 +msgid "password" +msgstr "密碼" + +#: .\accounts\admin.py:13 +msgid "Enter password again" +msgstr "再次輸入密碼" + +#: .\accounts\admin.py:24 .\accounts\forms.py:89 +msgid "passwords do not match" +msgstr "密碼不匹配" + +#: .\accounts\forms.py:36 +msgid "email already exists" +msgstr "郵箱已存在" + +#: .\accounts\forms.py:46 .\accounts\forms.py:50 +msgid "New password" +msgstr "新密碼" + +#: .\accounts\forms.py:60 +msgid "Confirm password" +msgstr "確認密碼" + +#: .\accounts\forms.py:70 .\accounts\forms.py:116 +msgid "Email" +msgstr "郵箱" + +#: .\accounts\forms.py:76 .\accounts\forms.py:80 +msgid "Code" +msgstr "驗證碼" + +#: .\accounts\forms.py:100 .\accounts\tests.py:194 +msgid "email does not exist" +msgstr "郵箱不存在" + +#: .\accounts\models.py:12 .\oauth\models.py:17 +msgid "nick name" +msgstr "昵稱" + +#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 +#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 +#: .\oauth\models.py:53 +msgid "creation time" +msgstr "創建時間" + +#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 +#: .\oauth\models.py:54 +msgid "last modify time" +msgstr "最後修改時間" + +#: .\accounts\models.py:15 +msgid "create source" +msgstr "來源" + +#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 +msgid "user" +msgstr "用戶" + +#: .\accounts\tests.py:216 .\accounts\utils.py:39 +msgid "Verification code error" +msgstr "驗證碼錯誤" + +#: .\accounts\utils.py:13 +msgid "Verify Email" +msgstr "驗證郵箱" + +#: .\accounts\utils.py:21 +#, python-format +msgid "" +"You are resetting the password, the verification code is:%(code)s, valid " +"within 5 minutes, please keep it properly" +msgstr "您正在重置密碼,驗證碼為:%(code)s,5分鐘內有效 請妥善保管." + +#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 +#: .\oauth\models.py:12 +msgid "author" +msgstr "作者" + +#: .\blog\admin.py:53 +msgid "Publish selected articles" +msgstr "發布選中的文章" + +#: .\blog\admin.py:54 +msgid "Draft selected articles" +msgstr "選中文章設為草稿" + +#: .\blog\admin.py:55 +msgid "Close article comments" +msgstr "關閉文章評論" + +#: .\blog\admin.py:56 +msgid "Open article comments" +msgstr "打開文章評論" + +#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 +#: .\templates\blog\tags\sidebar.html:40 +msgid "category" +msgstr "分類目錄" + +#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 +msgid "index" +msgstr "首頁" + +#: .\blog\models.py:21 +msgid "list" +msgstr "列表" + +#: .\blog\models.py:22 +msgid "post" +msgstr "文章" + +#: .\blog\models.py:23 +msgid "all" +msgstr "所有" + +#: .\blog\models.py:24 +msgid "slide" +msgstr "側邊欄" + +#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 +msgid "modify time" +msgstr "修改時間" + +#: .\blog\models.py:63 +msgid "Draft" +msgstr "草稿" + +#: .\blog\models.py:64 +msgid "Published" +msgstr "發布" + +#: .\blog\models.py:67 +msgid "Open" +msgstr "打開" + +#: .\blog\models.py:68 +msgid "Close" +msgstr "關閉" + +#: .\blog\models.py:71 .\comments\admin.py:47 +msgid "Article" +msgstr "文章" + +#: .\blog\models.py:72 +msgid "Page" +msgstr "頁面" + +#: .\blog\models.py:74 .\blog\models.py:280 +msgid "title" +msgstr "標題" + +#: .\blog\models.py:75 +msgid "body" +msgstr "內容" + +#: .\blog\models.py:77 +msgid "publish time" +msgstr "發布時間" + +#: .\blog\models.py:79 +msgid "status" +msgstr "狀態" + +#: .\blog\models.py:84 +msgid "comment status" +msgstr "評論狀態" + +#: .\blog\models.py:88 .\oauth\models.py:43 +msgid "type" +msgstr "類型" + +#: .\blog\models.py:89 +msgid "views" +msgstr "閱讀量" + +#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 +msgid "order" +msgstr "排序" + +#: .\blog\models.py:98 +msgid "show toc" +msgstr "顯示目錄" + +#: .\blog\models.py:105 .\blog\models.py:249 +msgid "tag" +msgstr "標簽" + +#: .\blog\models.py:115 .\comments\models.py:21 +msgid "article" +msgstr "文章" + +#: .\blog\models.py:171 +msgid "category name" +msgstr "分類名" + +#: .\blog\models.py:174 +msgid "parent category" +msgstr "上級分類" + +#: .\blog\models.py:234 +msgid "tag name" +msgstr "標簽名" + +#: .\blog\models.py:256 +msgid "link name" +msgstr "鏈接名" + +#: .\blog\models.py:257 .\blog\models.py:271 +msgid "link" +msgstr "鏈接" + +#: .\blog\models.py:260 +msgid "is show" +msgstr "是否顯示" + +#: .\blog\models.py:262 +msgid "show type" +msgstr "顯示類型" + +#: .\blog\models.py:281 +msgid "content" +msgstr "內容" + +#: .\blog\models.py:283 .\oauth\models.py:52 +msgid "is enable" +msgstr "是否啟用" + +#: .\blog\models.py:289 +msgid "sidebar" +msgstr "側邊欄" + +#: .\blog\models.py:299 +msgid "site name" +msgstr "站點名稱" + +#: .\blog\models.py:305 +msgid "site description" +msgstr "站點描述" + +#: .\blog\models.py:311 +msgid "site seo description" +msgstr "站點SEO描述" + +#: .\blog\models.py:313 +msgid "site keywords" +msgstr "關鍵字" + +#: .\blog\models.py:318 +msgid "article sub length" +msgstr "文章摘要長度" + +#: .\blog\models.py:319 +msgid "sidebar article count" +msgstr "側邊欄文章數目" + +#: .\blog\models.py:320 +msgid "sidebar comment count" +msgstr "側邊欄評論數目" + +#: .\blog\models.py:321 +msgid "article comment count" +msgstr "文章頁面默認顯示評論數目" + +#: .\blog\models.py:322 +msgid "show adsense" +msgstr "是否顯示廣告" + +#: .\blog\models.py:324 +msgid "adsense code" +msgstr "廣告內容" + +#: .\blog\models.py:325 +msgid "open site comment" +msgstr "公共頭部" + +#: .\blog\models.py:352 +msgid "Website configuration" +msgstr "網站配置" + +#: .\blog\models.py:360 +msgid "There can only be one configuration" +msgstr "只能有一個配置" + +#: .\blog\views.py:348 +msgid "" +"Sorry, the page you requested is not found, please click the home page to " +"see other?" +msgstr "抱歉,你所訪問的頁面找不到,請點擊首頁看看別的?" + +#: .\blog\views.py:356 +msgid "Sorry, the server is busy, please click the home page to see other?" +msgstr "抱歉,服務出錯了,請點擊首頁看看別的?" + +#: .\blog\views.py:369 +msgid "Sorry, you do not have permission to access this page?" +msgstr "抱歉,你沒用權限訪問此頁面。" + +#: .\comments\admin.py:15 +msgid "Disable comments" +msgstr "禁用評論" + +#: .\comments\admin.py:16 +msgid "Enable comments" +msgstr "啟用評論" + +#: .\comments\admin.py:46 +msgid "User" +msgstr "用戶" + +#: .\comments\models.py:25 +msgid "parent comment" +msgstr "上級評論" + +#: .\comments\models.py:29 +msgid "enable" +msgstr "啟用" + +#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 +msgid "comment" +msgstr "評論" + +#: .\comments\utils.py:13 +msgid "Thanks for your comment" +msgstr "感謝你的評論" + +#: .\comments\utils.py:15 +#, python-format +msgid "" +"

    Thank you very much for your comments on this site

    \n" +" You can visit %(article_title)s\n" +" to review your comments,\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s" +msgstr "" +"

    非常感謝您對此網站的評論

    \n" +" 您可以訪問%(article_title)s\n" +"查看您的評論,\n" +"再次感謝您!\n" +"
    \n" +" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n" +"%(article_url)s" + +#: .\comments\utils.py:26 +#, python-format +msgid "" +"Your comment on " +"%(article_title)s
    has \n" +" received a reply.
    %(comment_body)s\n" +"
    \n" +" go check it out!\n" +"
    \n" +" If the link above cannot be opened, please copy this " +"link to your browser.\n" +" %(article_url)s\n" +" " +msgstr "" +"您對 %(article_title)s
    " +"的評論有\n" +" 收到回復。
    %(comment_body)s\n" +"
    \n" +"快去看看吧!\n" +"
    \n" +" 如果上面的鏈接打不開,請復製此鏈接鏈接到您的瀏覽器。\n" +" %(article_url)s\n" +" " + +#: .\djangoblog\logentryadmin.py:63 +msgid "object" +msgstr "對象" + +#: .\djangoblog\settings.py:140 +msgid "English" +msgstr "英文" + +#: .\djangoblog\settings.py:141 +msgid "Simplified Chinese" +msgstr "簡體中文" + +#: .\djangoblog\settings.py:142 +msgid "Traditional Chinese" +msgstr "繁體中文" + +#: .\oauth\models.py:30 +msgid "oauth user" +msgstr "第三方用戶" + +#: .\oauth\models.py:37 +msgid "weibo" +msgstr "微博" + +#: .\oauth\models.py:38 +msgid "google" +msgstr "谷歌" + +#: .\oauth\models.py:48 +msgid "callback url" +msgstr "回調地址" + +#: .\oauth\models.py:59 +msgid "already exists" +msgstr "已經存在" + +#: .\oauth\views.py:154 +#, python-format +msgid "" +"\n" +"

    Congratulations, you have successfully bound your email address. You " +"can use\n" +" %(oauthuser_type)s to directly log in to this website without a " +"password.

    \n" +" You are welcome to continue to follow this site, the address is\n" +" %(site)s\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link to your " +"browser.\n" +" %(site)s\n" +" " +msgstr "" +"\n" +"

    恭喜你已經綁定成功 你可以使用\n" +" %(oauthuser_type)s 來免密登錄本站

    \n" +" 歡迎繼續關註本站, 地址是\n" +" %(site)s\n" +" 再次感謝你\n" +"
    \n" +" 如果上面鏈接無法打開,請復製此鏈接到你的瀏覽器 \n" +" %(site)s\n" +" " + +#: .\oauth\views.py:165 +msgid "Congratulations on your successful binding!" +msgstr "恭喜你綁定成功" + +#: .\oauth\views.py:217 +#, python-format +msgid "" +"\n" +"

    Please click the link below to bind your email

    \n" +"\n" +" %(url)s\n" +"\n" +" Thank you again!\n" +"
    \n" +" If the link above cannot be opened, please copy this link " +"to your browser.\n" +"
    \n" +" %(url)s\n" +" " +msgstr "" +"\n" +"

    請點擊下面的鏈接綁定您的郵箱

    \n" +"\n" +" %(url)s\n" +"\n" +"再次感謝您!\n" +"
    \n" +"如果上面的鏈接打不開,請復製此鏈接到您的瀏覽器。\n" +"%(url)s\n" +" " + +#: .\oauth\views.py:228 .\oauth\views.py:240 +msgid "Bind your email" +msgstr "綁定郵箱" + +#: .\oauth\views.py:242 +msgid "" +"Congratulations, the binding is just one step away. Please log in to your " +"email to check the email to complete the binding. Thank you." +msgstr "恭喜您,還差一步就綁定成功了,請登錄您的郵箱查看郵件完成綁定,謝謝。" + +#: .\oauth\views.py:245 +msgid "Binding successful" +msgstr "綁定成功" + +#: .\oauth\views.py:247 +#, python-format +msgid "" +"Congratulations, you have successfully bound your email address. You can use " +"%(oauthuser_type)s to directly log in to this website without a password. " +"You are welcome to continue to follow this site." +msgstr "" +"恭喜您綁定成功,您以後可以使用%(oauthuser_type)s來直接免密碼登錄本站啦,感謝" +"您對本站對關註。" + +#: .\templates\account\forget_password.html:7 +msgid "forget the password" +msgstr "忘記密碼" + +#: .\templates\account\forget_password.html:18 +msgid "get verification code" +msgstr "獲取驗證碼" + +#: .\templates\account\forget_password.html:19 +msgid "submit" +msgstr "提交" + +#: .\templates\account\login.html:36 +msgid "Create Account" +msgstr "創建賬號" + +#: .\templates\account\login.html:42 +#, fuzzy +#| msgid "forget the password" +msgid "Forget Password" +msgstr "忘記密碼" + +#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 +msgid "login" +msgstr "登錄" + +#: .\templates\account\result.html:22 +msgid "back to the homepage" +msgstr "返回首頁吧" + +#: .\templates\blog\article_archives.html:7 +#: .\templates\blog\article_archives.html:24 +msgid "article archive" +msgstr "文章歸檔" + +#: .\templates\blog\article_archives.html:32 +msgid "year" +msgstr "年" + +#: .\templates\blog\article_archives.html:36 +msgid "month" +msgstr "月" + +#: .\templates\blog\tags\article_info.html:12 +msgid "pin to top" +msgstr "置頂" + +#: .\templates\blog\tags\article_info.html:28 +msgid "comments" +msgstr "評論" + +#: .\templates\blog\tags\article_info.html:58 +msgid "toc" +msgstr "目錄" + +#: .\templates\blog\tags\article_meta_info.html:6 +msgid "posted in" +msgstr "發布於" + +#: .\templates\blog\tags\article_meta_info.html:14 +msgid "and tagged" +msgstr "並標記為" + +#: .\templates\blog\tags\article_meta_info.html:25 +msgid "by " +msgstr "由" + +#: .\templates\blog\tags\article_meta_info.html:29 +#, python-format +msgid "" +"\n" +" title=\"View all articles published by " +"%(article.author.username)s\"\n" +" " +msgstr "" +"\n" +" title=\"查看所有由 %(article.author.username)s\"發布的文章\n" +" " + +#: .\templates\blog\tags\article_meta_info.html:44 +msgid "on" +msgstr "在" + +#: .\templates\blog\tags\article_meta_info.html:54 +msgid "edit" +msgstr "編輯" + +#: .\templates\blog\tags\article_pagination.html:4 +msgid "article navigation" +msgstr "文章導航" + +#: .\templates\blog\tags\article_pagination.html:9 +msgid "earlier articles" +msgstr "早期文章" + +#: .\templates\blog\tags\article_pagination.html:12 +msgid "newer articles" +msgstr "較新文章" + +#: .\templates\blog\tags\article_tag_list.html:5 +msgid "tags" +msgstr "標簽" + +#: .\templates\blog\tags\sidebar.html:7 +msgid "search" +msgstr "搜索" + +#: .\templates\blog\tags\sidebar.html:50 +msgid "recent comments" +msgstr "近期評論" + +#: .\templates\blog\tags\sidebar.html:57 +msgid "published on" +msgstr "發表於" + +#: .\templates\blog\tags\sidebar.html:65 +msgid "recent articles" +msgstr "近期文章" + +#: .\templates\blog\tags\sidebar.html:77 +msgid "bookmark" +msgstr "書簽" + +#: .\templates\blog\tags\sidebar.html:96 +msgid "Tag Cloud" +msgstr "標簽雲" + +#: .\templates\blog\tags\sidebar.html:107 +msgid "Welcome to star or fork the source code of this site" +msgstr "歡迎您STAR或者FORK本站源代碼" + +#: .\templates\blog\tags\sidebar.html:118 +msgid "Function" +msgstr "功能" + +#: .\templates\blog\tags\sidebar.html:120 +msgid "management site" +msgstr "管理站點" + +#: .\templates\blog\tags\sidebar.html:122 +msgid "logout" +msgstr "登出" + +#: .\templates\blog\tags\sidebar.html:129 +msgid "Track record" +msgstr "運動軌跡記錄" + +#: .\templates\blog\tags\sidebar.html:135 +msgid "Click me to return to the top" +msgstr "點我返回頂部" + +#: .\templates\oauth\oauth_applications.html:5 +#| msgid "login" +msgid "quick login" +msgstr "快捷登錄" + +#: .\templates\share_layout\nav.html:26 +msgid "Article archive" +msgstr "文章歸檔" diff --git a/src/DjangoBlog-master/manage.py b/src/DjangoBlog-master/manage.py new file mode 100644 index 0000000..919ba74 --- /dev/null +++ b/src/DjangoBlog-master/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/src/DjangoBlog-master/oauth/__init__.py b/src/DjangoBlog-master/oauth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/oauth/admin.py b/src/DjangoBlog-master/oauth/admin.py new file mode 100644 index 0000000..57eab5f --- /dev/null +++ b/src/DjangoBlog-master/oauth/admin.py @@ -0,0 +1,54 @@ +import logging + +from django.contrib import admin +# Register your models here. +from django.urls import reverse +from django.utils.html import format_html + +logger = logging.getLogger(__name__) + + +class OAuthUserAdmin(admin.ModelAdmin): + search_fields = ('nickname', 'email') + list_per_page = 20 + list_display = ( + 'id', + 'nickname', + 'link_to_usermodel', + 'show_user_image', + 'type', + 'email', + ) + list_display_links = ('id', 'nickname') + list_filter = ('author', 'type',) + readonly_fields = [] + + def get_readonly_fields(self, request, obj=None): + return list(self.readonly_fields) + \ + [field.name for field in obj._meta.fields] + \ + [field.name for field in obj._meta.many_to_many] + + def has_add_permission(self, request): + return False + + def link_to_usermodel(self, obj): + if obj.author: + info = (obj.author._meta.app_label, obj.author._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + def show_user_image(self, obj): + img = obj.picture + return format_html( + u'' % + (img)) + + link_to_usermodel.short_description = '用户' + show_user_image.short_description = '用户头像' + + +class OAuthConfigAdmin(admin.ModelAdmin): + list_display = ('type', 'appkey', 'appsecret', 'is_enable') + list_filter = ('type',) diff --git a/src/DjangoBlog-master/oauth/apps.py b/src/DjangoBlog-master/oauth/apps.py new file mode 100644 index 0000000..17fcea2 --- /dev/null +++ b/src/DjangoBlog-master/oauth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OauthConfig(AppConfig): + name = 'oauth' diff --git a/src/DjangoBlog-master/oauth/forms.py b/src/DjangoBlog-master/oauth/forms.py new file mode 100644 index 0000000..0e4ede3 --- /dev/null +++ b/src/DjangoBlog-master/oauth/forms.py @@ -0,0 +1,12 @@ +from django.contrib.auth.forms import forms +from django.forms import widgets + + +class RequireEmailForm(forms.Form): + email = forms.EmailField(label='电子邮箱', required=True) + oauthid = forms.IntegerField(widget=forms.HiddenInput, required=False) + + def __init__(self, *args, **kwargs): + super(RequireEmailForm, self).__init__(*args, **kwargs) + self.fields['email'].widget = widgets.EmailInput( + attrs={'placeholder': "email", "class": "form-control"}) diff --git a/src/DjangoBlog-master/oauth/migrations/0001_initial.py b/src/DjangoBlog-master/oauth/migrations/0001_initial.py new file mode 100644 index 0000000..3aa3e03 --- /dev/null +++ b/src/DjangoBlog-master/oauth/migrations/0001_initial.py @@ -0,0 +1,57 @@ +# Generated by Django 4.1.7 on 2023-03-07 09:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='OAuthConfig', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(choices=[('weibo', '微博'), ('google', '谷歌'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='类型')), + ('appkey', models.CharField(max_length=200, verbose_name='AppKey')), + ('appsecret', models.CharField(max_length=200, verbose_name='AppSecret')), + ('callback_url', models.CharField(default='http://www.baidu.com', max_length=200, verbose_name='回调地址')), + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ], + options={ + 'verbose_name': 'oauth配置', + 'verbose_name_plural': 'oauth配置', + 'ordering': ['-created_time'], + }, + ), + migrations.CreateModel( + name='OAuthUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('openid', models.CharField(max_length=50)), + ('nickname', models.CharField(max_length=50, verbose_name='昵称')), + ('token', models.CharField(blank=True, max_length=150, null=True)), + ('picture', models.CharField(blank=True, max_length=350, null=True)), + ('type', models.CharField(max_length=50)), + ('email', models.CharField(blank=True, max_length=50, null=True)), + ('metadata', models.TextField(blank=True, null=True)), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), + ], + options={ + 'verbose_name': 'oauth用户', + 'verbose_name_plural': 'oauth用户', + 'ordering': ['-created_time'], + }, + ), + ] diff --git a/src/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/src/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py new file mode 100644 index 0000000..d5cc70e --- /dev/null +++ b/src/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py @@ -0,0 +1,86 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:13 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('oauth', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='oauthconfig', + options={'ordering': ['-creation_time'], 'verbose_name': 'oauth配置', 'verbose_name_plural': 'oauth配置'}, + ), + migrations.AlterModelOptions( + name='oauthuser', + options={'ordering': ['-creation_time'], 'verbose_name': 'oauth user', 'verbose_name_plural': 'oauth user'}, + ), + migrations.RemoveField( + model_name='oauthconfig', + name='created_time', + ), + migrations.RemoveField( + model_name='oauthconfig', + name='last_mod_time', + ), + migrations.RemoveField( + model_name='oauthuser', + name='created_time', + ), + migrations.RemoveField( + model_name='oauthuser', + name='last_mod_time', + ), + migrations.AddField( + model_name='oauthconfig', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='oauthconfig', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), + ), + migrations.AddField( + model_name='oauthuser', + name='creation_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + ), + migrations.AddField( + model_name='oauthuser', + name='last_modify_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), + ), + migrations.AlterField( + model_name='oauthconfig', + name='callback_url', + field=models.CharField(default='', max_length=200, verbose_name='callback url'), + ), + migrations.AlterField( + model_name='oauthconfig', + name='is_enable', + field=models.BooleanField(default=True, verbose_name='is enable'), + ), + migrations.AlterField( + model_name='oauthconfig', + name='type', + field=models.CharField(choices=[('weibo', 'weibo'), ('google', 'google'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='type'), + ), + migrations.AlterField( + model_name='oauthuser', + name='author', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), + ), + migrations.AlterField( + model_name='oauthuser', + name='nickname', + field=models.CharField(max_length=50, verbose_name='nickname'), + ), + ] diff --git a/src/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py new file mode 100644 index 0000000..6af08eb --- /dev/null +++ b/src/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2024-01-26 02:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oauth', '0002_alter_oauthconfig_options_alter_oauthuser_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='oauthuser', + name='nickname', + field=models.CharField(max_length=50, verbose_name='nick name'), + ), + ] diff --git a/src/DjangoBlog-master/oauth/migrations/__init__.py b/src/DjangoBlog-master/oauth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/oauth/models.py b/src/DjangoBlog-master/oauth/models.py new file mode 100644 index 0000000..be838ed --- /dev/null +++ b/src/DjangoBlog-master/oauth/models.py @@ -0,0 +1,67 @@ +# Create your models here. +from django.conf import settings +from django.core.exceptions import ValidationError +from django.db import models +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ + + +class OAuthUser(models.Model): + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + blank=True, + null=True, + on_delete=models.CASCADE) + openid = models.CharField(max_length=50) + nickname = models.CharField(max_length=50, verbose_name=_('nick name')) + token = models.CharField(max_length=150, null=True, blank=True) + picture = models.CharField(max_length=350, blank=True, null=True) + type = models.CharField(blank=False, null=False, max_length=50) + email = models.CharField(max_length=50, null=True, blank=True) + metadata = models.TextField(null=True, blank=True) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + + def __str__(self): + return self.nickname + + class Meta: + verbose_name = _('oauth user') + verbose_name_plural = verbose_name + ordering = ['-creation_time'] + + +class OAuthConfig(models.Model): + TYPE = ( + ('weibo', _('weibo')), + ('google', _('google')), + ('github', 'GitHub'), + ('facebook', 'FaceBook'), + ('qq', 'QQ'), + ) + type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a') + appkey = models.CharField(max_length=200, verbose_name='AppKey') + appsecret = models.CharField(max_length=200, verbose_name='AppSecret') + callback_url = models.CharField( + max_length=200, + verbose_name=_('callback url'), + blank=False, + default='') + is_enable = models.BooleanField( + _('is enable'), default=True, blank=False, null=False) + creation_time = models.DateTimeField(_('creation time'), default=now) + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + + def clean(self): + if OAuthConfig.objects.filter( + type=self.type).exclude(id=self.id).count(): + raise ValidationError(_(self.type + _('already exists'))) + + def __str__(self): + return self.type + + class Meta: + verbose_name = 'oauth配置' + verbose_name_plural = verbose_name + ordering = ['-creation_time'] diff --git a/src/DjangoBlog-master/oauth/oauthmanager.py b/src/DjangoBlog-master/oauth/oauthmanager.py new file mode 100644 index 0000000..2e7ceef --- /dev/null +++ b/src/DjangoBlog-master/oauth/oauthmanager.py @@ -0,0 +1,504 @@ +import json +import logging +import os +import urllib.parse +from abc import ABCMeta, abstractmethod + +import requests + +from djangoblog.utils import cache_decorator +from oauth.models import OAuthUser, OAuthConfig + +logger = logging.getLogger(__name__) + + +class OAuthAccessTokenException(Exception): + ''' + oauth授权失败异常 + ''' + + +class BaseOauthManager(metaclass=ABCMeta): + """获取用户授权""" + AUTH_URL = None + """获取token""" + TOKEN_URL = None + """获取用户信息""" + API_URL = None + '''icon图标名''' + ICON_NAME = None + + def __init__(self, access_token=None, openid=None): + self.access_token = access_token + self.openid = openid + + @property + def is_access_token_set(self): + return self.access_token is not None + + @property + def is_authorized(self): + return self.is_access_token_set and self.access_token is not None and self.openid is not None + + @abstractmethod + def get_authorization_url(self, nexturl='/'): + pass + + @abstractmethod + def get_access_token_by_code(self, code): + pass + + @abstractmethod + def get_oauth_userinfo(self): + pass + + @abstractmethod + def get_picture(self, metadata): + pass + + def do_get(self, url, params, headers=None): + rsp = requests.get(url=url, params=params, headers=headers) + logger.info(rsp.text) + return rsp.text + + def do_post(self, url, params, headers=None): + rsp = requests.post(url, params, headers=headers) + logger.info(rsp.text) + return rsp.text + + def get_config(self): + value = OAuthConfig.objects.filter(type=self.ICON_NAME) + return value[0] if value else None + + +class WBOauthManager(BaseOauthManager): + AUTH_URL = 'https://api.weibo.com/oauth2/authorize' + TOKEN_URL = 'https://api.weibo.com/oauth2/access_token' + API_URL = 'https://api.weibo.com/2/users/show.json' + ICON_NAME = 'weibo' + + def __init__(self, access_token=None, openid=None): + config = self.get_config() + self.client_id = config.appkey if config else '' + self.client_secret = config.appsecret if config else '' + self.callback_url = config.callback_url if config else '' + super( + WBOauthManager, + self).__init__( + access_token=access_token, + openid=openid) + + def get_authorization_url(self, nexturl='/'): + params = { + 'client_id': self.client_id, + 'response_type': 'code', + 'redirect_uri': self.callback_url + '&next_url=' + nexturl + } + url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) + return url + + def get_access_token_by_code(self, code): + + params = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'grant_type': 'authorization_code', + 'code': code, + 'redirect_uri': self.callback_url + } + rsp = self.do_post(self.TOKEN_URL, params) + + obj = json.loads(rsp) + if 'access_token' in obj: + self.access_token = str(obj['access_token']) + self.openid = str(obj['uid']) + return self.get_oauth_userinfo() + else: + raise OAuthAccessTokenException(rsp) + + def get_oauth_userinfo(self): + if not self.is_authorized: + return None + params = { + 'uid': self.openid, + 'access_token': self.access_token + } + rsp = self.do_get(self.API_URL, params) + try: + datas = json.loads(rsp) + user = OAuthUser() + user.metadata = rsp + user.picture = datas['avatar_large'] + user.nickname = datas['screen_name'] + user.openid = datas['id'] + user.type = 'weibo' + user.token = self.access_token + if 'email' in datas and datas['email']: + user.email = datas['email'] + return user + except Exception as e: + logger.error(e) + logger.error('weibo oauth error.rsp:' + rsp) + return None + + def get_picture(self, metadata): + datas = json.loads(metadata) + return datas['avatar_large'] + + +class ProxyManagerMixin: + def __init__(self, *args, **kwargs): + if os.environ.get("HTTP_PROXY"): + self.proxies = { + "http": os.environ.get("HTTP_PROXY"), + "https": os.environ.get("HTTP_PROXY") + } + else: + self.proxies = None + + def do_get(self, url, params, headers=None): + rsp = requests.get(url=url, params=params, headers=headers, proxies=self.proxies) + logger.info(rsp.text) + return rsp.text + + def do_post(self, url, params, headers=None): + rsp = requests.post(url, params, headers=headers, proxies=self.proxies) + logger.info(rsp.text) + return rsp.text + + +class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager): + AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth' + TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token' + API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo' + ICON_NAME = 'google' + + def __init__(self, access_token=None, openid=None): + config = self.get_config() + self.client_id = config.appkey if config else '' + self.client_secret = config.appsecret if config else '' + self.callback_url = config.callback_url if config else '' + super( + GoogleOauthManager, + self).__init__( + access_token=access_token, + openid=openid) + + def get_authorization_url(self, nexturl='/'): + params = { + 'client_id': self.client_id, + 'response_type': 'code', + 'redirect_uri': self.callback_url, + 'scope': 'openid email', + } + url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) + return url + + def get_access_token_by_code(self, code): + params = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'grant_type': 'authorization_code', + 'code': code, + + 'redirect_uri': self.callback_url + } + rsp = self.do_post(self.TOKEN_URL, params) + + obj = json.loads(rsp) + + if 'access_token' in obj: + self.access_token = str(obj['access_token']) + self.openid = str(obj['id_token']) + logger.info(self.ICON_NAME + ' oauth ' + rsp) + return self.access_token + else: + raise OAuthAccessTokenException(rsp) + + def get_oauth_userinfo(self): + if not self.is_authorized: + return None + params = { + 'access_token': self.access_token + } + rsp = self.do_get(self.API_URL, params) + try: + + datas = json.loads(rsp) + user = OAuthUser() + user.metadata = rsp + user.picture = datas['picture'] + user.nickname = datas['name'] + user.openid = datas['sub'] + user.token = self.access_token + user.type = 'google' + if datas['email']: + user.email = datas['email'] + return user + except Exception as e: + logger.error(e) + logger.error('google oauth error.rsp:' + rsp) + return None + + def get_picture(self, metadata): + datas = json.loads(metadata) + return datas['picture'] + + +class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager): + AUTH_URL = 'https://github.com/login/oauth/authorize' + TOKEN_URL = 'https://github.com/login/oauth/access_token' + API_URL = 'https://api.github.com/user' + ICON_NAME = 'github' + + def __init__(self, access_token=None, openid=None): + config = self.get_config() + self.client_id = config.appkey if config else '' + self.client_secret = config.appsecret if config else '' + self.callback_url = config.callback_url if config else '' + super( + GitHubOauthManager, + self).__init__( + access_token=access_token, + openid=openid) + + def get_authorization_url(self, next_url='/'): + params = { + 'client_id': self.client_id, + 'response_type': 'code', + 'redirect_uri': f'{self.callback_url}&next_url={next_url}', + 'scope': 'user' + } + url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) + return url + + def get_access_token_by_code(self, code): + params = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'grant_type': 'authorization_code', + 'code': code, + + 'redirect_uri': self.callback_url + } + rsp = self.do_post(self.TOKEN_URL, params) + + from urllib import parse + r = parse.parse_qs(rsp) + if 'access_token' in r: + self.access_token = (r['access_token'][0]) + return self.access_token + else: + raise OAuthAccessTokenException(rsp) + + def get_oauth_userinfo(self): + + rsp = self.do_get(self.API_URL, params={}, headers={ + "Authorization": "token " + self.access_token + }) + try: + datas = json.loads(rsp) + user = OAuthUser() + user.picture = datas['avatar_url'] + user.nickname = datas['name'] + user.openid = datas['id'] + user.type = 'github' + user.token = self.access_token + user.metadata = rsp + if 'email' in datas and datas['email']: + user.email = datas['email'] + return user + except Exception as e: + logger.error(e) + logger.error('github oauth error.rsp:' + rsp) + return None + + def get_picture(self, metadata): + datas = json.loads(metadata) + return datas['avatar_url'] + + +class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager): + AUTH_URL = 'https://www.facebook.com/v16.0/dialog/oauth' + TOKEN_URL = 'https://graph.facebook.com/v16.0/oauth/access_token' + API_URL = 'https://graph.facebook.com/me' + ICON_NAME = 'facebook' + + def __init__(self, access_token=None, openid=None): + config = self.get_config() + self.client_id = config.appkey if config else '' + self.client_secret = config.appsecret if config else '' + self.callback_url = config.callback_url if config else '' + super( + FaceBookOauthManager, + self).__init__( + access_token=access_token, + openid=openid) + + def get_authorization_url(self, next_url='/'): + params = { + 'client_id': self.client_id, + 'response_type': 'code', + 'redirect_uri': self.callback_url, + 'scope': 'email,public_profile' + } + url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) + return url + + def get_access_token_by_code(self, code): + params = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + # 'grant_type': 'authorization_code', + 'code': code, + + 'redirect_uri': self.callback_url + } + rsp = self.do_post(self.TOKEN_URL, params) + + obj = json.loads(rsp) + if 'access_token' in obj: + token = str(obj['access_token']) + self.access_token = token + return self.access_token + else: + raise OAuthAccessTokenException(rsp) + + def get_oauth_userinfo(self): + params = { + 'access_token': self.access_token, + 'fields': 'id,name,picture,email' + } + try: + rsp = self.do_get(self.API_URL, params) + datas = json.loads(rsp) + user = OAuthUser() + user.nickname = datas['name'] + user.openid = datas['id'] + user.type = 'facebook' + user.token = self.access_token + user.metadata = rsp + if 'email' in datas and datas['email']: + user.email = datas['email'] + if 'picture' in datas and datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']: + user.picture = str(datas['picture']['data']['url']) + return user + except Exception as e: + logger.error(e) + return None + + def get_picture(self, metadata): + datas = json.loads(metadata) + return str(datas['picture']['data']['url']) + + +class QQOauthManager(BaseOauthManager): + AUTH_URL = 'https://graph.qq.com/oauth2.0/authorize' + TOKEN_URL = 'https://graph.qq.com/oauth2.0/token' + API_URL = 'https://graph.qq.com/user/get_user_info' + OPEN_ID_URL = 'https://graph.qq.com/oauth2.0/me' + ICON_NAME = 'qq' + + def __init__(self, access_token=None, openid=None): + config = self.get_config() + self.client_id = config.appkey if config else '' + self.client_secret = config.appsecret if config else '' + self.callback_url = config.callback_url if config else '' + super( + QQOauthManager, + self).__init__( + access_token=access_token, + openid=openid) + + def get_authorization_url(self, next_url='/'): + params = { + 'response_type': 'code', + 'client_id': self.client_id, + 'redirect_uri': self.callback_url + '&next_url=' + next_url, + } + url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) + return url + + def get_access_token_by_code(self, code): + params = { + 'grant_type': 'authorization_code', + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'code': code, + 'redirect_uri': self.callback_url + } + rsp = self.do_get(self.TOKEN_URL, params) + if rsp: + d = urllib.parse.parse_qs(rsp) + if 'access_token' in d: + token = d['access_token'] + self.access_token = token[0] + return token + else: + raise OAuthAccessTokenException(rsp) + + def get_open_id(self): + if self.is_access_token_set: + params = { + 'access_token': self.access_token + } + rsp = self.do_get(self.OPEN_ID_URL, params) + if rsp: + rsp = rsp.replace( + 'callback(', '').replace( + ')', '').replace( + ';', '') + obj = json.loads(rsp) + openid = str(obj['openid']) + self.openid = openid + return openid + + def get_oauth_userinfo(self): + openid = self.get_open_id() + if openid: + params = { + 'access_token': self.access_token, + 'oauth_consumer_key': self.client_id, + 'openid': self.openid + } + rsp = self.do_get(self.API_URL, params) + logger.info(rsp) + obj = json.loads(rsp) + user = OAuthUser() + user.nickname = obj['nickname'] + user.openid = openid + user.type = 'qq' + user.token = self.access_token + user.metadata = rsp + if 'email' in obj: + user.email = obj['email'] + if 'figureurl' in obj: + user.picture = str(obj['figureurl']) + return user + + def get_picture(self, metadata): + datas = json.loads(metadata) + return str(datas['figureurl']) + + +@cache_decorator(expiration=100 * 60) +def get_oauth_apps(): + configs = OAuthConfig.objects.filter(is_enable=True).all() + if not configs: + return [] + configtypes = [x.type for x in configs] + applications = BaseOauthManager.__subclasses__() + apps = [x() for x in applications if x().ICON_NAME.lower() in configtypes] + return apps + + +def get_manager_by_type(type): + applications = get_oauth_apps() + if applications: + finds = list( + filter( + lambda x: x.ICON_NAME.lower() == type.lower(), + applications)) + if finds: + return finds[0] + return None diff --git a/src/DjangoBlog-master/oauth/templatetags/__init__.py b/src/DjangoBlog-master/oauth/templatetags/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/DjangoBlog-master/oauth/templatetags/__init__.py @@ -0,0 +1 @@ + diff --git a/src/DjangoBlog-master/oauth/templatetags/oauth_tags.py b/src/DjangoBlog-master/oauth/templatetags/oauth_tags.py new file mode 100644 index 0000000..7b687d5 --- /dev/null +++ b/src/DjangoBlog-master/oauth/templatetags/oauth_tags.py @@ -0,0 +1,22 @@ +from django import template +from django.urls import reverse + +from oauth.oauthmanager import get_oauth_apps + +register = template.Library() + + +@register.inclusion_tag('oauth/oauth_applications.html') +def load_oauth_applications(request): + applications = get_oauth_apps() + if applications: + baseurl = reverse('oauth:oauthlogin') + path = request.get_full_path() + + apps = list(map(lambda x: (x.ICON_NAME, '{baseurl}?type={type}&next_url={next}'.format( + baseurl=baseurl, type=x.ICON_NAME, next=path)), applications)) + else: + apps = [] + return { + 'apps': apps + } diff --git a/src/DjangoBlog-master/oauth/tests.py b/src/DjangoBlog-master/oauth/tests.py new file mode 100644 index 0000000..bb23b9b --- /dev/null +++ b/src/DjangoBlog-master/oauth/tests.py @@ -0,0 +1,249 @@ +import json +from unittest.mock import patch + +from django.conf import settings +from django.contrib import auth +from django.test import Client, RequestFactory, TestCase +from django.urls import reverse + +from djangoblog.utils import get_sha256 +from oauth.models import OAuthConfig +from oauth.oauthmanager import BaseOauthManager + + +# Create your tests here. +class OAuthConfigTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + + def test_oauth_login_test(self): + c = OAuthConfig() + c.type = 'weibo' + c.appkey = 'appkey' + c.appsecret = 'appsecret' + c.save() + + response = self.client.get('/oauth/oauthlogin?type=weibo') + self.assertEqual(response.status_code, 302) + self.assertTrue("api.weibo.com" in response.url) + + response = self.client.get('/oauth/authorize?type=weibo&code=code') + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, '/') + + +class OauthLoginTest(TestCase): + def setUp(self) -> None: + self.client = Client() + self.factory = RequestFactory() + self.apps = self.init_apps() + + def init_apps(self): + applications = [p() for p in BaseOauthManager.__subclasses__()] + for application in applications: + c = OAuthConfig() + c.type = application.ICON_NAME.lower() + c.appkey = 'appkey' + c.appsecret = 'appsecret' + c.save() + return applications + + def get_app_by_type(self, type): + for app in self.apps: + if app.ICON_NAME.lower() == type: + return app + + @patch("oauth.oauthmanager.WBOauthManager.do_post") + @patch("oauth.oauthmanager.WBOauthManager.do_get") + def test_weibo_login(self, mock_do_get, mock_do_post): + weibo_app = self.get_app_by_type('weibo') + assert weibo_app + url = weibo_app.get_authorization_url() + mock_do_post.return_value = json.dumps({"access_token": "access_token", + "uid": "uid" + }) + mock_do_get.return_value = json.dumps({ + "avatar_large": "avatar_large", + "screen_name": "screen_name", + "id": "id", + "email": "email", + }) + userinfo = weibo_app.get_access_token_by_code('code') + self.assertEqual(userinfo.token, 'access_token') + self.assertEqual(userinfo.openid, 'id') + + @patch("oauth.oauthmanager.GoogleOauthManager.do_post") + @patch("oauth.oauthmanager.GoogleOauthManager.do_get") + def test_google_login(self, mock_do_get, mock_do_post): + google_app = self.get_app_by_type('google') + assert google_app + url = google_app.get_authorization_url() + mock_do_post.return_value = json.dumps({ + "access_token": "access_token", + "id_token": "id_token", + }) + mock_do_get.return_value = json.dumps({ + "picture": "picture", + "name": "name", + "sub": "sub", + "email": "email", + }) + token = google_app.get_access_token_by_code('code') + userinfo = google_app.get_oauth_userinfo() + self.assertEqual(userinfo.token, 'access_token') + self.assertEqual(userinfo.openid, 'sub') + + @patch("oauth.oauthmanager.GitHubOauthManager.do_post") + @patch("oauth.oauthmanager.GitHubOauthManager.do_get") + def test_github_login(self, mock_do_get, mock_do_post): + github_app = self.get_app_by_type('github') + assert github_app + url = github_app.get_authorization_url() + self.assertTrue("github.com" in url) + self.assertTrue("client_id" in url) + mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer" + mock_do_get.return_value = json.dumps({ + "avatar_url": "avatar_url", + "name": "name", + "id": "id", + "email": "email", + }) + token = github_app.get_access_token_by_code('code') + userinfo = github_app.get_oauth_userinfo() + self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a') + self.assertEqual(userinfo.openid, 'id') + + @patch("oauth.oauthmanager.FaceBookOauthManager.do_post") + @patch("oauth.oauthmanager.FaceBookOauthManager.do_get") + def test_facebook_login(self, mock_do_get, mock_do_post): + facebook_app = self.get_app_by_type('facebook') + assert facebook_app + url = facebook_app.get_authorization_url() + self.assertTrue("facebook.com" in url) + mock_do_post.return_value = json.dumps({ + "access_token": "access_token", + }) + mock_do_get.return_value = json.dumps({ + "name": "name", + "id": "id", + "email": "email", + "picture": { + "data": { + "url": "url" + } + } + }) + token = facebook_app.get_access_token_by_code('code') + userinfo = facebook_app.get_oauth_userinfo() + self.assertEqual(userinfo.token, 'access_token') + + @patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[ + 'access_token=access_token&expires_in=3600', + 'callback({"client_id":"appid","openid":"openid"} );', + json.dumps({ + "nickname": "nickname", + "email": "email", + "figureurl": "figureurl", + "openid": "openid", + }) + ]) + def test_qq_login(self, mock_do_get): + qq_app = self.get_app_by_type('qq') + assert qq_app + url = qq_app.get_authorization_url() + self.assertTrue("qq.com" in url) + token = qq_app.get_access_token_by_code('code') + userinfo = qq_app.get_oauth_userinfo() + self.assertEqual(userinfo.token, 'access_token') + + @patch("oauth.oauthmanager.WBOauthManager.do_post") + @patch("oauth.oauthmanager.WBOauthManager.do_get") + def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post): + + mock_do_post.return_value = json.dumps({"access_token": "access_token", + "uid": "uid" + }) + mock_user_info = { + "avatar_large": "avatar_large", + "screen_name": "screen_name1", + "id": "id", + "email": "email", + } + mock_do_get.return_value = json.dumps(mock_user_info) + + response = self.client.get('/oauth/oauthlogin?type=weibo') + self.assertEqual(response.status_code, 302) + self.assertTrue("api.weibo.com" in response.url) + + response = self.client.get('/oauth/authorize?type=weibo&code=code') + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, '/') + + user = auth.get_user(self.client) + assert user.is_authenticated + self.assertTrue(user.is_authenticated) + self.assertEqual(user.username, mock_user_info['screen_name']) + self.assertEqual(user.email, mock_user_info['email']) + self.client.logout() + + response = self.client.get('/oauth/authorize?type=weibo&code=code') + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, '/') + + user = auth.get_user(self.client) + assert user.is_authenticated + self.assertTrue(user.is_authenticated) + self.assertEqual(user.username, mock_user_info['screen_name']) + self.assertEqual(user.email, mock_user_info['email']) + + @patch("oauth.oauthmanager.WBOauthManager.do_post") + @patch("oauth.oauthmanager.WBOauthManager.do_get") + def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post): + + mock_do_post.return_value = json.dumps({"access_token": "access_token", + "uid": "uid" + }) + mock_user_info = { + "avatar_large": "avatar_large", + "screen_name": "screen_name1", + "id": "id", + } + mock_do_get.return_value = json.dumps(mock_user_info) + + response = self.client.get('/oauth/oauthlogin?type=weibo') + self.assertEqual(response.status_code, 302) + self.assertTrue("api.weibo.com" in response.url) + + response = self.client.get('/oauth/authorize?type=weibo&code=code') + + self.assertEqual(response.status_code, 302) + + oauth_user_id = int(response.url.split('/')[-1].split('.')[0]) + self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html') + + response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id}) + + self.assertEqual(response.status_code, 302) + sign = get_sha256(settings.SECRET_KEY + + str(oauth_user_id) + settings.SECRET_KEY) + + url = reverse('oauth:bindsuccess', kwargs={ + 'oauthid': oauth_user_id, + }) + self.assertEqual(response.url, f'{url}?type=email') + + path = reverse('oauth:email_confirm', kwargs={ + 'id': oauth_user_id, + 'sign': sign + }) + response = self.client.get(path) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success') + user = auth.get_user(self.client) + from oauth.models import OAuthUser + oauth_user = OAuthUser.objects.get(author=user) + self.assertTrue(user.is_authenticated) + self.assertEqual(user.username, mock_user_info['screen_name']) + self.assertEqual(user.email, 'test@gmail.com') + self.assertEqual(oauth_user.pk, oauth_user_id) diff --git a/src/DjangoBlog-master/oauth/urls.py b/src/DjangoBlog-master/oauth/urls.py new file mode 100644 index 0000000..c4a12a0 --- /dev/null +++ b/src/DjangoBlog-master/oauth/urls.py @@ -0,0 +1,25 @@ +from django.urls import path + +from . import views + +app_name = "oauth" +urlpatterns = [ + path( + r'oauth/authorize', + views.authorize), + path( + r'oauth/requireemail/.html', + views.RequireEmailView.as_view(), + name='require_email'), + path( + r'oauth/emailconfirm//.html', + views.emailconfirm, + name='email_confirm'), + path( + r'oauth/bindsuccess/.html', + views.bindsuccess, + name='bindsuccess'), + path( + r'oauth/oauthlogin', + views.oauthlogin, + name='oauthlogin')] diff --git a/src/DjangoBlog-master/oauth/views.py b/src/DjangoBlog-master/oauth/views.py new file mode 100644 index 0000000..12e3a6e --- /dev/null +++ b/src/DjangoBlog-master/oauth/views.py @@ -0,0 +1,253 @@ +import logging +# Create your views here. +from urllib.parse import urlparse + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth import login +from django.core.exceptions import ObjectDoesNotExist +from django.db import transaction +from django.http import HttpResponseForbidden +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.shortcuts import render +from django.urls import reverse +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from django.views.generic import FormView + +from djangoblog.blog_signals import oauth_user_login_signal +from djangoblog.utils import get_current_site +from djangoblog.utils import send_email, get_sha256 +from oauth.forms import RequireEmailForm +from .models import OAuthUser +from .oauthmanager import get_manager_by_type, OAuthAccessTokenException + +logger = logging.getLogger(__name__) + + +def get_redirecturl(request): + nexturl = request.GET.get('next_url', None) + if not nexturl or nexturl == '/login/' or nexturl == '/login': + nexturl = '/' + return nexturl + p = urlparse(nexturl) + if p.netloc: + site = get_current_site().domain + if not p.netloc.replace('www.', '') == site.replace('www.', ''): + logger.info('非法url:' + nexturl) + return "/" + return nexturl + + +def oauthlogin(request): + type = request.GET.get('type', None) + if not type: + return HttpResponseRedirect('/') + manager = get_manager_by_type(type) + if not manager: + return HttpResponseRedirect('/') + nexturl = get_redirecturl(request) + authorizeurl = manager.get_authorization_url(nexturl) + return HttpResponseRedirect(authorizeurl) + + +def authorize(request): + type = request.GET.get('type', None) + if not type: + return HttpResponseRedirect('/') + manager = get_manager_by_type(type) + if not manager: + return HttpResponseRedirect('/') + code = request.GET.get('code', None) + try: + rsp = manager.get_access_token_by_code(code) + except OAuthAccessTokenException as e: + logger.warning("OAuthAccessTokenException:" + str(e)) + return HttpResponseRedirect('/') + except Exception as e: + logger.error(e) + rsp = None + nexturl = get_redirecturl(request) + if not rsp: + return HttpResponseRedirect(manager.get_authorization_url(nexturl)) + user = manager.get_oauth_userinfo() + if user: + if not user.nickname or not user.nickname.strip(): + user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') + try: + temp = OAuthUser.objects.get(type=type, openid=user.openid) + temp.picture = user.picture + temp.metadata = user.metadata + temp.nickname = user.nickname + user = temp + except ObjectDoesNotExist: + pass + # facebook的token过长 + if type == 'facebook': + user.token = '' + if user.email: + with transaction.atomic(): + author = None + try: + author = get_user_model().objects.get(id=user.author_id) + except ObjectDoesNotExist: + pass + if not author: + result = get_user_model().objects.get_or_create(email=user.email) + author = result[0] + if result[1]: + try: + get_user_model().objects.get(username=user.nickname) + except ObjectDoesNotExist: + author.username = user.nickname + else: + author.username = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') + author.source = 'authorize' + author.save() + + user.author = author + user.save() + + oauth_user_login_signal.send( + sender=authorize.__class__, id=user.id) + login(request, author) + return HttpResponseRedirect(nexturl) + else: + user.save() + url = reverse('oauth:require_email', kwargs={ + 'oauthid': user.id + }) + + return HttpResponseRedirect(url) + else: + return HttpResponseRedirect(nexturl) + + +def emailconfirm(request, id, sign): + if not sign: + return HttpResponseForbidden() + if not get_sha256(settings.SECRET_KEY + + str(id) + + settings.SECRET_KEY).upper() == sign.upper(): + return HttpResponseForbidden() + oauthuser = get_object_or_404(OAuthUser, pk=id) + with transaction.atomic(): + if oauthuser.author: + author = get_user_model().objects.get(pk=oauthuser.author_id) + else: + result = get_user_model().objects.get_or_create(email=oauthuser.email) + author = result[0] + if result[1]: + author.source = 'emailconfirm' + author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip( + ) else "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') + author.save() + oauthuser.author = author + oauthuser.save() + oauth_user_login_signal.send( + sender=emailconfirm.__class__, + id=oauthuser.id) + login(request, author) + + site = 'http://' + get_current_site().domain + content = _(''' +

    Congratulations, you have successfully bound your email address. You can use + %(oauthuser_type)s to directly log in to this website without a password.

    + You are welcome to continue to follow this site, the address is + %(site)s + Thank you again! +
    + If the link above cannot be opened, please copy this link to your browser. + %(site)s + ''') % {'oauthuser_type': oauthuser.type, 'site': site} + + send_email(emailto=[oauthuser.email, ], title=_('Congratulations on your successful binding!'), content=content) + url = reverse('oauth:bindsuccess', kwargs={ + 'oauthid': id + }) + url = url + '?type=success' + return HttpResponseRedirect(url) + + +class RequireEmailView(FormView): + form_class = RequireEmailForm + template_name = 'oauth/require_email.html' + + def get(self, request, *args, **kwargs): + oauthid = self.kwargs['oauthid'] + oauthuser = get_object_or_404(OAuthUser, pk=oauthid) + if oauthuser.email: + pass + # return HttpResponseRedirect('/') + + return super(RequireEmailView, self).get(request, *args, **kwargs) + + def get_initial(self): + oauthid = self.kwargs['oauthid'] + return { + 'email': '', + 'oauthid': oauthid + } + + def get_context_data(self, **kwargs): + oauthid = self.kwargs['oauthid'] + oauthuser = get_object_or_404(OAuthUser, pk=oauthid) + if oauthuser.picture: + kwargs['picture'] = oauthuser.picture + return super(RequireEmailView, self).get_context_data(**kwargs) + + def form_valid(self, form): + email = form.cleaned_data['email'] + oauthid = form.cleaned_data['oauthid'] + oauthuser = get_object_or_404(OAuthUser, pk=oauthid) + oauthuser.email = email + oauthuser.save() + sign = get_sha256(settings.SECRET_KEY + + str(oauthuser.id) + settings.SECRET_KEY) + site = get_current_site().domain + if settings.DEBUG: + site = '127.0.0.1:8000' + path = reverse('oauth:email_confirm', kwargs={ + 'id': oauthid, + 'sign': sign + }) + url = "http://{site}{path}".format(site=site, path=path) + + content = _(""" +

    Please click the link below to bind your email

    + + %(url)s + + Thank you again! +
    + If the link above cannot be opened, please copy this link to your browser. +
    + %(url)s + """) % {'url': url} + send_email(emailto=[email, ], title=_('Bind your email'), content=content) + url = reverse('oauth:bindsuccess', kwargs={ + 'oauthid': oauthid + }) + url = url + '?type=email' + return HttpResponseRedirect(url) + + +def bindsuccess(request, oauthid): + type = request.GET.get('type', None) + oauthuser = get_object_or_404(OAuthUser, pk=oauthid) + if type == 'email': + title = _('Bind your email') + content = _( + 'Congratulations, the binding is just one step away. ' + 'Please log in to your email to check the email to complete the binding. Thank you.') + else: + title = _('Binding successful') + content = _( + "Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s" + " to directly log in to this website without a password. You are welcome to continue to follow this site." % { + 'oauthuser_type': oauthuser.type}) + return render(request, 'oauth/bindsuccess.html', { + 'title': title, + 'content': content + }) diff --git a/src/DjangoBlog-master/owntracks/__init__.py b/src/DjangoBlog-master/owntracks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/owntracks/admin.py b/src/DjangoBlog-master/owntracks/admin.py new file mode 100644 index 0000000..655b535 --- /dev/null +++ b/src/DjangoBlog-master/owntracks/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +# Register your models here. + + +class OwnTrackLogsAdmin(admin.ModelAdmin): + pass diff --git a/src/DjangoBlog-master/owntracks/apps.py b/src/DjangoBlog-master/owntracks/apps.py new file mode 100644 index 0000000..1bc5f12 --- /dev/null +++ b/src/DjangoBlog-master/owntracks/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OwntracksConfig(AppConfig): + name = 'owntracks' diff --git a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py new file mode 100644 index 0000000..9eee55c --- /dev/null +++ b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='OwnTrackLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tid', models.CharField(max_length=100, verbose_name='用户')), + ('lat', models.FloatField(verbose_name='纬度')), + ('lon', models.FloatField(verbose_name='经度')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + ], + options={ + 'verbose_name': 'OwnTrackLogs', + 'verbose_name_plural': 'OwnTrackLogs', + 'ordering': ['created_time'], + 'get_latest_by': 'created_time', + }, + ), + ] diff --git a/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py new file mode 100644 index 0000000..b4f8dec --- /dev/null +++ b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('owntracks', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='owntracklog', + options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'}, + ), + migrations.RenameField( + model_name='owntracklog', + old_name='created_time', + new_name='creation_time', + ), + ] diff --git a/src/DjangoBlog-master/owntracks/migrations/__init__.py b/src/DjangoBlog-master/owntracks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/owntracks/models.py b/src/DjangoBlog-master/owntracks/models.py new file mode 100644 index 0000000..760942c --- /dev/null +++ b/src/DjangoBlog-master/owntracks/models.py @@ -0,0 +1,20 @@ +from django.db import models +from django.utils.timezone import now + + +# Create your models here. + +class OwnTrackLog(models.Model): + tid = models.CharField(max_length=100, null=False, verbose_name='用户') + lat = models.FloatField(verbose_name='纬度') + lon = models.FloatField(verbose_name='经度') + creation_time = models.DateTimeField('创建时间', default=now) + + def __str__(self): + return self.tid + + class Meta: + ordering = ['creation_time'] + verbose_name = "OwnTrackLogs" + verbose_name_plural = verbose_name + get_latest_by = 'creation_time' diff --git a/src/DjangoBlog-master/owntracks/tests.py b/src/DjangoBlog-master/owntracks/tests.py new file mode 100644 index 0000000..3b4b9d8 --- /dev/null +++ b/src/DjangoBlog-master/owntracks/tests.py @@ -0,0 +1,64 @@ +import json + +from django.test import Client, RequestFactory, TestCase + +from accounts.models import BlogUser +from .models import OwnTrackLog + + +# Create your tests here. + +class OwnTrackLogTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + + def test_own_track_log(self): + o = { + 'tid': 12, + 'lat': 123.123, + 'lon': 134.341 + } + + self.client.post( + '/owntracks/logtracks', + json.dumps(o), + content_type='application/json') + length = len(OwnTrackLog.objects.all()) + self.assertEqual(length, 1) + + o = { + 'tid': 12, + 'lat': 123.123 + } + + self.client.post( + '/owntracks/logtracks', + json.dumps(o), + content_type='application/json') + length = len(OwnTrackLog.objects.all()) + self.assertEqual(length, 1) + + rsp = self.client.get('/owntracks/show_maps') + self.assertEqual(rsp.status_code, 302) + + user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="liangliangyy1") + + self.client.login(username='liangliangyy1', password='liangliangyy1') + s = OwnTrackLog() + s.tid = 12 + s.lon = 123.234 + s.lat = 34.234 + s.save() + + rsp = self.client.get('/owntracks/show_dates') + self.assertEqual(rsp.status_code, 200) + rsp = self.client.get('/owntracks/show_maps') + self.assertEqual(rsp.status_code, 200) + rsp = self.client.get('/owntracks/get_datas') + self.assertEqual(rsp.status_code, 200) + rsp = self.client.get('/owntracks/get_datas?date=2018-02-26') + self.assertEqual(rsp.status_code, 200) diff --git a/src/DjangoBlog-master/owntracks/urls.py b/src/DjangoBlog-master/owntracks/urls.py new file mode 100644 index 0000000..c19ada8 --- /dev/null +++ b/src/DjangoBlog-master/owntracks/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from . import views + +app_name = "owntracks" + +urlpatterns = [ + path('owntracks/logtracks', views.manage_owntrack_log, name='logtracks'), + path('owntracks/show_maps', views.show_maps, name='show_maps'), + path('owntracks/get_datas', views.get_datas, name='get_datas'), + path('owntracks/show_dates', views.show_log_dates, name='show_dates') +] diff --git a/src/DjangoBlog-master/owntracks/views.py b/src/DjangoBlog-master/owntracks/views.py new file mode 100644 index 0000000..4c72bdd --- /dev/null +++ b/src/DjangoBlog-master/owntracks/views.py @@ -0,0 +1,127 @@ +# Create your views here. +import datetime +import itertools +import json +import logging +from datetime import timezone +from itertools import groupby + +import django +import requests +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse +from django.http import JsonResponse +from django.shortcuts import render +from django.views.decorators.csrf import csrf_exempt + +from .models import OwnTrackLog + +logger = logging.getLogger(__name__) + + +@csrf_exempt +def manage_owntrack_log(request): + try: + s = json.loads(request.read().decode('utf-8')) + tid = s['tid'] + lat = s['lat'] + lon = s['lon'] + + logger.info( + 'tid:{tid}.lat:{lat}.lon:{lon}'.format( + tid=tid, lat=lat, lon=lon)) + if tid and lat and lon: + m = OwnTrackLog() + m.tid = tid + m.lat = lat + m.lon = lon + m.save() + return HttpResponse('ok') + else: + return HttpResponse('data error') + except Exception as e: + logger.error(e) + return HttpResponse('error') + + +@login_required +def show_maps(request): + if request.user.is_superuser: + defaultdate = str(datetime.datetime.now(timezone.utc).date()) + date = request.GET.get('date', defaultdate) + context = { + 'date': date + } + return render(request, 'owntracks/show_maps.html', context) + else: + from django.http import HttpResponseForbidden + return HttpResponseForbidden() + + +@login_required +def show_log_dates(request): + dates = OwnTrackLog.objects.values_list('creation_time', flat=True) + results = list(sorted(set(map(lambda x: x.strftime('%Y-%m-%d'), dates)))) + + context = { + 'results': results + } + return render(request, 'owntracks/show_log_dates.html', context) + + +def convert_to_amap(locations): + convert_result = [] + it = iter(locations) + + item = list(itertools.islice(it, 30)) + while item: + datas = ';'.join( + set(map(lambda x: str(x.lon) + ',' + str(x.lat), item))) + + key = '8440a376dfc9743d8924bf0ad141f28e' + api = 'http://restapi.amap.com/v3/assistant/coordinate/convert' + query = { + 'key': key, + 'locations': datas, + 'coordsys': 'gps' + } + rsp = requests.get(url=api, params=query) + result = json.loads(rsp.text) + if "locations" in result: + convert_result.append(result['locations']) + item = list(itertools.islice(it, 30)) + + return ";".join(convert_result) + + +@login_required +def get_datas(request): + now = django.utils.timezone.now().replace(tzinfo=timezone.utc) + querydate = django.utils.timezone.datetime( + now.year, now.month, now.day, 0, 0, 0) + if request.GET.get('date', None): + date = list(map(lambda x: int(x), request.GET.get('date').split('-'))) + querydate = django.utils.timezone.datetime( + date[0], date[1], date[2], 0, 0, 0) + nextdate = querydate + datetime.timedelta(days=1) + models = OwnTrackLog.objects.filter( + creation_time__range=(querydate, nextdate)) + result = list() + if models and len(models): + for tid, item in groupby( + sorted(models, key=lambda k: k.tid), key=lambda k: k.tid): + + d = dict() + d["name"] = tid + paths = list() + # 使用高德转换后的经纬度 + # locations = convert_to_amap( + # sorted(item, key=lambda x: x.creation_time)) + # for i in locations.split(';'): + # paths.append(i.split(',')) + # 使用GPS原始经纬度 + for location in sorted(item, key=lambda x: x.creation_time): + paths.append([str(location.lon), str(location.lat)]) + d["path"] = paths + result.append(d) + return JsonResponse(result, safe=False) diff --git a/src/DjangoBlog-master/plugins/__init__.py b/src/DjangoBlog-master/plugins/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/DjangoBlog-master/plugins/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/DjangoBlog-master/plugins/article_copyright/__init__.py b/src/DjangoBlog-master/plugins/article_copyright/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/DjangoBlog-master/plugins/article_copyright/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/DjangoBlog-master/plugins/article_copyright/plugin.py b/src/DjangoBlog-master/plugins/article_copyright/plugin.py new file mode 100644 index 0000000..317fed2 --- /dev/null +++ b/src/DjangoBlog-master/plugins/article_copyright/plugin.py @@ -0,0 +1,32 @@ +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + + +class ArticleCopyrightPlugin(BasePlugin): + PLUGIN_NAME = '文章结尾版权声明' + PLUGIN_DESCRIPTION = '一个在文章正文末尾添加版权声明的插件。' + PLUGIN_VERSION = '0.2.0' + PLUGIN_AUTHOR = 'liangliangyy' + + # 2. 实现 register_hooks 方法,专门用于注册钩子 + def register_hooks(self): + # 在这里将插件的方法注册到指定的钩子上 + hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_copyright_to_content) + + def add_copyright_to_content(self, content, *args, **kwargs): + """ + 这个方法会被注册到 'the_content' 过滤器钩子上。 + 它接收原始内容,并返回添加了版权信息的新内容。 + """ + article = kwargs.get('article') + if not article: + return content + + copyright_info = f"\n

    本文由 {article.author.username} 原创,转载请注明出处。

    " + return content + copyright_info + + +# 3. 实例化插件。 +# 这会自动调用 BasePlugin.__init__,然后 BasePlugin.__init__ 会调用我们上面定义的 register_hooks 方法。 +plugin = ArticleCopyrightPlugin() diff --git a/src/DjangoBlog-master/plugins/external_links/__init__.py b/src/DjangoBlog-master/plugins/external_links/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/DjangoBlog-master/plugins/external_links/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/DjangoBlog-master/plugins/external_links/plugin.py b/src/DjangoBlog-master/plugins/external_links/plugin.py new file mode 100644 index 0000000..5b2ef14 --- /dev/null +++ b/src/DjangoBlog-master/plugins/external_links/plugin.py @@ -0,0 +1,48 @@ +import re +from urllib.parse import urlparse +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + + +class ExternalLinksPlugin(BasePlugin): + PLUGIN_NAME = '外部链接处理器' + PLUGIN_DESCRIPTION = '自动为文章中的外部链接添加 target="_blank" 和 rel="noopener noreferrer" 属性。' + PLUGIN_VERSION = '0.1.0' + PLUGIN_AUTHOR = 'liangliangyy' + + def register_hooks(self): + hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.process_external_links) + + def process_external_links(self, content, *args, **kwargs): + from djangoblog.utils import get_current_site + site_domain = get_current_site().domain + + # 正则表达式查找所有 标签 + link_pattern = re.compile(r'(]*?\s+)?href=")([^"]*)(".*?/a>)', re.IGNORECASE) + + def replacer(match): + # match.group(1) 是 ... + href = match.group(2) + + # 如果链接已经有 target 属性,则不处理 + if 'target=' in match.group(0).lower(): + return match.group(0) + + # 解析链接 + parsed_url = urlparse(href) + + # 如果链接是外部的 (有域名且域名不等于当前网站域名) + if parsed_url.netloc and parsed_url.netloc != site_domain: + # 添加 target 和 rel 属性 + return f'{match.group(1)}{href}" target="_blank" rel="noopener noreferrer"{match.group(3)}' + + # 否则返回原样 + return match.group(0) + + return link_pattern.sub(replacer, content) + + +plugin = ExternalLinksPlugin() diff --git a/src/DjangoBlog-master/plugins/reading_time/__init__.py b/src/DjangoBlog-master/plugins/reading_time/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/DjangoBlog-master/plugins/reading_time/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/DjangoBlog-master/plugins/reading_time/plugin.py b/src/DjangoBlog-master/plugins/reading_time/plugin.py new file mode 100644 index 0000000..35f9db1 --- /dev/null +++ b/src/DjangoBlog-master/plugins/reading_time/plugin.py @@ -0,0 +1,43 @@ +import math +import re +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + + +class ReadingTimePlugin(BasePlugin): + PLUGIN_NAME = '阅读时间预测' + PLUGIN_DESCRIPTION = '估算文章阅读时间并显示在文章开头。' + PLUGIN_VERSION = '0.1.0' + PLUGIN_AUTHOR = 'liangliangyy' + + def register_hooks(self): + hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_reading_time) + + def add_reading_time(self, content, *args, **kwargs): + """ + 计算阅读时间并添加到内容开头。 + """ + # 移除HTML标签和空白字符,以获得纯文本 + clean_content = re.sub(r'<[^>]*>', '', content) + clean_content = clean_content.strip() + + # 中文和英文单词混合计数的一个简单方法 + # 匹配中文字符或连续的非中文字符(视为单词) + words = re.findall(r'[\u4e00-\u9fa5]|\w+', clean_content) + word_count = len(words) + + # 按平均每分钟200字的速度计算 + reading_speed = 200 + reading_minutes = math.ceil(word_count / reading_speed) + + # 如果阅读时间少于1分钟,则显示为1分钟 + if reading_minutes < 1: + reading_minutes = 1 + + reading_time_html = f'

    预计阅读时间:{reading_minutes} 分钟

    ' + + return reading_time_html + content + + +plugin = ReadingTimePlugin() \ No newline at end of file diff --git a/src/DjangoBlog-master/plugins/seo_optimizer/__init__.py b/src/DjangoBlog-master/plugins/seo_optimizer/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/DjangoBlog-master/plugins/seo_optimizer/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/DjangoBlog-master/plugins/seo_optimizer/plugin.py b/src/DjangoBlog-master/plugins/seo_optimizer/plugin.py new file mode 100644 index 0000000..b5b19a3 --- /dev/null +++ b/src/DjangoBlog-master/plugins/seo_optimizer/plugin.py @@ -0,0 +1,142 @@ +import json +from django.utils.html import strip_tags +from django.template.defaultfilters import truncatewords +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from blog.models import Article, Category, Tag +from djangoblog.utils import get_blog_setting + + +class SeoOptimizerPlugin(BasePlugin): + PLUGIN_NAME = 'SEO 优化器' + PLUGIN_DESCRIPTION = '为文章、页面等提供 SEO 优化,动态生成 meta 标签和 JSON-LD 结构化数据。' + PLUGIN_VERSION = '0.2.0' + PLUGIN_AUTHOR = 'liuangliangyy' + + def register_hooks(self): + hooks.register('head_meta', self.dispatch_seo_generation) + + def _get_article_seo_data(self, context, request, blog_setting): + article = context.get('article') + if not isinstance(article, Article): + return None + + description = strip_tags(article.body)[:150] + keywords = ",".join([tag.name for tag in article.tags.all()]) or blog_setting.site_keywords + + meta_tags = f''' + + + + + + + + + ''' + for tag in article.tags.all(): + meta_tags += f'' + meta_tags += f'' + + structured_data = { + "@context": "https://schema.org", + "@type": "Article", + "mainEntityOfPage": {"@type": "WebPage", "@id": request.build_absolute_uri()}, + "headline": article.title, + "description": description, + "image": request.build_absolute_uri(article.get_first_image_url()), + "datePublished": article.pub_time.isoformat(), + "dateModified": article.last_modify_time.isoformat(), + "author": {"@type": "Person", "name": article.author.username}, + "publisher": {"@type": "Organization", "name": blog_setting.site_name} + } + if not structured_data.get("image"): + del structured_data["image"] + + return { + "title": f"{article.title} | {blog_setting.site_name}", + "description": description, + "keywords": keywords, + "meta_tags": meta_tags, + "json_ld": structured_data + } + + def _get_category_seo_data(self, context, request, blog_setting): + category_name = context.get('tag_name') + if not category_name: + return None + + category = Category.objects.filter(name=category_name).first() + if not category: + return None + + title = f"{category.name} | {blog_setting.site_name}" + description = strip_tags(category.name) or blog_setting.site_description + keywords = category.name + + # BreadcrumbList structured data for category page + breadcrumb_items = [{"@type": "ListItem", "position": 1, "name": "首页", "item": request.build_absolute_uri('/')}] + breadcrumb_items.append({"@type": "ListItem", "position": 2, "name": category.name, "item": request.build_absolute_uri()}) + + structured_data = { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + "itemListElement": breadcrumb_items + } + + return { + "title": title, + "description": description, + "keywords": keywords, + "meta_tags": "", + "json_ld": structured_data + } + + def _get_default_seo_data(self, context, request, blog_setting): + # Homepage and other default pages + structured_data = { + "@context": "https://schema.org", + "@type": "WebSite", + "url": request.build_absolute_uri('/'), + "potentialAction": { + "@type": "SearchAction", + "target": f"{request.build_absolute_uri('/search/')}?q={{search_term_string}}", + "query-input": "required name=search_term_string" + } + } + return { + "title": f"{blog_setting.site_name} | {blog_setting.site_description}", + "description": blog_setting.site_description, + "keywords": blog_setting.site_keywords, + "meta_tags": "", + "json_ld": structured_data + } + + def dispatch_seo_generation(self, metas, context): + request = context.get('request') + if not request: + return metas + + view_name = request.resolver_match.view_name + blog_setting = get_blog_setting() + + seo_data = None + if view_name == 'blog:detailbyid': + seo_data = self._get_article_seo_data(context, request, blog_setting) + elif view_name == 'blog:category_detail': + seo_data = self._get_category_seo_data(context, request, blog_setting) + + if not seo_data: + seo_data = self._get_default_seo_data(context, request, blog_setting) + + json_ld_script = f'' + + return f""" + {seo_data.get("title", "")} + + + {seo_data.get("meta_tags", "")} + {json_ld_script} + """ + +plugin = SeoOptimizerPlugin() diff --git a/src/DjangoBlog-master/plugins/view_count/__init__.py b/src/DjangoBlog-master/plugins/view_count/__init__.py new file mode 100644 index 0000000..8804fdf --- /dev/null +++ b/src/DjangoBlog-master/plugins/view_count/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package \ No newline at end of file diff --git a/src/DjangoBlog-master/plugins/view_count/plugin.py b/src/DjangoBlog-master/plugins/view_count/plugin.py new file mode 100644 index 0000000..15e9d94 --- /dev/null +++ b/src/DjangoBlog-master/plugins/view_count/plugin.py @@ -0,0 +1,18 @@ +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks + + +class ViewCountPlugin(BasePlugin): + PLUGIN_NAME = '文章浏览次数统计' + PLUGIN_DESCRIPTION = '统计文章的浏览次数' + PLUGIN_VERSION = '0.1.0' + PLUGIN_AUTHOR = 'liangliangyy' + + def register_hooks(self): + hooks.register('after_article_body_get', self.record_view) + + def record_view(self, article, *args, **kwargs): + article.viewed() + + +plugin = ViewCountPlugin() \ No newline at end of file diff --git a/src/DjangoBlog-master/requirements.txt b/src/DjangoBlog-master/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..9dc5c935191f166db408850beb747475a262f65f GIT binary patch literal 2554 zcmZ{mOK)0H41~|RQhy3jxa83;x~aOVRHt-$MBCMc>m$SHk)3NsCx724O z&svob%XMU|V{iQC@?R zZ~0G!W+z_|B@^!EYb0|a8|zFJJ(1fNe%^|{En8uk>3b5kw*sb!x=F1+XweA^2Zpom&^VXcC#kB7{YUtyk1AW8lRCsH7kc&9|2FJZDW6fPxwcK<4UuCUBdsXLVs`_5F zp~0oR=sQS>?<%PjW*zBU@cbYY3-!J}L^}_3Y27N;PkG)pk*uJsc&?t#R3o*J(X1SA zka;NaQGO#GYHJxbBbSkJp-k9@@&PlNs$w3EcPR@sF|TltTB6Tpl&WX?P!AFM`gAq# ziDP5!b%x?N`7!_PG~o%JQRKX9Y6p=$>EBXOM*7a}6;HOi-71ev8|^_li^@^eSj@w__0A3aLAaD3lW$cY-+ejf+0D0n;c*g?m`=Y9 z#D{NVKaQOf&bHqN|K6p!G41^vmi8TR=TIe^a2}m??@)Ypmc4|+%#Zt&bBpa!Y_Lna z95ZK`d!>na86B&$`)xt#Oe-&k^ISIS%h_8C0ec(O{{Ln{w6Q!`KVv#@hd&K=)9UJoM~W6el-zM7!{x**T=1X z^wa1z+L2b5chAc%ZKb?k2fY03=7b3E0}!J+D^ z`Om~yc~RJ~yuIGH-QLq%`|ZTuPLXcJX{%xE{f6^yk|#Rdx2k=` SS`X?#O)JeKo3nk{t@?jbHFwMa literal 0 HcmV?d00001 diff --git a/src/DjangoBlog-master/servermanager/MemcacheStorage.py b/src/DjangoBlog-master/servermanager/MemcacheStorage.py new file mode 100644 index 0000000..38a7990 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/MemcacheStorage.py @@ -0,0 +1,32 @@ +from werobot.session import SessionStorage +from werobot.utils import json_loads, json_dumps + +from djangoblog.utils import cache + + +class MemcacheStorage(SessionStorage): + def __init__(self, prefix='ws_'): + self.prefix = prefix + self.cache = cache + + @property + def is_available(self): + value = "1" + self.set('checkavaliable', value=value) + return value == self.get('checkavaliable') + + def key_name(self, s): + return '{prefix}{s}'.format(prefix=self.prefix, s=s) + + def get(self, id): + id = self.key_name(id) + session_json = self.cache.get(id) or '{}' + return json_loads(session_json) + + def set(self, id, value): + id = self.key_name(id) + self.cache.set(id, json_dumps(value)) + + def delete(self, id): + id = self.key_name(id) + self.cache.delete(id) diff --git a/src/DjangoBlog-master/servermanager/__init__.py b/src/DjangoBlog-master/servermanager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/servermanager/admin.py b/src/DjangoBlog-master/servermanager/admin.py new file mode 100644 index 0000000..f26f4f6 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +# Register your models here. + + +class CommandsAdmin(admin.ModelAdmin): + list_display = ('title', 'command', 'describe') + + +class EmailSendLogAdmin(admin.ModelAdmin): + list_display = ('title', 'emailto', 'send_result', 'creation_time') + readonly_fields = ( + 'title', + 'emailto', + 'send_result', + 'creation_time', + 'content') + + def has_add_permission(self, request): + return False diff --git a/src/DjangoBlog-master/servermanager/api/__init__.py b/src/DjangoBlog-master/servermanager/api/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/api/__init__.py @@ -0,0 +1 @@ + diff --git a/src/DjangoBlog-master/servermanager/api/blogapi.py b/src/DjangoBlog-master/servermanager/api/blogapi.py new file mode 100644 index 0000000..8a4d6ac --- /dev/null +++ b/src/DjangoBlog-master/servermanager/api/blogapi.py @@ -0,0 +1,27 @@ +from haystack.query import SearchQuerySet + +from blog.models import Article, Category + + +class BlogApi: + def __init__(self): + self.searchqueryset = SearchQuerySet() + self.searchqueryset.auto_query('') + self.__max_takecount__ = 8 + + def search_articles(self, query): + sqs = self.searchqueryset.auto_query(query) + sqs = sqs.load_all() + return sqs[:self.__max_takecount__] + + def get_category_lists(self): + return Category.objects.all() + + def get_category_articles(self, categoryname): + articles = Article.objects.filter(category__name=categoryname) + if articles: + return articles[:self.__max_takecount__] + return None + + def get_recent_articles(self): + return Article.objects.all()[:self.__max_takecount__] diff --git a/src/DjangoBlog-master/servermanager/api/commonapi.py b/src/DjangoBlog-master/servermanager/api/commonapi.py new file mode 100644 index 0000000..83ad9ff --- /dev/null +++ b/src/DjangoBlog-master/servermanager/api/commonapi.py @@ -0,0 +1,64 @@ +import logging +import os + +import openai + +from servermanager.models import commands + +logger = logging.getLogger(__name__) + +openai.api_key = os.environ.get('OPENAI_API_KEY') +if os.environ.get('HTTP_PROXY'): + openai.proxy = os.environ.get('HTTP_PROXY') + + +class ChatGPT: + + @staticmethod + def chat(prompt): + try: + completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", + messages=[{"role": "user", "content": prompt}]) + return completion.choices[0].message.content + except Exception as e: + logger.error(e) + return "服务器出错了" + + +class CommandHandler: + def __init__(self): + self.commands = commands.objects.all() + + def run(self, title): + """ + 运行命令 + :param title: 命令 + :return: 返回命令执行结果 + """ + cmd = list( + filter( + lambda x: x.title.upper() == title.upper(), + self.commands)) + if cmd: + return self.__run_command__(cmd[0].command) + else: + return "未找到相关命令,请输入hepme获得帮助。" + + def __run_command__(self, cmd): + try: + res = os.popen(cmd).read() + return res + except BaseException: + return '命令执行出错!' + + def get_help(self): + rsp = '' + for cmd in self.commands: + rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe) + return rsp + + +if __name__ == '__main__': + chatbot = ChatGPT() + prompt = "写一篇1000字关于AI的论文" + print(chatbot.chat(prompt)) diff --git a/src/DjangoBlog-master/servermanager/apps.py b/src/DjangoBlog-master/servermanager/apps.py new file mode 100644 index 0000000..03cc38d --- /dev/null +++ b/src/DjangoBlog-master/servermanager/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ServermanagerConfig(AppConfig): + name = 'servermanager' diff --git a/src/DjangoBlog-master/servermanager/migrations/0001_initial.py b/src/DjangoBlog-master/servermanager/migrations/0001_initial.py new file mode 100644 index 0000000..bbdbf77 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='commands', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=300, verbose_name='命令标题')), + ('command', models.CharField(max_length=2000, verbose_name='命令')), + ('describe', models.CharField(max_length=300, verbose_name='命令描述')), + ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('last_mod_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ], + options={ + 'verbose_name': '命令', + 'verbose_name_plural': '命令', + }, + ), + migrations.CreateModel( + name='EmailSendLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('emailto', models.CharField(max_length=300, verbose_name='收件人')), + ('title', models.CharField(max_length=2000, verbose_name='邮件标题')), + ('content', models.TextField(verbose_name='邮件内容')), + ('send_result', models.BooleanField(default=False, verbose_name='结果')), + ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ], + options={ + 'verbose_name': '邮件发送log', + 'verbose_name_plural': '邮件发送log', + 'ordering': ['-created_time'], + }, + ), + ] diff --git a/src/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py new file mode 100644 index 0000000..4858857 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('servermanager', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='emailsendlog', + options={'ordering': ['-creation_time'], 'verbose_name': '邮件发送log', 'verbose_name_plural': '邮件发送log'}, + ), + migrations.RenameField( + model_name='commands', + old_name='created_time', + new_name='creation_time', + ), + migrations.RenameField( + model_name='commands', + old_name='last_mod_time', + new_name='last_modify_time', + ), + migrations.RenameField( + model_name='emailsendlog', + old_name='created_time', + new_name='creation_time', + ), + ] diff --git a/src/DjangoBlog-master/servermanager/migrations/__init__.py b/src/DjangoBlog-master/servermanager/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/servermanager/models.py b/src/DjangoBlog-master/servermanager/models.py new file mode 100644 index 0000000..4326c65 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/models.py @@ -0,0 +1,33 @@ +from django.db import models + + +# Create your models here. +class commands(models.Model): + title = models.CharField('命令标题', max_length=300) + command = models.CharField('命令', max_length=2000) + describe = models.CharField('命令描述', max_length=300) + creation_time = models.DateTimeField('创建时间', auto_now_add=True) + last_modify_time = models.DateTimeField('修改时间', auto_now=True) + + def __str__(self): + return self.title + + class Meta: + verbose_name = '命令' + verbose_name_plural = verbose_name + + +class EmailSendLog(models.Model): + emailto = models.CharField('收件人', max_length=300) + title = models.CharField('邮件标题', max_length=2000) + content = models.TextField('邮件内容') + send_result = models.BooleanField('结果', default=False) + creation_time = models.DateTimeField('创建时间', auto_now_add=True) + + def __str__(self): + return self.title + + class Meta: + verbose_name = '邮件发送log' + verbose_name_plural = verbose_name + ordering = ['-creation_time'] diff --git a/src/DjangoBlog-master/servermanager/robot.py b/src/DjangoBlog-master/servermanager/robot.py new file mode 100644 index 0000000..7b45736 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/robot.py @@ -0,0 +1,187 @@ +import logging +import os +import re + +import jsonpickle +from django.conf import settings +from werobot import WeRoBot +from werobot.replies import ArticlesReply, Article +from werobot.session.filestorage import FileStorage + +from djangoblog.utils import get_sha256 +from servermanager.api.blogapi import BlogApi +from servermanager.api.commonapi import ChatGPT, CommandHandler +from .MemcacheStorage import MemcacheStorage + +robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') + or 'lylinux', enable_session=True) +memstorage = MemcacheStorage() +if memstorage.is_available: + robot.config['SESSION_STORAGE'] = memstorage +else: + if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')): + os.remove(os.path.join(settings.BASE_DIR, 'werobot_session')) + robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') + +blogapi = BlogApi() +cmd_handler = CommandHandler() +logger = logging.getLogger(__name__) + + +def convert_to_article_reply(articles, message): + reply = ArticlesReply(message=message) + from blog.templatetags.blog_tags import truncatechars_content + for post in articles: + imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body) + imgurl = '' + if imgs: + imgurl = imgs[0] + article = Article( + title=post.title, + description=truncatechars_content(post.body), + img=imgurl, + url=post.get_full_url() + ) + reply.add_article(article) + return reply + + +@robot.filter(re.compile(r"^\?.*")) +def search(message, session): + s = message.content + searchstr = str(s).replace('?', '') + result = blogapi.search_articles(searchstr) + if result: + articles = list(map(lambda x: x.object, result)) + reply = convert_to_article_reply(articles, message) + return reply + else: + return '没有找到相关文章。' + + +@robot.filter(re.compile(r'^category\s*$', re.I)) +def category(message, session): + categorys = blogapi.get_category_lists() + content = ','.join(map(lambda x: x.name, categorys)) + return '所有文章分类目录:' + content + + +@robot.filter(re.compile(r'^recent\s*$', re.I)) +def recents(message, session): + articles = blogapi.get_recent_articles() + if articles: + reply = convert_to_article_reply(articles, message) + return reply + else: + return "暂时还没有文章" + + +@robot.filter(re.compile('^help$', re.I)) +def help(message, session): + return '''欢迎关注! + 默认会与图灵机器人聊天~~ + 你可以通过下面这些命令来获得信息 + ?关键字搜索文章. + 如?python. + category获得文章分类目录及文章数. + category-***获得该分类目录文章 + 如category-python + recent获得最新文章 + help获得帮助. + weather:获得天气 + 如weather:西安 + idcard:获得身份证信息 + 如idcard:61048119xxxxxxxxxx + music:音乐搜索 + 如music:阴天快乐 + PS:以上标点符号都不支持中文标点~~ + ''' + + +@robot.filter(re.compile(r'^weather\:.*$', re.I)) +def weather(message, session): + return "建设中..." + + +@robot.filter(re.compile(r'^idcard\:.*$', re.I)) +def idcard(message, session): + return "建设中..." + + +@robot.handler +def echo(message, session): + handler = MessageHandler(message, session) + return handler.handler() + + +class MessageHandler: + def __init__(self, message, session): + userid = message.source + self.message = message + self.session = session + self.userid = userid + try: + info = session[userid] + self.userinfo = jsonpickle.decode(info) + except Exception as e: + userinfo = WxUserInfo() + self.userinfo = userinfo + + @property + def is_admin(self): + return self.userinfo.isAdmin + + @property + def is_password_set(self): + return self.userinfo.isPasswordSet + + def save_session(self): + info = jsonpickle.encode(self.userinfo) + self.session[self.userid] = info + + def handler(self): + info = self.message.content + + if self.userinfo.isAdmin and info.upper() == 'EXIT': + self.userinfo = WxUserInfo() + self.save_session() + return "退出成功" + if info.upper() == 'ADMIN': + self.userinfo.isAdmin = True + self.save_session() + return "输入管理员密码" + if self.userinfo.isAdmin and not self.userinfo.isPasswordSet: + passwd = settings.WXADMIN + if settings.TESTING: + passwd = '123' + if passwd.upper() == get_sha256(get_sha256(info)).upper(): + self.userinfo.isPasswordSet = True + self.save_session() + return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助" + else: + if self.userinfo.Count >= 3: + self.userinfo = WxUserInfo() + self.save_session() + return "超过验证次数" + self.userinfo.Count += 1 + self.save_session() + return "验证失败,请重新输入管理员密码:" + if self.userinfo.isAdmin and self.userinfo.isPasswordSet: + if self.userinfo.Command != '' and info.upper() == 'Y': + return cmd_handler.run(self.userinfo.Command) + else: + if info.upper() == 'HELPME': + return cmd_handler.get_help() + self.userinfo.Command = info + self.save_session() + return "确认执行: " + info + " 命令?" + + return ChatGPT.chat(info) + + +class WxUserInfo(): + def __init__(self): + self.isAdmin = False + self.isPasswordSet = False + self.Count = 0 + self.Command = '' diff --git a/src/DjangoBlog-master/servermanager/tests.py b/src/DjangoBlog-master/servermanager/tests.py new file mode 100644 index 0000000..22a6689 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/tests.py @@ -0,0 +1,79 @@ +from django.test import Client, RequestFactory, TestCase +from django.utils import timezone +from werobot.messages.messages import TextMessage + +from accounts.models import BlogUser +from blog.models import Category, Article +from servermanager.api.commonapi import ChatGPT +from .models import commands +from .robot import MessageHandler, CommandHandler +from .robot import search, category, recents + + +# Create your tests here. +class ServerManagerTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + + def test_chat_gpt(self): + content = ChatGPT.chat("你好") + self.assertIsNotNone(content) + + def test_validate_comment(self): + user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="liangliangyy1") + + self.client.login(username='liangliangyy1', password='liangliangyy1') + + c = Category() + c.name = "categoryccc" + c.save() + + article = Article() + article.title = "nicetitleccc" + article.body = "nicecontentccc" + article.author = user + article.category = c + article.type = 'a' + article.status = 'p' + article.save() + s = TextMessage([]) + s.content = "nice" + rsp = search(s, None) + rsp = category(None, None) + self.assertIsNotNone(rsp) + rsp = recents(None, None) + self.assertTrue(rsp != '暂时还没有文章') + + cmd = commands() + cmd.title = "test" + cmd.command = "ls" + cmd.describe = "test" + cmd.save() + + cmdhandler = CommandHandler() + rsp = cmdhandler.run('test') + self.assertIsNotNone(rsp) + s.source = 'u' + s.content = 'test' + msghandler = MessageHandler(s, {}) + + # msghandler.userinfo.isPasswordSet = True + # msghandler.userinfo.isAdmin = True + msghandler.handler() + s.content = 'y' + msghandler.handler() + s.content = 'idcard:12321233' + msghandler.handler() + s.content = 'weather:上海' + msghandler.handler() + s.content = 'admin' + msghandler.handler() + s.content = '123' + msghandler.handler() + + s.content = 'exit' + msghandler.handler() diff --git a/src/DjangoBlog-master/servermanager/urls.py b/src/DjangoBlog-master/servermanager/urls.py new file mode 100644 index 0000000..8d134d2 --- /dev/null +++ b/src/DjangoBlog-master/servermanager/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from werobot.contrib.django import make_view + +from .robot import robot + +app_name = "servermanager" +urlpatterns = [ + path(r'robot', make_view(robot)), + +] diff --git a/src/DjangoBlog-master/servermanager/views.py b/src/DjangoBlog-master/servermanager/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/src/DjangoBlog-master/servermanager/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/src/DjangoBlog-master/templates/account/forget_password.html b/src/DjangoBlog-master/templates/account/forget_password.html new file mode 100644 index 0000000..3384531 --- /dev/null +++ b/src/DjangoBlog-master/templates/account/forget_password.html @@ -0,0 +1,30 @@ +{% extends 'share_layout/base_account.html' %} +{% load i18n %} +{% load static %} +{% block content %} +
    + + + + + +

    + Home Page + | + login page +

    + +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/account/login.html b/src/DjangoBlog-master/templates/account/login.html new file mode 100644 index 0000000..cff8d33 --- /dev/null +++ b/src/DjangoBlog-master/templates/account/login.html @@ -0,0 +1,46 @@ +{% extends 'share_layout/base_account.html' %} +{% load static %} +{% load i18n %} +{% block content %} +
    + + + + + +

    + + {% trans 'Create Account' %} + + | + Home Page + | + + {% trans 'Forget Password' %} + +

    + +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/account/registration_form.html b/src/DjangoBlog-master/templates/account/registration_form.html new file mode 100644 index 0000000..65e7549 --- /dev/null +++ b/src/DjangoBlog-master/templates/account/registration_form.html @@ -0,0 +1,29 @@ +{% extends 'share_layout/base_account.html' %} +{% load static %} +{% block content %} +
    + + + + + +

    + Sign In +

    + +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/account/result.html b/src/DjangoBlog-master/templates/account/result.html new file mode 100644 index 0000000..23c9094 --- /dev/null +++ b/src/DjangoBlog-master/templates/account/result.html @@ -0,0 +1,27 @@ +{% extends 'share_layout/base.html' %} +{% load i18n %} +{% block header %} + {{ title }} +{% endblock %} +{% block content %} +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/blog/article_archives.html b/src/DjangoBlog-master/templates/blog/article_archives.html new file mode 100644 index 0000000..959319e --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/article_archives.html @@ -0,0 +1,60 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} +{% load cache %} +{% load i18n %} +{% block header %} + + {% trans 'article archive' %} | {{ SITE_DESCRIPTION }} + + + + + + + + + +{% endblock %} +{% block content %} +
    +
    + +
    + +

    {% trans 'article archive' %}

    +
    + +
    + + {% regroup article_list by pub_time.year as year_post_group %} +
      + {% for year in year_post_group %} +
    • {{ year.grouper }} {% trans 'year' %} + {% regroup year.list by pub_time.month as month_post_group %} +
        + {% for month in month_post_group %} +
      • {{ month.grouper }} {% trans 'month' %} + +
      • + {% endfor %} +
      +
    • + {% endfor %} +
    +
    +
    +
    + +{% endblock %} + + +{% block sidebar %} + {% load_sidebar user 'i' %} +{% endblock %} + + diff --git a/src/DjangoBlog-master/templates/blog/article_detail.html b/src/DjangoBlog-master/templates/blog/article_detail.html new file mode 100644 index 0000000..a74a0db --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/article_detail.html @@ -0,0 +1,52 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} + +{% block header %} +{% endblock %} +{% block content %} +
    +
    + {% load_article_detail article False user %} + + {% if article.type == 'a' %} + + {% endif %} + +
    + {% if article.comment_status == "o" and OPEN_SITE_COMMENT %} + + + {% include 'comments/tags/comment_list.html' %} + {% if user.is_authenticated %} + {% include 'comments/tags/post_comment.html' %} + {% else %} +
    +

    您还没有登录,请您登录后发表评论。 +

    + + {% load oauth_tags %} + {% load_oauth_applications request %} + +
    + {% endif %} + {% endif %} +
    + +{% endblock %} + +{% block sidebar %} + {% load_sidebar user "p" %} +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/blog/article_index.html b/src/DjangoBlog-master/templates/blog/article_index.html new file mode 100644 index 0000000..0ee6150 --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/article_index.html @@ -0,0 +1,42 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} +{% load cache %} +{% block header %} + {% if tag_name %} + {{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }} + {% comment %}{% endcomment %} + {% else %} + {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} + {% endif %} + + + + + + + +{% endblock %} +{% block content %} +
    +
    + {% if page_type and tag_name %} +
    + +

    {{ page_type }}:{{ tag_name }}

    +
    + {% endif %} + + {% for article in article_list %} + {% load_article_detail article True user %} + {% endfor %} + {% if is_paginated %} + {% load_pagination_info page_obj page_type tag_name %} + + {% endif %} +
    +
    + +{% endblock %} +{% block sidebar %} + {% load_sidebar user linktype %} +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/blog/error_page.html b/src/DjangoBlog-master/templates/blog/error_page.html new file mode 100644 index 0000000..d41cfb6 --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/error_page.html @@ -0,0 +1,45 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} +{% load cache %} +{% block header %} + {% if tag_name %} + {% if statuscode == '404' %} + 404 NotFound + {% elif statuscode == '403' %} + Permission Denied + {% elif statuscode == '500' %} + 500 Error + {% else %} + + {% endif %} + {% comment %}{% endcomment %} + {% else %} + {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} + {% endif %} + + + + + + + +{% endblock %} +{% block content %} +
    +
    + +
    +

    {{ message }}

    +
    + +
    +
    + +{% endblock %} + + +{% block sidebar %} + {% load_sidebar user 'i' %} +{% endblock %} + + diff --git a/src/DjangoBlog-master/templates/blog/links_list.html b/src/DjangoBlog-master/templates/blog/links_list.html new file mode 100644 index 0000000..ccecbea --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/links_list.html @@ -0,0 +1,44 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} +{% load cache %} +{% block header %} + + 友情链接 | {{ SITE_DESCRIPTION }} + + + + + + + + + +{% endblock %} +{% block content %} +
    +
    + +
    + +

    友情链接

    +
    + +
    + +
    +
    +
    + +{% endblock %} + + +{% block sidebar %} + {% load_sidebar user 'i' %} +{% endblock %} + + diff --git a/src/DjangoBlog-master/templates/blog/tags/article_info.html b/src/DjangoBlog-master/templates/blog/tags/article_info.html new file mode 100644 index 0000000..3deec44 --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/article_info.html @@ -0,0 +1,74 @@ +{% load blog_tags %} +{% load cache %} +{% load i18n %} +
    +
    + +

    + {% if isindex %} + {% if article.article_order > 0 %} + 【{% trans 'pin to top' %}】{{ article.title }} + {% else %} + {{ article.title }} + {% endif %} + + {% else %} + {{ article.title }} + {% endif %} +

    + +
    + {% if article.type == 'a' %} + {% if not isindex %} + {% cache 36000 breadcrumb article.pk %} + {% load_breadcrumb article %} + {% endcache %} + {% endif %} + {% endif %} +
    + +
    + {% if isindex %} + {{ article.body|custom_markdown|escape|truncatechars_content }} +

    Read more

    + {% else %} + + {% if article.show_toc %} + {% get_markdown_toc article.body as toc %} + {% trans 'toc' %}: + {{ toc|safe }} + +
    + {% endif %} +
    + + {{ article.body|custom_markdown|escape }} + +
    + {% endif %} + +
    + + {% load_article_metas article user %} + +
    \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/blog/tags/article_meta_info.html b/src/DjangoBlog-master/templates/blog/tags/article_meta_info.html new file mode 100644 index 0000000..cb6111c --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/article_meta_info.html @@ -0,0 +1,59 @@ +{% load i18n %} +{% load blog_tags %} + + + + + diff --git a/src/DjangoBlog-master/templates/blog/tags/article_pagination.html b/src/DjangoBlog-master/templates/blog/tags/article_pagination.html new file mode 100644 index 0000000..95514ff --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/article_pagination.html @@ -0,0 +1,17 @@ +{% load i18n %} + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/blog/tags/article_tag_list.html b/src/DjangoBlog-master/templates/blog/tags/article_tag_list.html new file mode 100644 index 0000000..c8ba474 --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/article_tag_list.html @@ -0,0 +1,19 @@ +{% load i18n %} +{% if article_tags_list %} +
    +
    + {% trans 'tags' %} +
    +
    + + {% for url,count,tag,color in article_tags_list %} + + {{ tag.name }} + {{ count }} + + {% endfor %} + +
    +
    +{% endif %} diff --git a/src/DjangoBlog-master/templates/blog/tags/breadcrumb.html b/src/DjangoBlog-master/templates/blog/tags/breadcrumb.html new file mode 100644 index 0000000..67087d5 --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/breadcrumb.html @@ -0,0 +1,19 @@ + + diff --git a/src/DjangoBlog-master/templates/blog/tags/sidebar.html b/src/DjangoBlog-master/templates/blog/tags/sidebar.html new file mode 100644 index 0000000..f70544c --- /dev/null +++ b/src/DjangoBlog-master/templates/blog/tags/sidebar.html @@ -0,0 +1,136 @@ +{% load blog_tags %} +{% load i18n %} + diff --git a/src/DjangoBlog-master/templates/comments/tags/comment_item.html b/src/DjangoBlog-master/templates/comments/tags/comment_item.html new file mode 100644 index 0000000..ebb0388 --- /dev/null +++ b/src/DjangoBlog-master/templates/comments/tags/comment_item.html @@ -0,0 +1,34 @@ +{% load blog_tags %} +
  • +
    + + + +

    {{ comment_item.body|escape|comment_markdown }}

    + +
    + +
  • \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/comments/tags/comment_item_tree.html b/src/DjangoBlog-master/templates/comments/tags/comment_item_tree.html new file mode 100644 index 0000000..a9decd1 --- /dev/null +++ b/src/DjangoBlog-master/templates/comments/tags/comment_item_tree.html @@ -0,0 +1,54 @@ +{% load blog_tags %} +
  • +
    + + + +

    + {% if comment_item.parent_comment %} +

    + {% endif %} +

    + +

    {{ comment_item.body|escape|comment_markdown }}

    + + +
    + +
  • +{% query article_comments parent_comment=comment_item as cc_comments %} +{% for cc in cc_comments %} + {% with comment_item=cc template_name="comments/tags/comment_item_tree.html" %} + {% if depth >= 1 %} + {% include template_name %} + {% else %} + {% with depth=depth|add:1 %} + {% include template_name %} + {% endwith %} + {% endif %} + {% endwith %} +{% endfor %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/comments/tags/comment_list.html b/src/DjangoBlog-master/templates/comments/tags/comment_list.html new file mode 100644 index 0000000..4092161 --- /dev/null +++ b/src/DjangoBlog-master/templates/comments/tags/comment_list.html @@ -0,0 +1,45 @@ + +
    + {% load blog_tags %} + {% load comments_tags %} + {% load cache %} + + + {% if article_comments %} +
    +
      + {# {% query article_comments parent_comment=None as parent_comments %}#} + {% for comment_item in p_comments %} + + {% with 0 as depth %} + {% include "comments/tags/comment_item_tree.html" %} + {% endwith %} + {% endfor %} + +
    + +
    +
    + {% endif %} +
    + +
    \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/comments/tags/post_comment.html b/src/DjangoBlog-master/templates/comments/tags/post_comment.html new file mode 100644 index 0000000..3ae5a27 --- /dev/null +++ b/src/DjangoBlog-master/templates/comments/tags/post_comment.html @@ -0,0 +1,33 @@ +
    + +
    +

    发表评论 + +

    +
    {% csrf_token %} +

    + {{ form.body.label_tag }} + + {{ form.body }} + {{ form.body.errors }} +

    + {{ form.parent_comment_id }} +
    + {% if COMMENT_NEED_REVIEW %} + 支持markdown,评论经审核后才会显示。 + {% else %} + 支持markdown。 + {% endif %} + + +
    +
    +
    + +
    + + diff --git a/src/DjangoBlog-master/templates/oauth/bindsuccess.html b/src/DjangoBlog-master/templates/oauth/bindsuccess.html new file mode 100644 index 0000000..4bee77c --- /dev/null +++ b/src/DjangoBlog-master/templates/oauth/bindsuccess.html @@ -0,0 +1,22 @@ +{% extends 'share_layout/base.html' %} +{% block header %} + {{ title }} +{% endblock %} +{% block content %} +
    +
    + +
    + +

    {{ content }}

    +
    +
    +
    + + 登录 + | + 回到首页 +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/oauth/oauth_applications.html b/src/DjangoBlog-master/templates/oauth/oauth_applications.html new file mode 100644 index 0000000..a841ad2 --- /dev/null +++ b/src/DjangoBlog-master/templates/oauth/oauth_applications.html @@ -0,0 +1,13 @@ +{% load i18n %} + diff --git a/src/DjangoBlog-master/templates/oauth/require_email.html b/src/DjangoBlog-master/templates/oauth/require_email.html new file mode 100644 index 0000000..3adef12 --- /dev/null +++ b/src/DjangoBlog-master/templates/oauth/require_email.html @@ -0,0 +1,46 @@ +{% extends 'share_layout/base_account.html' %} + +{% load static %} +{% block content %} +
    + + + + + +

    + 登录 +

    + +
    +{% endblock %} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/owntracks/show_log_dates.html b/src/DjangoBlog-master/templates/owntracks/show_log_dates.html new file mode 100644 index 0000000..7dbba21 --- /dev/null +++ b/src/DjangoBlog-master/templates/owntracks/show_log_dates.html @@ -0,0 +1,17 @@ + + + + + 记录日期 + + + +
      + {% for date in results %} +
    • + {{ date }} +
    • + {% endfor %} +
    + + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/owntracks/show_maps.html b/src/DjangoBlog-master/templates/owntracks/show_maps.html new file mode 100644 index 0000000..3aeda36 --- /dev/null +++ b/src/DjangoBlog-master/templates/owntracks/show_maps.html @@ -0,0 +1,135 @@ + + + + + + + 运动轨迹 + + + +
    + + + + + + + + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/search/indexes/blog/article_text.txt b/src/DjangoBlog-master/templates/search/indexes/blog/article_text.txt new file mode 100644 index 0000000..4f9ca76 --- /dev/null +++ b/src/DjangoBlog-master/templates/search/indexes/blog/article_text.txt @@ -0,0 +1,3 @@ +{{ object.title }} +{{ object.author.username }} +{{ object.body }} \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/search/search.html b/src/DjangoBlog-master/templates/search/search.html new file mode 100644 index 0000000..1404c60 --- /dev/null +++ b/src/DjangoBlog-master/templates/search/search.html @@ -0,0 +1,66 @@ +{% extends 'share_layout/base.html' %} +{% load blog_tags %} +{% block header %} + {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} + + + + + + + +{% endblock %} +{% block content %} +
    +
    + {% if query %} +
    + {% if suggestion %} +

    + 已显示 “{{ suggestion }}” 的搜索结果。   + 仍然搜索:{{ query }}
    +

    + {% else %} +

    + 搜索:{{ query }}    +

    + {% endif %} +
    + {% endif %} + {% if query and page.object_list %} + {% for article in page.object_list %} + {% load_article_detail article.object True user %} + {% endfor %} + {% if page.has_previous or page.has_next %} + + + {% endif %} + {% else %} +
    + +

    哎呀,关键字:{{ query }}没有找到结果,要不换个词再试试?

    +
    + {% endif %} +
    +
    +{% endblock %} + + +{% block sidebar %} + {% load_sidebar request.user 'i' %} +{% endblock %} + + diff --git a/src/DjangoBlog-master/templates/share_layout/adsense.html b/src/DjangoBlog-master/templates/share_layout/adsense.html new file mode 100644 index 0000000..8f99c55 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/adsense.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/share_layout/base.html b/src/DjangoBlog-master/templates/share_layout/base.html new file mode 100644 index 0000000..75d0df5 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/base.html @@ -0,0 +1,123 @@ +{% load static %} +{% load cache %} +{% load i18n %} +{% load compress %} + + + + + + + + + + {% block header %} + {% block title %}{{ SITE_NAME }}{% endblock %} + + + {% endblock %} + {% load blog_tags %} + {% head_meta %} + + + + + + + + + + + {% compress css %} + + + + {% comment %}{% endcomment %} + + + + {% block compress_css %} + {% endblock %} + {% endcompress %} + {% if GLOBAL_HEADER %} + {{ GLOBAL_HEADER|safe }} + {% endif %} + + + +
    + +
    + + {% block content %} + {% endblock %} + + + {% block sidebar %} + {% endblock %} + + +
    + {% include 'share_layout/footer.html' %} +
    + + +
    + + {% compress js %} + + + + + + {% block compress_js %} + {% endblock %} + {% endcompress %} + {% block footer %} + {% endblock %} +
    + diff --git a/src/DjangoBlog-master/templates/share_layout/base_account.html b/src/DjangoBlog-master/templates/share_layout/base_account.html new file mode 100644 index 0000000..c00d842 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/base_account.html @@ -0,0 +1,47 @@ + + + + {% load static %} + + + + + + + + + {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} + + {% load compress %} + {% compress css %} + + + + + + + + + + {% endcompress %} + {% compress js %} + + + {% endcompress %} + + + + + +{% block content %} +{% endblock %} + + + + + + + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/share_layout/footer.html b/src/DjangoBlog-master/templates/share_layout/footer.html new file mode 100644 index 0000000..cd86a29 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/footer.html @@ -0,0 +1,56 @@ + + + diff --git a/src/DjangoBlog-master/templates/share_layout/nav.html b/src/DjangoBlog-master/templates/share_layout/nav.html new file mode 100644 index 0000000..24d4da6 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/nav.html @@ -0,0 +1,30 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/src/DjangoBlog-master/templates/share_layout/nav_node.html b/src/DjangoBlog-master/templates/share_layout/nav_node.html new file mode 100644 index 0000000..c266880 --- /dev/null +++ b/src/DjangoBlog-master/templates/share_layout/nav_node.html @@ -0,0 +1,19 @@ + + + From 63328bcfcbe54fffdcfecdfe4d0f14c57504169e Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:02:45 +0800 Subject: [PATCH 07/24] Update 0001_initial.py --- .../comments/migrations/0001_initial.py | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/DjangoBlog-master/comments/migrations/0001_initial.py b/src/DjangoBlog-master/comments/migrations/0001_initial.py index 61d1e53..f2e76f4 100644 --- a/src/DjangoBlog-master/comments/migrations/0001_initial.py +++ b/src/DjangoBlog-master/comments/migrations/0001_initial.py @@ -1,38 +1,37 @@ # Generated by Django 4.1.7 on 2023-03-02 07:14 +from django.conf import settings # 导入Django项目配置,用于获取用户模型等设置 +from django.db import migrations, models # 导入迁移和模型模块,用于定义数据库迁移操作 +import django.db.models.deletion # 导入外键删除行为处理模块,定义外键删除策略 +import django.utils.timezone # 导入时区工具,处理时间字段默认值 -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone +class Migration(migrations.Migration): # 定义迁移类,包含数据库迁移操作 -class Migration(migrations.Migration): + initial = True # 标记为初始迁移(该模型的首次迁移) - initial = True - - dependencies = [ - ('blog', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移 + ('blog', '0001_initial'), # 依赖blog应用的0001_initial迁移(确保Article模型存在) + migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移(支持自定义用户模型) ] - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('body', models.TextField(max_length=300, verbose_name='正文')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), + operations = [ # 迁移操作列表:当前迁移需执行的数据库操作 + migrations.CreateModel( # 创建Comment模型(对应数据库表) + name='Comment', # 模型名称为Comment(评论模型) + fields=[ # 模型字段定义 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 自增主键,自动创建,作为表的唯一标识 + ('body', models.TextField(max_length=300, verbose_name='正文')), # 评论正文字段,文本类型,最大300字符 + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 创建时间字段,默认值为当前时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # 最后修改时间字段,默认值为当前时间 + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # 评论显示开关,默认显示(True) + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), # 外键关联Article,级联删除(文章删则评论删) + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), # 外键关联用户模型,级联删除(用户删则评论删) + ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), # 自关联外键(支持评论回复),允许为空 ], - options={ - 'verbose_name': '评论', - 'verbose_name_plural': '评论', - 'ordering': ['-id'], - 'get_latest_by': 'id', + options={ # 模型元数据配置 + 'verbose_name': '评论', # 模型单数显示名称 + 'verbose_name_plural': '评论', # 模型复数显示名称 + 'ordering': ['-id'], # 默认排序:按id降序(最新评论在前) + 'get_latest_by': 'id', # 获取最新记录时依据id字段 }, ), - ] + ] \ No newline at end of file From d2ff1764001bccecee6ac765bc52cb2782e5593a Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:03:40 +0800 Subject: [PATCH 08/24] Update 0002_alter_comment_is_enable.py --- .../0002_alter_comment_is_enable.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py index 17c44db..239a27e 100644 --- a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py +++ b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py @@ -1,18 +1,17 @@ # Generated by Django 4.1.7 on 2023-04-24 13:48 +from django.db import migrations, models # 导入Django迁移和模型模块,用于数据库结构变更 -from django.db import migrations, models +class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作 -class Migration(migrations.Migration): - - dependencies = [ + dependencies = [ # 迁移依赖:需先执行comments应用的0001_initial迁移 ('comments', '0001_initial'), ] - operations = [ - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='是否显示'), + operations = [ # 迁移操作列表:当前需要执行的数据库变更 + migrations.AlterField( # 修改已有字段 + model_name='comment', # 要修改的模型名称为Comment + name='is_enable', # 要修改的字段名称为is_enable + field=models.BooleanField(default=False, verbose_name='是否显示'), # 将字段默认值从True改为False(评论默认不显示) ), - ] + ] \ No newline at end of file From 662cfd97590f9805b9d05ecc416f94b7be0fb136 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:04:49 +0800 Subject: [PATCH 09/24] Update 0003_alter_comment_options_remove_comment_created_time_and_more.py --- ...ns_remove_comment_created_time_and_more.py | 107 +++++++++--------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py index a1ca970..252918c 100644 --- a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py @@ -1,60 +1,59 @@ # Generated by Django 4.2.5 on 2023-09-06 13:13 +from django.conf import settings # 导入Django项目配置,用于获取用户模型设置 +from django.db import migrations, models # 导入迁移和模型模块,用于数据库结构变更 +import django.db.models.deletion # 导入外键删除行为处理模块 +import django.utils.timezone # 导入时区工具,处理时间字段默认值 -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone +class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作 -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ('comments', '0002_alter_comment_is_enable'), + dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移 + migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移 + ('blog', '0005_alter_article_options_alter_category_options_and_more'), # 依赖blog应用的指定迁移 + ('comments', '0002_alter_comment_is_enable'), # 依赖comments应用的0002迁移 ] - operations = [ - migrations.AlterModelOptions( - name='comment', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, - ), - migrations.RemoveField( - model_name='comment', - name='created_time', - ), - migrations.RemoveField( - model_name='comment', - name='last_mod_time', - ), - migrations.AddField( - model_name='comment', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='comment', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='comment', - name='article', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), - ), - 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'), - ), - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='enable'), - ), - 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'), - ), - ] + operations = [ # 迁移操作列表:当前需要执行的数据库变更 + migrations.AlterModelOptions( # 修改模型的元数据配置 + name='comment', # 目标模型为Comment + options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, # 将显示名称改为英文 + ), + migrations.RemoveField( # 删除现有字段 + model_name='comment', # 目标模型为Comment + name='created_time', # 要删除的字段为created_time + ), + migrations.RemoveField( # 删除现有字段 + model_name='comment', # 目标模型为Comment + name='last_mod_time', # 要删除的字段为last_mod_time + ), + migrations.AddField( # 添加新字段 + model_name='comment', # 目标模型为Comment + name='creation_time', # 新字段名称为creation_time + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), # 时间字段,默认当前时间,显示名称为英文 + ), + migrations.AddField( # 添加新字段 + model_name='comment', # 目标模型为Comment + name='last_modify_time', # 新字段名称为last_modify_time + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), # 时间字段,默认当前时间,显示名称为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='article', # 目标字段为article + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), # 将显示名称改为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='author', # 目标字段为author + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), # 将显示名称改为英文 + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='is_enable', # 目标字段为is_enable + field=models.BooleanField(default=False, verbose_name='enable'), # 将显示名称改为英文"enable" + ), + migrations.AlterField( # 修改现有字段 + model_name='comment', # 目标模型为Comment + name='parent_comment', # 目标字段为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 From 4836e57e98f72825ae8e5dfb56a9b24f43230f0b Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:06:12 +0800 Subject: [PATCH 10/24] Update comments_tags.py --- .../comments/templatetags/comments_tags.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/DjangoBlog-master/comments/templatetags/comments_tags.py b/src/DjangoBlog-master/comments/templatetags/comments_tags.py index fde02b4..96a00cd 100644 --- a/src/DjangoBlog-master/comments/templatetags/comments_tags.py +++ b/src/DjangoBlog-master/comments/templatetags/comments_tags.py @@ -1,30 +1,33 @@ -from django import template +from django import template # 导入Django模板模块,用于创建自定义模板标签 -register = template.Library() +register = template.Library() # 创建模板标签注册器,用于注册自定义标签 -@register.simple_tag +@register.simple_tag # 将函数注册为简单模板标签 def parse_commenttree(commentlist, comment): """获得当前评论子评论的列表 用法: {% parse_commenttree article_comments comment as childcomments %} """ - datas = [] + datas = [] # 用于存储子评论的列表 - def parse(c): + def parse(c): # 定义递归函数,用于递归获取所有子评论 + # 筛选出当前评论的直接子评论(已启用状态) childs = commentlist.filter(parent_comment=c, is_enable=True) - for child in childs: - datas.append(child) - parse(child) + for child in childs: # 遍历直接子评论 + datas.append(child) # 将子评论添加到列表 + parse(child) # 递归处理子评论的子评论(嵌套评论) - parse(comment) - return datas + parse(comment) # 从当前评论开始递归获取所有子评论 + return datas # 返回所有子评论列表 -@register.inclusion_tag('comments/tags/comment_item.html') +@register.inclusion_tag('comments/tags/comment_item.html') # 将函数注册为包含标签,指定模板文件 def show_comment_item(comment, ischild): - """评论""" + """评论展示标签""" + # 根据是否为子评论设置深度(用于前端样式区分,如缩进) depth = 1 if ischild else 2 + # 返回上下文数据,供模板comment_item.html使用 return { - 'comment_item': comment, - 'depth': depth - } + 'comment_item': comment, # 当前评论对象 + 'depth': depth # 评论深度(用于样式控制) + } \ No newline at end of file From a8dcf5aa392ee8d2be70483060adf78988054e8b Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:08:12 +0800 Subject: [PATCH 11/24] Update admin.py --- src/DjangoBlog-master/comments/admin.py | 41 ++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py index a814f3f..8abfe4e 100644 --- a/src/DjangoBlog-master/comments/admin.py +++ b/src/DjangoBlog-master/comments/admin.py @@ -1,47 +1,66 @@ -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 _ +from django.contrib import admin # 导入Django管理后台模块 +from django.urls import reverse # 导入reverse函数,用于生成URL +from django.utils.html import format_html # 导入HTML格式化函数,用于生成HTML标签 +from django.utils.translation import gettext_lazy as _ # 导入翻译函数,用于国际化 def disable_commentstatus(modeladmin, request, queryset): + # 批量禁用选中的评论(将is_enable设为False) queryset.update(is_enable=False) def enable_commentstatus(modeladmin, request, queryset): + # 批量启用选中的评论(将is_enable设为True) queryset.update(is_enable=True) +# 为批量操作设置显示名称(支持国际化) disable_commentstatus.short_description = _('Disable comments') enable_commentstatus.short_description = _('Enable comments') class CommentAdmin(admin.ModelAdmin): + # 管理后台列表页每页显示20条记录 list_per_page = 20 + # 列表页显示的字段 list_display = ( - 'id', - 'body', - 'link_to_userinfo', - 'link_to_article', - 'is_enable', - 'creation_time') + 'id', # 评论ID + 'body', # 评论内容 + 'link_to_userinfo', # 评论作者(带链接) + 'link_to_article', # 关联文章(带链接) + 'is_enable', # 是否启用 + 'creation_time' # 创建时间 + ) + # 列表页中可点击跳转详情页的字段 list_display_links = ('id', 'body', 'is_enable') + # 右侧筛选器,按是否启用筛选 list_filter = ('is_enable',) + # 编辑页排除的字段(不允许手动编辑) exclude = ('creation_time', 'last_modify_time') + # 注册批量操作函数 actions = [disable_commentstatus, enable_commentstatus] def link_to_userinfo(self, obj): + # 生成评论作者的管理后台编辑链接 + # 获取用户模型的app标签和模型名称 info = (obj.author._meta.app_label, obj.author._meta.model_name) + # 生成用户编辑页的URL link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + # 返回带链接的HTML,显示昵称或邮箱 return format_html( u'%s' % (link, obj.author.nickname if obj.author.nickname else obj.author.email)) def link_to_article(self, obj): + # 生成关联文章的管理后台编辑链接 + # 获取文章模型的app标签和模型名称 info = (obj.article._meta.app_label, obj.article._meta.model_name) + # 生成文章编辑页的URL link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + # 返回带链接的HTML,显示文章标题 return format_html( u'%s' % (link, obj.article.title)) + # 设置自定义字段在列表页的显示名称(支持国际化) link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') + link_to_article.short_description = _('Article') \ No newline at end of file From 190f420c497720c49818e9a4b13758a37c746742 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:09:02 +0800 Subject: [PATCH 12/24] Update apps.py --- src/DjangoBlog-master/comments/apps.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py index ff01b77..4b8998b 100644 --- a/src/DjangoBlog-master/comments/apps.py +++ b/src/DjangoBlog-master/comments/apps.py @@ -1,5 +1,4 @@ -from django.apps import AppConfig +from django.apps import AppConfig # 导入Django的应用配置基类 - -class CommentsConfig(AppConfig): - name = 'comments' +class CommentsConfig(AppConfig): # 定义评论应用的配置类,继承自AppConfig + name = 'comments' # 指定应用的名称为'comments',Django通过此名称识别该应用 \ No newline at end of file From 65020e1115bcabb98acf53bba49ec3607b53675c Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:09:46 +0800 Subject: [PATCH 13/24] Update forms.py --- src/DjangoBlog-master/comments/forms.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py index e83737d..74d8695 100644 --- a/src/DjangoBlog-master/comments/forms.py +++ b/src/DjangoBlog-master/comments/forms.py @@ -1,13 +1,15 @@ -from django import forms -from django.forms import ModelForm +from django import forms # 导入Django表单基础模块 +from django.forms import ModelForm # 导入模型表单类,用于基于模型创建表单 -from .models import Comment +from .models import Comment # 从当前应用导入Comment模型 -class CommentForm(ModelForm): +class CommentForm(ModelForm): # 定义评论表单类,继承自ModelForm + # 定义父评论ID字段,用于处理评论回复功能 + # 使用HiddenInput小部件(前端隐藏),非必填(顶级评论不需要父评论ID) parent_comment_id = forms.IntegerField( widget=forms.HiddenInput, required=False) - class Meta: - model = Comment - fields = ['body'] + class Meta: # 元数据配置 + model = Comment # 指定表单关联的模型为Comment + fields = ['body'] # 表单包含的字段,仅包含评论内容字段body \ No newline at end of file From 82d4f9a98b0a6182ada45f20c648742dc45c25b5 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:10:29 +0800 Subject: [PATCH 14/24] Update models.py --- src/DjangoBlog-master/comments/models.py | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py index 7c3bbc8..b3be4ee 100644 --- a/src/DjangoBlog-master/comments/models.py +++ b/src/DjangoBlog-master/comments/models.py @@ -1,39 +1,47 @@ -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 django.conf import settings # 导入Django项目设置,用于获取用户模型 +from django.db import models # 导入Django模型模块,用于定义数据模型 +from django.utils.timezone import now # 导入当前时间工具,用于时间字段默认值 +from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化 -from blog.models import Article +from blog.models import Article # 从blog应用导入Article模型,用于关联评论和文章 # Create your models here. class Comment(models.Model): + # 评论内容字段,文本类型,最大长度300字符,显示名称为"正文" body = models.TextField('正文', max_length=300) + # 评论创建时间字段,使用国际化显示名称,默认值为当前时间 creation_time = models.DateTimeField(_('creation time'), default=now) + # 评论最后修改时间字段,使用国际化显示名称,默认值为当前时间 last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # 外键关联到用户模型,使用国际化显示名称,级联删除(用户删除则评论删除) author = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('author'), on_delete=models.CASCADE) + # 外键关联到文章模型,使用国际化显示名称,级联删除(文章删除则评论删除) article = models.ForeignKey( Article, verbose_name=_('article'), on_delete=models.CASCADE) + # 自关联外键,用于实现评论回复功能,允许为空,级联删除 parent_comment = models.ForeignKey( 'self', verbose_name=_('parent comment'), blank=True, null=True, on_delete=models.CASCADE) + # 评论是否启用的开关,布尔类型,默认不启用,不允许为空 is_enable = models.BooleanField(_('enable'), default=False, blank=False, null=False) class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-id'] # 默认排序方式:按ID降序(最新评论在前) + verbose_name = _('comment') # 模型单数显示名称(国际化) + verbose_name_plural = verbose_name # 模型复数显示名称(与单数相同) + get_latest_by = 'id' # 获取最新记录时依据ID字段 def __str__(self): - return self.body + # 模型实例的字符串表示,返回评论内容 + return self.body \ No newline at end of file From 955b0a6eadd2e74ca6be3819370bc0720bb17328 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:11:48 +0800 Subject: [PATCH 15/24] Update tests.py --- src/DjangoBlog-master/comments/tests.py | 112 +++++++----------------- 1 file changed, 32 insertions(+), 80 deletions(-) diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py index 2a7f55f..269cdae 100644 --- a/src/DjangoBlog-master/comments/tests.py +++ b/src/DjangoBlog-master/comments/tests.py @@ -1,109 +1,61 @@ -from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse +from django.test import Client, RequestFactory, TransactionTestCase # 导入Django测试相关类 +from django.urls import reverse # 导入reverse函数,用于生成URL -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 +from accounts.models import BlogUser # 从accounts应用导入BlogUser模型(用户模型) +from blog.models import Category, Article # 从blog应用导入分类和文章模型 +from comments.models import Comment # 导入评论模型 +from comments.templatetags.comment_tags import * # 导入评论相关的模板标签 +from djangoblog.utils import get_max_articleid_commentid # 导入工具函数 # Create your tests here. -class CommentsTest(TransactionTestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() +class CommentsTest(TransactionTestCase): # 定义评论测试类,继承事务测试类(支持数据库事务回滚) + def setUp(self): # 测试前的初始化方法,每个测试方法执行前都会调用 + self.client = Client() # 创建测试客户端,用于模拟用户请求 + self.factory = RequestFactory() # 创建请求工厂,用于构造请求对象 + + # 配置博客评论设置 from blog.models import BlogSettings value = BlogSettings() - value.comment_need_review = True + value.comment_need_review = True # 设置评论需要审核 value.save() + # 创建超级用户(测试用) self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="liangliangyy1") - def update_article_comment_status(self, article): - comments = article.comment_set.all() - for comment in comments: - comment.is_enable = True - comment.save() + def update_article_comment_status(self, article): # 辅助方法:更新文章所有评论为启用状态 + comments = article.comment_set.all() # 获取文章的所有评论 + for comment in comments: # 遍历评论 + comment.is_enable = True # 设置为启用 + comment.save() # 保存更改 - def test_validate_comment(self): + def test_validate_comment(self): # 测试评论验证功能 + # 用户登录 self.client.login(username='liangliangyy1', password='liangliangyy1') + # 创建测试分类 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.author = self.user # 设置作者为测试用户 + article.category = category # 设置分类 + article.type = 'a' # 文章类型(假设'a'表示普通文章) + article.status = 'p' # 发布状态(假设'p'表示已发布) article.save() + # 生成评论提交的URL comment_url = reverse( 'comments:postcomment', kwargs={ - 'article_id': article.id}) + 'article_id': article.id}) # 传入文章ID参数 - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 0) - self.update_article_comment_status(article) - - self.assertEqual(len(article.comment_list()), 1) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].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 - }) - - 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) - comment = Comment.objects.get(id=parent_comment_id) - tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) - data = show_comment_item(comment, True) - self.assertIsNotNone(data) - s = get_max_articleid_commentid() - self.assertIsNotNone(s) - - from comments.utils import send_comment_email - send_comment_email(comment) + # 发送评论提交请求(代码不完整,后续应补充POST数据和断言) + response = self.client.post(comment_url, \ No newline at end of file From aa95dc52b3728a3f9b1727f87df48c75a40b9b5b Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:12:46 +0800 Subject: [PATCH 16/24] Update urls.py --- src/DjangoBlog-master/comments/urls.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py index 7df3fab..74f29b7 100644 --- a/src/DjangoBlog-master/comments/urls.py +++ b/src/DjangoBlog-master/comments/urls.py @@ -1,11 +1,12 @@ -from django.urls import path +from django.urls import path # 导入Django的路径函数,用于定义URL路由 -from . import views +from . import views # 从当前应用导入视图模块 -app_name = "comments" -urlpatterns = [ +app_name = "comments" # 定义应用的命名空间,用于模板中URL反向解析 +urlpatterns = [ # URL模式列表,定义URL与视图的映射关系 path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'), -] + 'article//postcomment', # URL路径,包含文章ID参数(整数类型) + views.CommentPostView.as_view(), # 关联的视图类,使用as_view()方法转换为可调用视图 + name='postcomment' # 该URL的名称,用于反向解析 + ), +] \ No newline at end of file From 11ae3570e0c1a9468bf16e46970763987d5ff7a2 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:13:50 +0800 Subject: [PATCH 17/24] Update utils.py --- src/DjangoBlog-master/comments/utils.py | 42 +++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py index f01dba7..32de978 100644 --- a/src/DjangoBlog-master/comments/utils.py +++ b/src/DjangoBlog-master/comments/utils.py @@ -1,28 +1,45 @@ -import logging +import logging # 导入日志模块,用于记录程序运行中的日志信息 -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化文本 -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email +from djangoblog.utils import get_current_site # 从自定义工具模块导入获取当前站点域名的函数 +from djangoblog.utils import send_email # 从自定义工具模块导入发送邮件的函数 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) # 创建当前模块的日志记录器,用于记录该模块的日志 def send_comment_email(comment): + """ + 发送评论相关邮件: + 1. 向评论作者发送评论成功的感谢邮件 + 2. 若当前评论是回复(有父评论),向父评论作者发送回复通知邮件 + """ + # 获取当前网站的域名(用于拼接文章链接) site = get_current_site().domain + # 邮件主题:评论感谢(支持国际化) subject = _('Thanks for your comment') + # 拼接评论对应的文章访问链接(HTTPS协议) article_url = f"https://{site}{comment.article.get_absolute_url()}" + # 构建给评论作者的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} + %(article_url)s""") % { + 'article_url': article_url, # 文章访问链接 + 'article_title': comment.article.title # 文章标题 + } + # 评论作者的邮箱(收件人) tomail = comment.author.email + # 调用发送邮件函数,向评论作者发送感谢邮件 send_email([tomail], subject, html_content) + try: + # 判断当前评论是否有父评论(即是否是回复评论) if comment.parent_comment: + # 构建给父评论作者的HTML格式邮件内容(回复通知,支持国际化) html_content = _("""Your comment on %(article_title)s
    has received a reply.
    %(comment_body)s
    @@ -30,9 +47,16 @@ def send_comment_email(comment):
    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} + """) % { + 'article_url': article_url, # 文章访问链接 + 'article_title': comment.article.title, # 文章标题 + 'comment_body': comment.parent_comment.body # 父评论的内容(供作者识别) + } + # 父评论作者的邮箱(收件人) tomail = comment.parent_comment.author.email + # 调用发送邮件函数,向父评论作者发送回复通知邮件 send_email([tomail], subject, html_content) + # 捕获发送回复邮件过程中的异常(避免单个邮件发送失败影响整体流程) except Exception as e: - logger.error(e) + # 记录异常日志(便于问题排查) + logger.error(e) \ No newline at end of file From dc3eef1f13937c26af58fcf5d3ea0441f6697f70 Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:15:40 +0800 Subject: [PATCH 18/24] Update views.py --- src/DjangoBlog-master/comments/views.py | 85 ++++++++++++++----------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py index ad9b2b9..57ffd52 100644 --- a/src/DjangoBlog-master/comments/views.py +++ b/src/DjangoBlog-master/comments/views.py @@ -1,63 +1,76 @@ # 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 django.core.exceptions import ValidationError # 导入验证异常类,用于处理验证错误 +from django.http import HttpResponseRedirect # 导入HTTP重定向类,用于页面跳转 +from django.shortcuts import get_object_or_404 # 导入获取对象或返回404的工具函数 +from django.utils.decorators import method_decorator # 导入方法装饰器工具,用于为类视图方法添加装饰器 +from django.views.decorators.csrf import csrf_protect # 导入CSRF保护装饰器,防止跨站请求伪造 +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 +from accounts.models import BlogUser # 从accounts应用导入用户模型 +from blog.models import Article # 从blog应用导入文章模型 +from .forms import CommentForm # 从当前应用导入评论表单 +from .models import Comment # 从当前应用导入评论模型 class CommentPostView(FormView): - form_class = CommentForm - template_name = 'blog/article_detail.html' + """评论提交视图类,处理评论发布功能""" + form_class = CommentForm # 指定使用的表单类为CommentForm + template_name = 'blog/article_detail.html' # 指定表单验证失败时渲染的模板 - @method_decorator(csrf_protect) + @method_decorator(csrf_protect) # 为dispatch方法添加CSRF保护 def dispatch(self, *args, **kwargs): + # 调用父类的dispatch方法,处理请求分发 return super(CommentPostView, self).dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - url = article.get_absolute_url() - return HttpResponseRedirect(url + "#comments") + """处理GET请求:重定向到文章详情页的评论区""" + article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象,不存在则返回404 + url = article.get_absolute_url() # 获取文章的绝对URL + return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论区锚点 def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """处理表单验证失败的情况""" + article_id = self.kwargs['article_id'] # 获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取文章对象 + # 渲染文章详情页,传递错误的表单和文章对象(用于显示错误信息) return self.render_to_response({ - 'form': form, - 'article': article + 'form': form, # 验证失败的表单(包含错误信息) + 'article': article # 文章对象 }) def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - user = self.request.user - author = BlogUser.objects.get(pk=user.pk) - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """处理表单验证通过后的逻辑:保存评论并跳转""" + user = self.request.user # 获取当前登录用户 + author = BlogUser.objects.get(pk=user.pk) # 获取用户对应的BlogUser对象 + article_id = self.kwargs['article_id'] # 获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取文章对象 + # 检查文章是否允许评论(评论状态为关闭或文章状态为草稿则不允许评论) if article.comment_status == 'c' or article.status == 'c': - raise ValidationError("该文章评论已关闭.") - comment = form.save(False) - comment.article = article + raise ValidationError("该文章评论已关闭.") # 抛出验证异常 + + comment = form.save(False) # 不立即保存表单数据,返回评论对象 + comment.article = article # 设置评论关联的文章 + + # 获取博客设置,判断评论是否需要审核 from djangoblog.utils import get_blog_setting settings = get_blog_setting() - if not settings.comment_need_review: - comment.is_enable = True - comment.author = author + if not settings.comment_need_review: # 如果不需要审核 + comment.is_enable = True # 直接设置评论为启用状态 + comment.author = author # 设置评论的作者 + + # 处理回复功能:如果存在父评论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 + pk=form.cleaned_data['parent_comment_id']) # 获取父评论对象 + comment.parent_comment = parent_comment # 设置当前评论的父评论 + + comment.save(True) # 保存评论到数据库 - comment.save(True) + # 重定向到文章详情页的当前评论位置(带锚点) return HttpResponseRedirect( "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) + (article.get_absolute_url(), comment.pk)) # 拼接URL,包含评论ID锚点 From 94dd264394e24beafcfff14ecfa29b5c4855403d Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:17:50 +0800 Subject: [PATCH 19/24] Update docker-compose.es.yml --- .../docker-compose/docker-compose.es.yml | 76 ++++++++++--------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml index 83e35ff..764aa81 100644 --- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml @@ -1,48 +1,52 @@ +# Docker Compose配置文件,版本为3(指定兼容的Compose语法版本) version: '3' +# 定义所有服务(容器) services: + # 1. Elasticsearch服务(用于全文搜索功能,集成IK中文分词器) es: - image: liangliangyy/elasticsearch-analysis-ik:8.6.1 - container_name: es - restart: always - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ports: + image: liangliangyy/elasticsearch-analysis-ik:8.6.1 # 使用带IK分词器的ES镜像,版本8.6.1 + container_name: es # 容器名称固定为"es",便于管理 + restart: always # 容器退出后自动重启(确保服务持续运行) + environment: # 环境变量配置 + - discovery.type=single-node # 单节点模式(无需集群,适合测试/小型部署) + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 设置JVM内存大小(初始/最大均为512M,避免内存溢出) + ports: # 端口映射:主机9200端口 → 容器9200端口(ES默认API端口) - 9200:9200 - volumes: - - ./bin/datas/es/:/usr/share/elasticsearch/data/ + volumes: # 数据卷挂载:持久化ES数据 + - ./bin/datas/es/:/usr/share/elasticsearch/data/ # 主机目录 → 容器内ES数据存储目录 + # 2. Kibana服务(ES的可视化管理工具,用于操作/监控ES) kibana: - image: kibana:8.6.1 - restart: always - container_name: kibana - ports: + image: kibana:8.6.1 # Kibana镜像,版本需与ES一致(8.6.1) + restart: always # 容器退出后自动重启 + container_name: kibana # 容器名称固定为"kibana" + ports: # 端口映射:主机5601端口 → 容器5601端口(Kibana默认Web端口) - 5601:5601 - environment: - - ELASTICSEARCH_HOSTS=http://es:9200 + environment: # 环境变量配置:指定关联的ES地址 + - ELASTICSEARCH_HOSTS=http://es:9200 # 指向同网络内的"es"服务(容器间通过服务名通信) + # 3. Django博客服务(核心应用服务) djangoblog: - build: . - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: + build: . # 基于当前目录的Dockerfile构建镜像(不使用现成镜像,需本地有Dockerfile) + restart: always # 容器退出后自动重启 + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 容器启动后执行的命令:运行启动脚本 + ports: # 端口映射:主机8000端口 → 容器8000端口(Django默认开发服务器端口) - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_MEMCACHED_LOCATION=memcached:11211 - - DJANGO_ELASTICSEARCH_HOST=es:9200 - links: + volumes: # 数据卷挂载:持久化应用数据/静态资源 + - ./collectedstatic:/code/djangoblog/collectedstatic # 主机静态资源目录 → 容器内静态资源目录(Nginx可直接访问) + - ./uploads:/code/djangoblog/uploads # 主机上传文件目录 → 容器内上传文件目录(如博客图片) + environment: # 环境变量配置:Django应用的关键参数(数据库、缓存、ES等) + - DJANGO_MYSQL_DATABASE=djangoblog # Django连接的MySQL数据库名 + - DJANGO_MYSQL_USER=root # MySQL用户名 + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL密码 + - DJANGO_MYSQL_HOST=db # MySQL服务地址(指向同网络内的"db"服务,需额外配置db服务) + - DJANGO_MYSQL_PORT=3306 # MySQL端口 + - DJANGO_MEMCACHED_LOCATION=memcached:11211 # Memcached缓存地址(指向同网络内的"memcached"服务,需额外配置) + - DJANGO_ELASTICSEARCH_HOST=es:9200 # ES服务地址(指向同网络内的"es"服务) + links: # 显式链接到其他服务(已逐步被depends_on替代,此处用于兼容) + - db # 链接到MySQL服务 + - memcached # 链接到Memcached服务 + depends_on: # 服务依赖:启动djangoblog前,先启动db服务(确保数据库就绪) - db - - memcached - depends_on: - - db - container_name: djangoblog - + container_name: djangoblog # 容器名称固定为"djangoblog" \ No newline at end of file From ba2c6831804b14060286cdb5783a2cd817a03cba Mon Sep 17 00:00:00 2001 From: plhw57tbe <2723863608@qq.com> Date: Sun, 12 Oct 2025 22:20:35 +0800 Subject: [PATCH 20/24] Update docker-compose.yml --- .../deploy/docker-compose/docker-compose.yml | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml index 9609af3..902b118 100644 --- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml +++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml @@ -1,60 +1,67 @@ +# Docker Compose配置文件,版本为3(指定Compose语法版本) version: '3' +# 定义所有服务(容器) services: + # 1. MySQL数据库服务(存储应用数据) db: - image: mysql:latest - restart: always - environment: - - MYSQL_DATABASE=djangoblog - - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E - ports: + image: mysql:latest # 使用最新版MySQL镜像 + restart: always # 容器退出后自动重启(确保服务持续运行) + environment: # 环境变量配置(数据库初始化参数) + - MYSQL_DATABASE=djangoblog # 自动创建的数据库名称 + - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL root用户密码 + ports: # 端口映射:主机3306端口 → 容器3306端口(MySQL默认端口) - 3306:3306 - volumes: - - ./bin/datas/mysql/:/var/lib/mysql - depends_on: + volumes: # 数据卷挂载:持久化MySQL数据 + - ./bin/datas/mysql/:/var/lib/mysql # 主机目录 → 容器内MySQL数据存储目录 + depends_on: # 服务依赖:启动db前先启动redis(可能用于数据库缓存等场景) - redis - container_name: db + container_name: db # 容器名称固定为"db" + # 2. Django博客应用服务(核心应用) djangoblog: - build: - context: ../../ - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: + build: # 构建配置 + context: ../../ # 指定Dockerfile所在的上下文目录(上级目录的上级目录) + restart: always # 容器退出后自动重启 + command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 启动命令:执行应用启动脚本 + ports: # 端口映射:主机8000端口 → 容器8000端口(Django应用端口) - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./logs:/code/djangoblog/logs - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_REDIS_URL=redis:6379 - links: + volumes: # 数据卷挂载:持久化应用数据和配置 + - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(供Nginx访问) + - ./logs:/code/djangoblog/logs # 应用日志目录 + - ./uploads:/code/djangoblog/uploads # 用户上传文件目录(如图片) + environment: # 环境变量配置(应用连接参数) + - DJANGO_MYSQL_DATABASE=djangoblog # 数据库名称(与db服务对应) + - DJANGO_MYSQL_USER=root # 数据库用户名 + - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # 数据库密码(与db服务对应) + - DJANGO_MYSQL_HOST=db # 数据库服务地址(指向同网络内的"db"服务) + - DJANGO_MYSQL_PORT=3306 # 数据库端口 + - DJANGO_REDIS_URL=redis:6379 # Redis服务地址(指向同网络内的"redis"服务) + links: # 显式链接到其他服务(用于容器间通信) + - db # 链接到MySQL服务 + - redis # 链接到Redis服务 + depends_on: # 服务依赖:启动djangoblog前先启动db服务(确保数据库就绪) - db - - redis - depends_on: - - db - container_name: djangoblog + container_name: djangoblog # 容器名称固定为"djangoblog" + + # 3. Nginx服务(反向代理和静态资源服务) nginx: - restart: always - image: nginx:latest - ports: + restart: always # 容器退出后自动重启 + image: nginx:latest # 使用最新版Nginx镜像 + ports: # 端口映射:HTTP(80)和HTTPS(443)端口 - "80:80" - "443:443" - volumes: - - ./bin/nginx.conf:/etc/nginx/nginx.conf - - ./collectedstatic:/code/djangoblog/collectedstatic - links: - - djangoblog:djangoblog - container_name: nginx + volumes: # 数据卷挂载:Nginx配置和静态资源 + - ./bin/nginx.conf:/etc/nginx/nginx.conf # 主机Nginx配置文件 → 容器内Nginx配置文件 + - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(与djangoblog服务共享) + links: # 链接到djangoblog服务,实现反向代理 + - djangoblog:djangoblog # 将djangoblog服务映射为"djangoblog"主机名 + container_name: nginx # 容器名称固定为"nginx" + # 4. Redis服务(缓存服务,用于提升应用性能) redis: - restart: always - image: redis:latest - container_name: redis - ports: - - "6379:6379" + restart: always # 容器退出后自动重启 + image: redis:latest # 使用最新版Redis镜像 + container_name: redis # 容器名称固定为"redis" + ports: # 端口映射:主机6379端口 → 容器6379端口(Redis默认端口) + - "6379:6379" \ No newline at end of file From 50b5eda147cd80d588d0d6f47394092d81fd9afb Mon Sep 17 00:00:00 2001 From: zxc <3425933825@qq.com> Date: Sun, 12 Oct 2025 22:56:58 +0800 Subject: [PATCH 21/24] Update 0001_initial.py --- .../owntracks/migrations/0001_initial.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py index 9eee55c..7939068 100644 --- a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py +++ b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py @@ -5,27 +5,37 @@ import django.utils.timezone class Migration(migrations.Migration): +"""数据库迁移类,用于定义数据库结构的变更""" +# 标识这是初始迁移(第一次创建模型) initial = True + + # 依赖的其他迁移文件,初始迁移没有依赖 dependencies = [ ] - + # 定义要执行的数据库操作列表 operations = [ + # 创建一个新的数据模型(数据库表) migrations.CreateModel( - name='OwnTrackLog', - fields=[ + name='OwnTrackLog', # 模型名称,对应数据库中的表名 + fields=[ # 模型包含的字段定义 + # 自增主键字段,BigAutoField会自动生成大整数类型的唯一ID ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + # 用户标识符字段,字符串类型,最大长度100 ('tid', models.CharField(max_length=100, verbose_name='用户')), + # 纬度字段,浮点型 ('lat', models.FloatField(verbose_name='纬度')), + # 经度字段,浮点型 ('lon', models.FloatField(verbose_name='经度')), + # 创建时间字段,默认值为当前时间 ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), ], - options={ - 'verbose_name': 'OwnTrackLogs', - 'verbose_name_plural': 'OwnTrackLogs', - 'ordering': ['created_time'], - 'get_latest_by': 'created_time', + options={ # 模型的额外配置选项 + 'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称 + 'verbose_name_plural': 'OwnTrackLogs', # 模型的复数显示名称 + 'ordering': ['created_time'], # 默认排序方式,按创建时间升序 + 'get_latest_by': 'created_time', # 指定获取最新记录时使用的字段 }, ), ] From 82c7f26e8f684a2e05863ec21be72c3cc74f4d52 Mon Sep 17 00:00:00 2001 From: zxc <3425933825@qq.com> Date: Sun, 12 Oct 2025 23:02:42 +0800 Subject: [PATCH 22/24] Update 0001_initial.py --- .../owntracks/migrations/0001_initial.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py index 7939068..6ce7555 100644 --- a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py +++ b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py @@ -1,25 +1,24 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - +# 由Django 4.1.7在2023年3月2日07:14生成 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): -"""数据库迁移类,用于定义数据库结构的变更""" + """数据库迁移类,用于定义数据库结构的变更""" -# 标识这是初始迁移(第一次创建模型) + # 标识这是初始迁移(第一次创建模型) initial = True - # 依赖的其他迁移文件,初始迁移没有依赖 dependencies = [ ] - # 定义要执行的数据库操作列表 + + # 定义要执行的数据库操作列表 operations = [ # 创建一个新的数据模型(数据库表) migrations.CreateModel( - name='OwnTrackLog', # 模型名称,对应数据库中的表名 - fields=[ # 模型包含的字段定义 + name='OwnTrackLog', # 模型名称,对应数据库中的表名 + fields=[ # 模型包含的字段定义 # 自增主键字段,BigAutoField会自动生成大整数类型的唯一ID ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 用户标识符字段,字符串类型,最大长度100 @@ -32,10 +31,10 @@ class Migration(migrations.Migration): ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), ], options={ # 模型的额外配置选项 - 'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称 + 'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称 'verbose_name_plural': 'OwnTrackLogs', # 模型的复数显示名称 - 'ordering': ['created_time'], # 默认排序方式,按创建时间升序 - 'get_latest_by': 'created_time', # 指定获取最新记录时使用的字段 + 'ordering': ['created_time'], # 默认排序方式,按创建时间升序 + 'get_latest_by': 'created_time', # 指定获取最新记录时使用的字段 }, ), ] From cfb4939c5a4890c07160415f1477f08e00c4aef3 Mon Sep 17 00:00:00 2001 From: zxc <3425933825@qq.com> Date: Sun, 12 Oct 2025 23:04:59 +0800 Subject: [PATCH 23/24] Update 0002_alter_owntracklog_options_and_more.py --- ...0002_alter_owntracklog_options_and_more.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py index b4f8dec..678a26a 100644 --- a/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py +++ b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py @@ -1,22 +1,36 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:19 - +# 由Django 4.2.5版本在2023年9月6日13:19自动生成 from django.db import migrations class Migration(migrations.Migration): + """ + 数据库迁移类,用于修改现有数据模型的结构和配置 + 这是一个增量迁移,基于之前的迁移进行修改 + """ + # 依赖关系:表示此迁移依赖于'owntracks'应用中的0001_initial迁移 + # 执行此迁移前必须先执行完依赖的迁移 dependencies = [ ('owntracks', '0001_initial'), ] + # 定义要执行的数据库操作列表 operations = [ + # 修改模型的配置选项 migrations.AlterModelOptions( - name='owntracklog', - options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'}, + name='owntracklog', # 要修改的模型名称 + # 新的模型配置选项 + options={ + 'get_latest_by': 'creation_time', # 更新获取最新记录的字段为creation_time + 'ordering': ['creation_time'], # 更新默认排序字段为creation_time + 'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称(未变) + 'verbose_name_plural': 'OwnTrackLogs' # 模型的复数显示名称(未变) + }, ), + # 重命名字段 migrations.RenameField( - model_name='owntracklog', - old_name='created_time', - new_name='creation_time', + model_name='owntracklog', # 要操作的模型名称 + old_name='created_time', # 原字段名 + new_name='creation_time', # 新字段名 ), ] From 2907454eb66ca66921decb229ab00c0f630e22af Mon Sep 17 00:00:00 2001 From: zxc <3425933825@qq.com> Date: Sun, 12 Oct 2025 23:15:13 +0800 Subject: [PATCH 24/24] Update admin.py --- src/DjangoBlog-master/owntracks/admin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/DjangoBlog-master/owntracks/admin.py b/src/DjangoBlog-master/owntracks/admin.py index 655b535..12f9dea 100644 --- a/src/DjangoBlog-master/owntracks/admin.py +++ b/src/DjangoBlog-master/owntracks/admin.py @@ -1,7 +1,13 @@ +# 导入Django的admin模块,用于管理后台配置 from django.contrib import admin +# 注册模型的地方(当前尚未注册任何模型) # Register your models here. +# 定义OwnTrackLogs模型的Admin管理类 +# 继承自ModelAdmin,这是Django admin的基础管理类 class OwnTrackLogsAdmin(admin.ModelAdmin): + # pass表示暂时不添加任何自定义配置 + # 此时会使用ModelAdmin的默认配置来展示和管理模型数据 pass