From 059ef9538e4e516900d1f773b8d04f5301412605 Mon Sep 17 00:00:00 2001 From: MuLang Tang <2439192341@qq.com> Date: Sun, 28 Sep 2025 22:11:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0DjangoBlog=E6=BA=90=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=88=B0src=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.coveragerc | 10 + src/.dockerignore | 11 + src/.gitattributes | 6 + src/.github/ISSUE_TEMPLATE.md | 18 + src/.github/workflows/codeql-analysis.yml | 47 + src/.github/workflows/django.yml | 136 +++ src/.github/workflows/docker.yml | 43 + src/.github/workflows/publish-release.yml | 39 + src/.gitignore | 80 ++ src/Dockerfile | 15 + src/LICENSE | 20 + src/README.md | 158 +++ src/accounts/__init__.py | 0 src/accounts/admin.py | 59 + src/accounts/apps.py | 5 + src/accounts/forms.py | 117 ++ src/accounts/migrations/0001_initial.py | 49 + ...s_remove_bloguser_created_time_and_more.py | 46 + src/accounts/migrations/__init__.py | 0 src/accounts/models.py | 35 + src/accounts/templatetags/__init__.py | 0 src/accounts/tests.py | 207 ++++ src/accounts/urls.py | 28 + src/accounts/user_login_backend.py | 26 + src/accounts/utils.py | 49 + src/accounts/views.py | 204 ++++ src/blog/__init__.py | 0 src/blog/admin.py | 112 ++ src/blog/apps.py | 5 + src/blog/context_processors.py | 43 + src/blog/documents.py | 213 ++++ src/blog/forms.py | 19 + src/blog/management/__init__.py | 0 src/blog/management/commands/__init__.py | 0 src/blog/management/commands/build_index.py | 18 + .../management/commands/build_search_words.py | 13 + src/blog/management/commands/clear_cache.py | 11 + .../management/commands/create_testdata.py | 40 + src/blog/management/commands/ping_baidu.py | 50 + .../management/commands/sync_user_avatar.py | 47 + src/blog/middleware.py | 42 + src/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 + src/blog/migrations/__init__.py | 0 src/blog/models.py | 376 ++++++ src/blog/search_indexes.py | 13 + src/blog/templatetags/__init__.py | 0 src/blog/templatetags/blog_tags.py | 344 ++++++ src/blog/tests.py | 232 ++++ src/blog/urls.py | 62 + src/blog/views.py | 379 ++++++ src/comments/__init__.py | 0 src/comments/admin.py | 47 + src/comments/apps.py | 5 + src/comments/forms.py | 13 + src/comments/migrations/0001_initial.py | 38 + .../0002_alter_comment_is_enable.py | 18 + ...ns_remove_comment_created_time_and_more.py | 60 + src/comments/migrations/__init__.py | 0 src/comments/models.py | 39 + src/comments/templatetags/__init__.py | 0 src/comments/templatetags/comments_tags.py | 30 + src/comments/tests.py | 109 ++ src/comments/urls.py | 11 + src/comments/utils.py | 38 + src/comments/views.py | 63 + src/db.sqlite3 | Bin 0 -> 278528 bytes .../docker-compose/docker-compose.es.yml | 48 + src/deploy/docker-compose/docker-compose.yml | 60 + src/deploy/entrypoint.sh | 31 + src/deploy/k8s/configmap.yaml | 119 ++ src/deploy/k8s/deployment.yaml | 274 +++++ src/deploy/k8s/gateway.yaml | 17 + src/deploy/k8s/pv.yaml | 94 ++ src/deploy/k8s/pvc.yaml | 60 + src/deploy/k8s/service.yaml | 80 ++ src/deploy/k8s/storageclass.yaml | 10 + src/deploy/nginx.conf | 50 + src/djangoblog/__init__.py | 1 + src/djangoblog/admin_site.py | 64 + src/djangoblog/apps.py | 11 + src/djangoblog/blog_signals.py | 122 ++ src/djangoblog/elasticsearch_backend.py | 183 +++ src/djangoblog/feeds.py | 40 + src/djangoblog/logentryadmin.py | 91 ++ src/djangoblog/plugin_manage/base_plugin.py | 41 + .../plugin_manage/hook_constants.py | 7 + src/djangoblog/plugin_manage/hooks.py | 44 + src/djangoblog/plugin_manage/loader.py | 19 + src/djangoblog/settings.py | 337 ++++++ src/djangoblog/sitemap.py | 59 + src/djangoblog/spider_notify.py | 21 + src/djangoblog/tests.py | 32 + src/djangoblog/urls.py | 64 + src/djangoblog/utils.py | 232 ++++ src/djangoblog/whoosh_cn_backend.py | 1044 +++++++++++++++++ src/djangoblog/wsgi.py | 16 + src/docs/README-en.md | 158 +++ src/docs/config-en.md | 64 + src/docs/config.md | 58 + src/docs/docker-en.md | 114 ++ src/docs/docker.md | 114 ++ src/docs/es.md | 28 + src/docs/imgs/alipay.jpg | Bin 0 -> 17961 bytes src/docs/imgs/pycharm_logo.png | Bin 0 -> 132045 bytes src/docs/imgs/wechat.jpg | Bin 0 -> 24722 bytes src/docs/k8s-en.md | 141 +++ src/docs/k8s.md | 141 +++ src/locale/en/LC_MESSAGES/django.mo | Bin 0 -> 11097 bytes src/locale/en/LC_MESSAGES/django.po | 685 +++++++++++ src/locale/zh_Hans/LC_MESSAGES/django.mo | Bin 0 -> 10321 bytes src/locale/zh_Hans/LC_MESSAGES/django.po | 667 +++++++++++ src/locale/zh_Hant/LC_MESSAGES/django.mo | Bin 0 -> 10268 bytes src/locale/zh_Hant/LC_MESSAGES/django.po | 668 +++++++++++ src/manage.py | 22 + src/myenv/Scripts/Activate.ps1 | 502 ++++++++ src/myenv/Scripts/activate | 69 ++ src/myenv/Scripts/activate.bat | 34 + src/myenv/Scripts/deactivate.bat | 22 + src/myenv/Scripts/pip.exe | Bin 0 -> 108409 bytes src/myenv/Scripts/pip3.10.exe | Bin 0 -> 108409 bytes src/myenv/Scripts/pip3.exe | Bin 0 -> 108409 bytes src/myenv/Scripts/python.exe | Bin 0 -> 268568 bytes src/myenv/Scripts/pythonw.exe | Bin 0 -> 257304 bytes src/myenv/pyvenv.cfg | 3 + src/oauth/__init__.py | 0 src/oauth/admin.py | 54 + src/oauth/apps.py | 5 + src/oauth/forms.py | 12 + src/oauth/migrations/0001_initial.py | 57 + ...ptions_alter_oauthuser_options_and_more.py | 86 ++ .../0003_alter_oauthuser_nickname.py | 18 + src/oauth/migrations/__init__.py | 0 src/oauth/models.py | 67 ++ src/oauth/oauthmanager.py | 504 ++++++++ src/oauth/templatetags/__init__.py | 1 + src/oauth/templatetags/oauth_tags.py | 22 + src/oauth/tests.py | 249 ++++ src/oauth/urls.py | 25 + src/oauth/views.py | 253 ++++ src/owntracks/__init__.py | 0 src/owntracks/admin.py | 7 + src/owntracks/apps.py | 5 + src/owntracks/migrations/0001_initial.py | 31 + ...0002_alter_owntracklog_options_and_more.py | 22 + src/owntracks/migrations/__init__.py | 0 src/owntracks/models.py | 20 + src/owntracks/tests.py | 64 + src/owntracks/urls.py | 12 + src/owntracks/views.py | 127 ++ src/plugins/__init__.py | 1 + src/plugins/article_copyright/__init__.py | 1 + src/plugins/article_copyright/plugin.py | 32 + src/plugins/external_links/__init__.py | 1 + src/plugins/external_links/plugin.py | 48 + src/plugins/reading_time/__init__.py | 1 + src/plugins/reading_time/plugin.py | 43 + src/plugins/seo_optimizer/__init__.py | 1 + src/plugins/seo_optimizer/plugin.py | 142 +++ src/plugins/view_count/__init__.py | 1 + src/plugins/view_count/plugin.py | 18 + src/requirements.txt | Bin 0 -> 2554 bytes src/servermanager/MemcacheStorage.py | 32 + src/servermanager/__init__.py | 0 src/servermanager/admin.py | 19 + src/servermanager/api/__init__.py | 1 + src/servermanager/api/blogapi.py | 27 + src/servermanager/api/commonapi.py | 64 + src/servermanager/apps.py | 5 + src/servermanager/migrations/0001_initial.py | 45 + ...002_alter_emailsendlog_options_and_more.py | 32 + src/servermanager/migrations/__init__.py | 0 src/servermanager/models.py | 33 + src/servermanager/robot.py | 187 +++ src/servermanager/tests.py | 79 ++ src/servermanager/urls.py | 10 + src/servermanager/views.py | 1 + src/templates/account/forget_password.html | 30 + src/templates/account/login.html | 46 + src/templates/account/registration_form.html | 29 + src/templates/account/result.html | 27 + src/templates/blog/article_archives.html | 60 + src/templates/blog/article_detail.html | 52 + src/templates/blog/article_index.html | 42 + src/templates/blog/error_page.html | 45 + src/templates/blog/links_list.html | 44 + src/templates/blog/tags/article_info.html | 74 ++ .../blog/tags/article_meta_info.html | 59 + .../blog/tags/article_pagination.html | 17 + src/templates/blog/tags/article_tag_list.html | 19 + src/templates/blog/tags/breadcrumb.html | 19 + src/templates/blog/tags/sidebar.html | 136 +++ src/templates/comments/tags/comment_item.html | 34 + .../comments/tags/comment_item_tree.html | 54 + src/templates/comments/tags/comment_list.html | 45 + src/templates/comments/tags/post_comment.html | 33 + src/templates/oauth/bindsuccess.html | 22 + src/templates/oauth/oauth_applications.html | 13 + src/templates/oauth/require_email.html | 46 + src/templates/owntracks/show_log_dates.html | 17 + src/templates/owntracks/show_maps.html | 135 +++ .../search/indexes/blog/article_text.txt | 3 + src/templates/search/search.html | 66 ++ src/templates/share_layout/adsense.html | 6 + src/templates/share_layout/base.html | 123 ++ src/templates/share_layout/base_account.html | 47 + src/templates/share_layout/footer.html | 56 + src/templates/share_layout/nav.html | 30 + src/templates/share_layout/nav_node.html | 19 + 213 files changed, 15093 insertions(+) create mode 100644 src/.coveragerc create mode 100644 src/.dockerignore create mode 100644 src/.gitattributes create mode 100644 src/.github/ISSUE_TEMPLATE.md create mode 100644 src/.github/workflows/codeql-analysis.yml create mode 100644 src/.github/workflows/django.yml create mode 100644 src/.github/workflows/docker.yml create mode 100644 src/.github/workflows/publish-release.yml create mode 100644 src/.gitignore create mode 100644 src/Dockerfile create mode 100644 src/LICENSE create mode 100644 src/README.md create mode 100644 src/accounts/__init__.py create mode 100644 src/accounts/admin.py create mode 100644 src/accounts/apps.py create mode 100644 src/accounts/forms.py create mode 100644 src/accounts/migrations/0001_initial.py create mode 100644 src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py create mode 100644 src/accounts/migrations/__init__.py create mode 100644 src/accounts/models.py create mode 100644 src/accounts/templatetags/__init__.py create mode 100644 src/accounts/tests.py create mode 100644 src/accounts/urls.py create mode 100644 src/accounts/user_login_backend.py create mode 100644 src/accounts/utils.py create mode 100644 src/accounts/views.py create mode 100644 src/blog/__init__.py create mode 100644 src/blog/admin.py create mode 100644 src/blog/apps.py create mode 100644 src/blog/context_processors.py create mode 100644 src/blog/documents.py create mode 100644 src/blog/forms.py create mode 100644 src/blog/management/__init__.py create mode 100644 src/blog/management/commands/__init__.py create mode 100644 src/blog/management/commands/build_index.py create mode 100644 src/blog/management/commands/build_search_words.py create mode 100644 src/blog/management/commands/clear_cache.py create mode 100644 src/blog/management/commands/create_testdata.py create mode 100644 src/blog/management/commands/ping_baidu.py create mode 100644 src/blog/management/commands/sync_user_avatar.py create mode 100644 src/blog/middleware.py create mode 100644 src/blog/migrations/0001_initial.py create mode 100644 src/blog/migrations/0002_blogsettings_global_footer_and_more.py create mode 100644 src/blog/migrations/0003_blogsettings_comment_need_review.py create mode 100644 src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py create mode 100644 src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py create mode 100644 src/blog/migrations/0006_alter_blogsettings_options.py create mode 100644 src/blog/migrations/__init__.py create mode 100644 src/blog/models.py create mode 100644 src/blog/search_indexes.py create mode 100644 src/blog/templatetags/__init__.py create mode 100644 src/blog/templatetags/blog_tags.py create mode 100644 src/blog/tests.py create mode 100644 src/blog/urls.py create mode 100644 src/blog/views.py create mode 100644 src/comments/__init__.py create mode 100644 src/comments/admin.py create mode 100644 src/comments/apps.py create mode 100644 src/comments/forms.py create mode 100644 src/comments/migrations/0001_initial.py create mode 100644 src/comments/migrations/0002_alter_comment_is_enable.py create mode 100644 src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py create mode 100644 src/comments/migrations/__init__.py create mode 100644 src/comments/models.py create mode 100644 src/comments/templatetags/__init__.py create mode 100644 src/comments/templatetags/comments_tags.py create mode 100644 src/comments/tests.py create mode 100644 src/comments/urls.py create mode 100644 src/comments/utils.py create mode 100644 src/comments/views.py create mode 100644 src/db.sqlite3 create mode 100644 src/deploy/docker-compose/docker-compose.es.yml create mode 100644 src/deploy/docker-compose/docker-compose.yml create mode 100644 src/deploy/entrypoint.sh create mode 100644 src/deploy/k8s/configmap.yaml create mode 100644 src/deploy/k8s/deployment.yaml create mode 100644 src/deploy/k8s/gateway.yaml create mode 100644 src/deploy/k8s/pv.yaml create mode 100644 src/deploy/k8s/pvc.yaml create mode 100644 src/deploy/k8s/service.yaml create mode 100644 src/deploy/k8s/storageclass.yaml create mode 100644 src/deploy/nginx.conf create mode 100644 src/djangoblog/__init__.py create mode 100644 src/djangoblog/admin_site.py create mode 100644 src/djangoblog/apps.py create mode 100644 src/djangoblog/blog_signals.py create mode 100644 src/djangoblog/elasticsearch_backend.py create mode 100644 src/djangoblog/feeds.py create mode 100644 src/djangoblog/logentryadmin.py create mode 100644 src/djangoblog/plugin_manage/base_plugin.py create mode 100644 src/djangoblog/plugin_manage/hook_constants.py create mode 100644 src/djangoblog/plugin_manage/hooks.py create mode 100644 src/djangoblog/plugin_manage/loader.py create mode 100644 src/djangoblog/settings.py create mode 100644 src/djangoblog/sitemap.py create mode 100644 src/djangoblog/spider_notify.py create mode 100644 src/djangoblog/tests.py create mode 100644 src/djangoblog/urls.py create mode 100644 src/djangoblog/utils.py create mode 100644 src/djangoblog/whoosh_cn_backend.py create mode 100644 src/djangoblog/wsgi.py create mode 100644 src/docs/README-en.md create mode 100644 src/docs/config-en.md create mode 100644 src/docs/config.md create mode 100644 src/docs/docker-en.md create mode 100644 src/docs/docker.md create mode 100644 src/docs/es.md create mode 100644 src/docs/imgs/alipay.jpg create mode 100644 src/docs/imgs/pycharm_logo.png create mode 100644 src/docs/imgs/wechat.jpg create mode 100644 src/docs/k8s-en.md create mode 100644 src/docs/k8s.md create mode 100644 src/locale/en/LC_MESSAGES/django.mo create mode 100644 src/locale/en/LC_MESSAGES/django.po create mode 100644 src/locale/zh_Hans/LC_MESSAGES/django.mo create mode 100644 src/locale/zh_Hans/LC_MESSAGES/django.po create mode 100644 src/locale/zh_Hant/LC_MESSAGES/django.mo create mode 100644 src/locale/zh_Hant/LC_MESSAGES/django.po create mode 100644 src/manage.py create mode 100644 src/myenv/Scripts/Activate.ps1 create mode 100644 src/myenv/Scripts/activate create mode 100644 src/myenv/Scripts/activate.bat create mode 100644 src/myenv/Scripts/deactivate.bat create mode 100644 src/myenv/Scripts/pip.exe create mode 100644 src/myenv/Scripts/pip3.10.exe create mode 100644 src/myenv/Scripts/pip3.exe create mode 100644 src/myenv/Scripts/python.exe create mode 100644 src/myenv/Scripts/pythonw.exe create mode 100644 src/myenv/pyvenv.cfg create mode 100644 src/oauth/__init__.py create mode 100644 src/oauth/admin.py create mode 100644 src/oauth/apps.py create mode 100644 src/oauth/forms.py create mode 100644 src/oauth/migrations/0001_initial.py create mode 100644 src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py create mode 100644 src/oauth/migrations/0003_alter_oauthuser_nickname.py create mode 100644 src/oauth/migrations/__init__.py create mode 100644 src/oauth/models.py create mode 100644 src/oauth/oauthmanager.py create mode 100644 src/oauth/templatetags/__init__.py create mode 100644 src/oauth/templatetags/oauth_tags.py create mode 100644 src/oauth/tests.py create mode 100644 src/oauth/urls.py create mode 100644 src/oauth/views.py create mode 100644 src/owntracks/__init__.py create mode 100644 src/owntracks/admin.py create mode 100644 src/owntracks/apps.py create mode 100644 src/owntracks/migrations/0001_initial.py create mode 100644 src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py create mode 100644 src/owntracks/migrations/__init__.py create mode 100644 src/owntracks/models.py create mode 100644 src/owntracks/tests.py create mode 100644 src/owntracks/urls.py create mode 100644 src/owntracks/views.py create mode 100644 src/plugins/__init__.py create mode 100644 src/plugins/article_copyright/__init__.py create mode 100644 src/plugins/article_copyright/plugin.py create mode 100644 src/plugins/external_links/__init__.py create mode 100644 src/plugins/external_links/plugin.py create mode 100644 src/plugins/reading_time/__init__.py create mode 100644 src/plugins/reading_time/plugin.py create mode 100644 src/plugins/seo_optimizer/__init__.py create mode 100644 src/plugins/seo_optimizer/plugin.py create mode 100644 src/plugins/view_count/__init__.py create mode 100644 src/plugins/view_count/plugin.py create mode 100644 src/requirements.txt create mode 100644 src/servermanager/MemcacheStorage.py create mode 100644 src/servermanager/__init__.py create mode 100644 src/servermanager/admin.py create mode 100644 src/servermanager/api/__init__.py create mode 100644 src/servermanager/api/blogapi.py create mode 100644 src/servermanager/api/commonapi.py create mode 100644 src/servermanager/apps.py create mode 100644 src/servermanager/migrations/0001_initial.py create mode 100644 src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py create mode 100644 src/servermanager/migrations/__init__.py create mode 100644 src/servermanager/models.py create mode 100644 src/servermanager/robot.py create mode 100644 src/servermanager/tests.py create mode 100644 src/servermanager/urls.py create mode 100644 src/servermanager/views.py create mode 100644 src/templates/account/forget_password.html create mode 100644 src/templates/account/login.html create mode 100644 src/templates/account/registration_form.html create mode 100644 src/templates/account/result.html create mode 100644 src/templates/blog/article_archives.html create mode 100644 src/templates/blog/article_detail.html create mode 100644 src/templates/blog/article_index.html create mode 100644 src/templates/blog/error_page.html create mode 100644 src/templates/blog/links_list.html create mode 100644 src/templates/blog/tags/article_info.html create mode 100644 src/templates/blog/tags/article_meta_info.html create mode 100644 src/templates/blog/tags/article_pagination.html create mode 100644 src/templates/blog/tags/article_tag_list.html create mode 100644 src/templates/blog/tags/breadcrumb.html create mode 100644 src/templates/blog/tags/sidebar.html create mode 100644 src/templates/comments/tags/comment_item.html create mode 100644 src/templates/comments/tags/comment_item_tree.html create mode 100644 src/templates/comments/tags/comment_list.html create mode 100644 src/templates/comments/tags/post_comment.html create mode 100644 src/templates/oauth/bindsuccess.html create mode 100644 src/templates/oauth/oauth_applications.html create mode 100644 src/templates/oauth/require_email.html create mode 100644 src/templates/owntracks/show_log_dates.html create mode 100644 src/templates/owntracks/show_maps.html create mode 100644 src/templates/search/indexes/blog/article_text.txt create mode 100644 src/templates/search/search.html create mode 100644 src/templates/share_layout/adsense.html create mode 100644 src/templates/share_layout/base.html create mode 100644 src/templates/share_layout/base_account.html create mode 100644 src/templates/share_layout/footer.html create mode 100644 src/templates/share_layout/nav.html create mode 100644 src/templates/share_layout/nav_node.html diff --git a/src/.coveragerc b/src/.coveragerc new file mode 100644 index 0000000..9757484 --- /dev/null +++ b/src/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = . +include = *.py +omit = + *migrations* + *tests* + *.html + *whoosh_cn_backend* + *settings.py* + *venv* diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..2818c38 --- /dev/null +++ b/src/.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/.gitattributes b/src/.gitattributes new file mode 100644 index 0000000..fd52ece --- /dev/null +++ b/src/.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/.github/ISSUE_TEMPLATE.md b/src/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..2b5b7aa --- /dev/null +++ b/src/.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/.github/workflows/codeql-analysis.yml b/src/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6b76522 --- /dev/null +++ b/src/.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/.github/workflows/django.yml b/src/.github/workflows/django.yml new file mode 100644 index 0000000..94baea9 --- /dev/null +++ b/src/.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/.github/workflows/docker.yml b/src/.github/workflows/docker.yml new file mode 100644 index 0000000..a312e2f --- /dev/null +++ b/src/.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/.github/workflows/publish-release.yml b/src/.github/workflows/publish-release.yml new file mode 100644 index 0000000..5eb0853 --- /dev/null +++ b/src/.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/.gitignore b/src/.gitignore new file mode 100644 index 0000000..3015816 --- /dev/null +++ b/src/.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/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..80b46ac --- /dev/null +++ b/src/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/LICENSE b/src/LICENSE new file mode 100644 index 0000000..3b08474 --- /dev/null +++ b/src/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/README.md b/src/README.md new file mode 100644 index 0000000..56aa4cc --- /dev/null +++ b/src/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/accounts/__init__.py b/src/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/admin.py b/src/accounts/admin.py new file mode 100644 index 0000000..32e483c --- /dev/null +++ b/src/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/accounts/apps.py b/src/accounts/apps.py new file mode 100644 index 0000000..9b3fc5a --- /dev/null +++ b/src/accounts/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'accounts' diff --git a/src/accounts/forms.py b/src/accounts/forms.py new file mode 100644 index 0000000..fce4137 --- /dev/null +++ b/src/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/accounts/migrations/0001_initial.py b/src/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..d2fbcab --- /dev/null +++ b/src/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/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py new file mode 100644 index 0000000..1a9f509 --- /dev/null +++ b/src/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/accounts/migrations/__init__.py b/src/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/models.py b/src/accounts/models.py new file mode 100644 index 0000000..3baddbb --- /dev/null +++ b/src/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/accounts/templatetags/__init__.py b/src/accounts/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/accounts/tests.py b/src/accounts/tests.py new file mode 100644 index 0000000..6893411 --- /dev/null +++ b/src/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/accounts/urls.py b/src/accounts/urls.py new file mode 100644 index 0000000..107a801 --- /dev/null +++ b/src/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/accounts/user_login_backend.py b/src/accounts/user_login_backend.py new file mode 100644 index 0000000..73cdca1 --- /dev/null +++ b/src/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/accounts/utils.py b/src/accounts/utils.py new file mode 100644 index 0000000..4b94bdf --- /dev/null +++ b/src/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/accounts/views.py b/src/accounts/views.py new file mode 100644 index 0000000..ae67aec --- /dev/null +++ b/src/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/blog/__init__.py b/src/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blog/admin.py b/src/blog/admin.py new file mode 100644 index 0000000..46c3420 --- /dev/null +++ b/src/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/blog/apps.py b/src/blog/apps.py new file mode 100644 index 0000000..7930587 --- /dev/null +++ b/src/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = 'blog' diff --git a/src/blog/context_processors.py b/src/blog/context_processors.py new file mode 100644 index 0000000..73e3088 --- /dev/null +++ b/src/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/blog/documents.py b/src/blog/documents.py new file mode 100644 index 0000000..0f1db7b --- /dev/null +++ b/src/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/blog/forms.py b/src/blog/forms.py new file mode 100644 index 0000000..715be76 --- /dev/null +++ b/src/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/blog/management/__init__.py b/src/blog/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blog/management/commands/__init__.py b/src/blog/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blog/management/commands/build_index.py b/src/blog/management/commands/build_index.py new file mode 100644 index 0000000..3c4acd7 --- /dev/null +++ b/src/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/blog/management/commands/build_search_words.py b/src/blog/management/commands/build_search_words.py new file mode 100644 index 0000000..cfe7e0d --- /dev/null +++ b/src/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/blog/management/commands/clear_cache.py b/src/blog/management/commands/clear_cache.py new file mode 100644 index 0000000..0d66172 --- /dev/null +++ b/src/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/blog/management/commands/create_testdata.py b/src/blog/management/commands/create_testdata.py new file mode 100644 index 0000000..675d2ba --- /dev/null +++ b/src/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/blog/management/commands/ping_baidu.py b/src/blog/management/commands/ping_baidu.py new file mode 100644 index 0000000..2c7fbdd --- /dev/null +++ b/src/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/blog/management/commands/sync_user_avatar.py b/src/blog/management/commands/sync_user_avatar.py new file mode 100644 index 0000000..d0f4612 --- /dev/null +++ b/src/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/blog/middleware.py b/src/blog/middleware.py new file mode 100644 index 0000000..94dd70c --- /dev/null +++ b/src/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/blog/migrations/0001_initial.py b/src/blog/migrations/0001_initial.py new file mode 100644 index 0000000..3d391b6 --- /dev/null +++ b/src/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/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/blog/migrations/0002_blogsettings_global_footer_and_more.py new file mode 100644 index 0000000..adbaa36 --- /dev/null +++ b/src/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/blog/migrations/0003_blogsettings_comment_need_review.py b/src/blog/migrations/0003_blogsettings_comment_need_review.py new file mode 100644 index 0000000..e9f5502 --- /dev/null +++ b/src/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/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py new file mode 100644 index 0000000..ceb1398 --- /dev/null +++ b/src/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/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py new file mode 100644 index 0000000..d08e853 --- /dev/null +++ b/src/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/blog/migrations/0006_alter_blogsettings_options.py b/src/blog/migrations/0006_alter_blogsettings_options.py new file mode 100644 index 0000000..e36feb4 --- /dev/null +++ b/src/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/blog/migrations/__init__.py b/src/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blog/models.py b/src/blog/models.py new file mode 100644 index 0000000..083788b --- /dev/null +++ b/src/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/blog/search_indexes.py b/src/blog/search_indexes.py new file mode 100644 index 0000000..7f1dfac --- /dev/null +++ b/src/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/blog/templatetags/__init__.py b/src/blog/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blog/templatetags/blog_tags.py b/src/blog/templatetags/blog_tags.py new file mode 100644 index 0000000..d6cd5d5 --- /dev/null +++ b/src/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/blog/tests.py b/src/blog/tests.py new file mode 100644 index 0000000..ee13505 --- /dev/null +++ b/src/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/blog/urls.py b/src/blog/urls.py new file mode 100644 index 0000000..adf2703 --- /dev/null +++ b/src/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/blog/views.py b/src/blog/views.py new file mode 100644 index 0000000..d5dc7ec --- /dev/null +++ b/src/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/comments/__init__.py b/src/comments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/comments/admin.py b/src/comments/admin.py new file mode 100644 index 0000000..a814f3f --- /dev/null +++ b/src/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/comments/apps.py b/src/comments/apps.py new file mode 100644 index 0000000..ff01b77 --- /dev/null +++ b/src/comments/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CommentsConfig(AppConfig): + name = 'comments' diff --git a/src/comments/forms.py b/src/comments/forms.py new file mode 100644 index 0000000..e83737d --- /dev/null +++ b/src/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/comments/migrations/0001_initial.py b/src/comments/migrations/0001_initial.py new file mode 100644 index 0000000..61d1e53 --- /dev/null +++ b/src/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/comments/migrations/0002_alter_comment_is_enable.py b/src/comments/migrations/0002_alter_comment_is_enable.py new file mode 100644 index 0000000..17c44db --- /dev/null +++ b/src/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/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py new file mode 100644 index 0000000..a1ca970 --- /dev/null +++ b/src/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/comments/migrations/__init__.py b/src/comments/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/comments/models.py b/src/comments/models.py new file mode 100644 index 0000000..7c3bbc8 --- /dev/null +++ b/src/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/comments/templatetags/__init__.py b/src/comments/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/comments/templatetags/comments_tags.py b/src/comments/templatetags/comments_tags.py new file mode 100644 index 0000000..fde02b4 --- /dev/null +++ b/src/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/comments/tests.py b/src/comments/tests.py new file mode 100644 index 0000000..2a7f55f --- /dev/null +++ b/src/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/comments/urls.py b/src/comments/urls.py new file mode 100644 index 0000000..7df3fab --- /dev/null +++ b/src/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/comments/utils.py b/src/comments/utils.py new file mode 100644 index 0000000..f01dba7 --- /dev/null +++ b/src/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/comments/views.py b/src/comments/views.py new file mode 100644 index 0000000..ad9b2b9 --- /dev/null +++ b/src/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/db.sqlite3 b/src/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..3643a5d549511a047045890ec4fc9e3fa5e6d671 GIT binary patch literal 278528 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 literal 0 HcmV?d00001 diff --git a/src/deploy/docker-compose/docker-compose.es.yml b/src/deploy/docker-compose/docker-compose.es.yml new file mode 100644 index 0000000..83e35ff --- /dev/null +++ b/src/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/deploy/docker-compose/docker-compose.yml b/src/deploy/docker-compose/docker-compose.yml new file mode 100644 index 0000000..9609af3 --- /dev/null +++ b/src/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/deploy/entrypoint.sh b/src/deploy/entrypoint.sh new file mode 100644 index 0000000..2fb6491 --- /dev/null +++ b/src/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/deploy/k8s/configmap.yaml b/src/deploy/k8s/configmap.yaml new file mode 100644 index 0000000..835d4ad --- /dev/null +++ b/src/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/deploy/k8s/deployment.yaml b/src/deploy/k8s/deployment.yaml new file mode 100644 index 0000000..414fdcc --- /dev/null +++ b/src/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/deploy/k8s/gateway.yaml b/src/deploy/k8s/gateway.yaml new file mode 100644 index 0000000..a8de073 --- /dev/null +++ b/src/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/deploy/k8s/pv.yaml b/src/deploy/k8s/pv.yaml new file mode 100644 index 0000000..874b72f --- /dev/null +++ b/src/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/deploy/k8s/pvc.yaml b/src/deploy/k8s/pvc.yaml new file mode 100644 index 0000000..ef238c5 --- /dev/null +++ b/src/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/deploy/k8s/service.yaml b/src/deploy/k8s/service.yaml new file mode 100644 index 0000000..4ef2931 --- /dev/null +++ b/src/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/deploy/k8s/storageclass.yaml b/src/deploy/k8s/storageclass.yaml new file mode 100644 index 0000000..5d5a14c --- /dev/null +++ b/src/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/deploy/nginx.conf b/src/deploy/nginx.conf new file mode 100644 index 0000000..32161d8 --- /dev/null +++ b/src/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/__init__.py b/src/djangoblog/__init__.py new file mode 100644 index 0000000..1e205f4 --- /dev/null +++ b/src/djangoblog/__init__.py @@ -0,0 +1 @@ +default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/djangoblog/admin_site.py b/src/djangoblog/admin_site.py new file mode 100644 index 0000000..f120405 --- /dev/null +++ b/src/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/apps.py b/src/djangoblog/apps.py new file mode 100644 index 0000000..d29e318 --- /dev/null +++ b/src/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/blog_signals.py b/src/djangoblog/blog_signals.py new file mode 100644 index 0000000..393f441 --- /dev/null +++ b/src/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/elasticsearch_backend.py b/src/djangoblog/elasticsearch_backend.py new file mode 100644 index 0000000..4afe498 --- /dev/null +++ b/src/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/feeds.py b/src/djangoblog/feeds.py new file mode 100644 index 0000000..8c4e851 --- /dev/null +++ b/src/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/logentryadmin.py b/src/djangoblog/logentryadmin.py new file mode 100644 index 0000000..2f6a535 --- /dev/null +++ b/src/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/plugin_manage/base_plugin.py b/src/djangoblog/plugin_manage/base_plugin.py new file mode 100644 index 0000000..2b4be5c --- /dev/null +++ b/src/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/plugin_manage/hook_constants.py b/src/djangoblog/plugin_manage/hook_constants.py new file mode 100644 index 0000000..6685b7c --- /dev/null +++ b/src/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/plugin_manage/hooks.py b/src/djangoblog/plugin_manage/hooks.py new file mode 100644 index 0000000..d712540 --- /dev/null +++ b/src/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/plugin_manage/loader.py b/src/djangoblog/plugin_manage/loader.py new file mode 100644 index 0000000..12e824b --- /dev/null +++ b/src/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/settings.py b/src/djangoblog/settings.py new file mode 100644 index 0000000..b2b2838 --- /dev/null +++ b/src/djangoblog/settings.py @@ -0,0 +1,337 @@ +""" +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.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +# 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/sitemap.py b/src/djangoblog/sitemap.py new file mode 100644 index 0000000..8b7d446 --- /dev/null +++ b/src/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/spider_notify.py b/src/djangoblog/spider_notify.py new file mode 100644 index 0000000..7b909e9 --- /dev/null +++ b/src/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/tests.py b/src/djangoblog/tests.py new file mode 100644 index 0000000..01237d9 --- /dev/null +++ b/src/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/urls.py b/src/djangoblog/urls.py new file mode 100644 index 0000000..4aae58a --- /dev/null +++ b/src/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/utils.py b/src/djangoblog/utils.py new file mode 100644 index 0000000..57f63dc --- /dev/null +++ b/src/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/whoosh_cn_backend.py b/src/djangoblog/whoosh_cn_backend.py new file mode 100644 index 0000000..04e3f7f --- /dev/null +++ b/src/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/wsgi.py b/src/djangoblog/wsgi.py new file mode 100644 index 0000000..2295efd --- /dev/null +++ b/src/djangoblog/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for djangoblog project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") + +application = get_wsgi_application() diff --git a/src/docs/README-en.md b/src/docs/README-en.md new file mode 100644 index 0000000..37ea069 --- /dev/null +++ b/src/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/docs/config-en.md b/src/docs/config-en.md new file mode 100644 index 0000000..b877efb --- /dev/null +++ b/src/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/docs/config.md b/src/docs/config.md new file mode 100644 index 0000000..24673a3 --- /dev/null +++ b/src/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/docs/docker-en.md b/src/docs/docker-en.md new file mode 100644 index 0000000..8d5d59e --- /dev/null +++ b/src/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/docs/docker.md b/src/docs/docker.md new file mode 100644 index 0000000..e7c255a --- /dev/null +++ b/src/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/docs/es.md b/src/docs/es.md new file mode 100644 index 0000000..97226c5 --- /dev/null +++ b/src/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/docs/imgs/alipay.jpg b/src/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/docs/imgs/pycharm_logo.png b/src/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/docs/k8s-en.md b/src/docs/k8s-en.md new file mode 100644 index 0000000..20e9527 --- /dev/null +++ b/src/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/docs/k8s.md b/src/docs/k8s.md new file mode 100644 index 0000000..9da3c28 --- /dev/null +++ b/src/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/locale/en/LC_MESSAGES/django.mo b/src/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/locale/en/LC_MESSAGES/django.po b/src/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..c80b30a --- /dev/null +++ b/src/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/locale/zh_Hans/LC_MESSAGES/django.mo b/src/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/locale/zh_Hant/LC_MESSAGES/django.mo b/src/locale/zh_Hant/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..fe2ea17dc2636742b9c3b8d4eddb6293ee3b7290 GIT binary patch literal 10268 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 literal 0 HcmV?d00001 diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.po b/src/locale/zh_Hant/LC_MESSAGES/django.po new file mode 100644 index 0000000..a2920ce --- /dev/null +++ b/src/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/manage.py b/src/manage.py new file mode 100644 index 0000000..919ba74 --- /dev/null +++ b/src/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/myenv/Scripts/Activate.ps1 b/src/myenv/Scripts/Activate.ps1 new file mode 100644 index 0000000..5e49b7e --- /dev/null +++ b/src/myenv/Scripts/Activate.ps1 @@ -0,0 +1,502 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" + +# SIG # Begin signature block +# MIIvIwYJKoZIhvcNAQcCoIIvFDCCLxACAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk +# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h +# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK +# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV +# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z +# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ +# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 +# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z +# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ +# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s +# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL +# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb +# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 +# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c +# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx +# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 +# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL +# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud +# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf +# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk +# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS +# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK +# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB +# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp +# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg +# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri +# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 +# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 +# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 +# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H +# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G +# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ +# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 +# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla +# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE +# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz +# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C +# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce +# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da +# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T +# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA +# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh +# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM +# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z +# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 +# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY +# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP +# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T +# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD +# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG +# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY +# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj +# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV +# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU +# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN +# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry +# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL +# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf +# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh +# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh +# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV +# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j +# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH +# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC +# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l +# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW +# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA +# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE +# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz +# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw +# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x +# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ +# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7 +# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx +# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb +# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA +# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm +# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA +# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1 +# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc +# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w +# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B +# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj +# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E +# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM +# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp +# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI +# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v +# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex +# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v +# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF +# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6 +# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu +# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI +# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N +# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA +# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2 +# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O +# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd +# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50 +# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU +# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq +# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs +# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa +# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa +# tjCCGrICAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu +# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT +# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl +# AwQCAQUAoIHKMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC +# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62 +# o7E75R0LxsW5OtyYiFESQckLhjBeBgorBgEEAYI3AgEMMVAwTqBIgEYAQgB1AGkA +# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAwAC4AMQAxAF8AMgAwADIA +# MwAwADQAMAA1AC4AMAAxoQKAADANBgkqhkiG9w0BAQEFAASCAgAGUpnYl5pjPDC8 +# uJclKp0WgZwr0W3huu2nUQgdQt24qZVmblWWESswIiqJ5FC7YnGxQ6AA57xsPKgz +# GHAIoJw7ETPQjC1IonI4yvI+/8Aw+RZ7m3eDaKCk/Wbs3as7AFaCoPrjxusZGO4y +# VGY0K5zx9Pi17AepkEA+nteZlNbWRNprY1BdQep4fUVykS7+KoqmI8eiGpJe4mtD +# SlXvap7Dqz3OSBJRyb4DecJeBvBflMdCuC+mjW7wskHm8B1oCjtKgnIzETXJOe9N +# Sw98CEHVWOBDqJyMG0jOs3V5hn0li/+esIfsAEl6xDoO+9GRlQKlZHOTDYf0uJaH +# NCqLuSgpHPz0zSWPQkp1GladJxRWUHaxi7NYznMHblCDH2p8pF1ibpbKvxaxMGX8 +# 0j+vAK/pzUK0HfZaY79scZn6q/kwQWjahFT32onbVH48QFTYUMBKfg1zjnQZtTnU +# Clv+Chk75xkPiyOVyd6frpK8I2jfPkXjSdIkRWGqaOkHcVrhKae8zPH+49Q+UDIX +# wjMmCuIarJzFtqh+Iu6eSlj/72q7/C2bwb0r+HkdaU3dRzxvYOqyQ6g0Cn4g+twh +# VTFKywiUiW6muz5HP7pJ9v3WUU+hpFx5WWb2MYQEO/Qh53iYGmLaT+8OvCuXM8Hm +# gmFbKlK7BtSHpVCOyiYW54YizjVvBaGCFz0wghc5BgorBgEEAYI3AwMBMYIXKTCC +# FyUGCSqGSIb3DQEHAqCCFxYwghcSAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZI +# hvcNAQkQAQSgaARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCBI +# 1dbHE57ZZcjKKZByi4HxJFntDaj547aEW4zgjY+zlQIQOybzqjbuRhUI00KoSULR +# UBgPMjAyMzA0MDUwMDQ1NDdaoIITBzCCBsAwggSooAMCAQICEAxNaXJLlPo8Kko9 +# KQeAPVowDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp +# Z2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQw +# OTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yMjA5MjEwMDAwMDBaFw0zMzEx +# MjEyMzU5NTlaMEYxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEkMCIG +# A1UEAxMbRGlnaUNlcnQgVGltZXN0YW1wIDIwMjIgLSAyMIICIjANBgkqhkiG9w0B +# AQEFAAOCAg8AMIICCgKCAgEAz+ylJjrGqfJru43BDZrboegUhXQzGias0BxVHh42 +# bbySVQxh9J0Jdz0Vlggva2Sk/QaDFteRkjgcMQKW+3KxlzpVrzPsYYrppijbkGNc +# vYlT4DotjIdCriak5Lt4eLl6FuFWxsC6ZFO7KhbnUEi7iGkMiMbxvuAvfTuxylON +# QIMe58tySSgeTIAehVbnhe3yYbyqOgd99qtu5Wbd4lz1L+2N1E2VhGjjgMtqedHS +# EJFGKes+JvK0jM1MuWbIu6pQOA3ljJRdGVq/9XtAbm8WqJqclUeGhXk+DF5mjBoK +# JL6cqtKctvdPbnjEKD+jHA9QBje6CNk1prUe2nhYHTno+EyREJZ+TeHdwq2lfvgt +# Gx/sK0YYoxn2Off1wU9xLokDEaJLu5i/+k/kezbvBkTkVf826uV8MefzwlLE5hZ7 +# Wn6lJXPbwGqZIS1j5Vn1TS+QHye30qsU5Thmh1EIa/tTQznQZPpWz+D0CuYUbWR4 +# u5j9lMNzIfMvwi4g14Gs0/EH1OG92V1LbjGUKYvmQaRllMBY5eUuKZCmt2Fk+tkg +# bBhRYLqmgQ8JJVPxvzvpqwcOagc5YhnJ1oV/E9mNec9ixezhe7nMZxMHmsF47caI +# yLBuMnnHC1mDjcbu9Sx8e47LZInxscS451NeX1XSfRkpWQNO+l3qRXMchH7XzuLU +# OncCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG +# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG +# SAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4E +# FgQUYore0GH8jzEU7ZcLzT0qlBTfUpwwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDov +# L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1 +# NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUH +# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDov +# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNI +# QTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAVaoqGvNG +# 83hXNzD8deNP1oUj8fz5lTmbJeb3coqYw3fUZPwV+zbCSVEseIhjVQlGOQD8adTK +# myn7oz/AyQCbEx2wmIncePLNfIXNU52vYuJhZqMUKkWHSphCK1D8G7WeCDAJ+uQt +# 1wmJefkJ5ojOfRu4aqKbwVNgCeijuJ3XrR8cuOyYQfD2DoD75P/fnRCn6wC6X0qP +# GjpStOq/CUkVNTZZmg9U0rIbf35eCa12VIp0bcrSBWcrduv/mLImlTgZiEQU5QpZ +# omvnIj5EIdI/HMCb7XxIstiSDJFPPGaUr10CU+ue4p7k0x+GAWScAMLpWnR1DT3h +# eYi/HAGXyRkjgNc2Wl+WFrFjDMZGQDvOXTXUWT5Dmhiuw8nLw/ubE19qtcfg8wXD +# Wd8nYiveQclTuf80EGf2JjKYe/5cQpSBlIKdrAqLxksVStOYkEVgM4DgI974A6T2 +# RUflzrgDQkfoQTZxd639ouiXdE4u2h4djFrIHprVwvDGIqhPm73YHJpRxC+a9l+n +# J5e6li6FV8Bg53hWf2rvwpWaSxECyIKcyRoFfLpxtU56mWz06J7UWpjIn7+Nuxhc +# Q/XQKujiYu54BNu90ftbCqhwfvCXhHjjCANdRyxjqCU4lwHSPzra5eX25pvcfizM +# /xdMTQCi2NYBDriL7ubgclWJLCcZYfZ3AYwwggauMIIElqADAgECAhAHNje3JFR8 +# 2Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK +# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV +# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0z +# NzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg +# SW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1 +# NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI +# 82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9 +# xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ +# 3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5Emfv +# DqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDET +# qVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHe +# IhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jo +# n7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ +# 9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/T +# Xkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJg +# o1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkw +# EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+e +# yG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQD +# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEF +# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRw +# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy +# dDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln +# aUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg +# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGw +# GC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0 +# MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1D +# X+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw +# 1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY +# +/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0I +# SQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr +# 5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7y +# Rp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDop +# hrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/ +# AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMO +# Hds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkq +# hkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j +# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB +# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5 +# WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +# ExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJv +# b3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1K +# PDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2r +# snnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C +# 8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBf +# sXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +# QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8 +# rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaY +# dj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+ +# wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw +# ++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+N +# P8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7F +# wI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUw +# AwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAU +# Reuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEB +# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG +# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1 +# cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp +# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAow +# CDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/ +# Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLe +# JLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE +# 1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9Hda +# XFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbO +# byMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYID +# djCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu +# Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg +# VGltZVN0YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFlAwQCAQUA +# oIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcN +# MjMwNDA1MDA0NTQ3WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTzhyJNhjOCkjWp +# lLy9j5bp/hx8czAvBgkqhkiG9w0BCQQxIgQgUjSjrzWa1N9tY3HG2o0Php0YCn7i +# UqqdaCMru/DoqI4wNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgx/ThvjIoiSCr4iY6 +# vhrE/E/meBwtZNBMgHVXoCO1tvowDQYJKoZIhvcNAQEBBQAEggIARWFWKOxm+FsN +# OV+ONMrWYC+repZLFGKHc5n3dC+cu+FoAsMy561MGvEBnittRqdypXAfKaZ3Ccj4 +# 82B9mWiPNcm/LzEGj2MF2hCS/SlN+g/h9JPDOVZtXcXsnH9lalQZzJLCOdEpCdKl +# NtEYQhVw48quqNSqm55liXFPZv5atRCLq0yO7CEgGTpK6PdmEZzAavzFLtQnvDJj +# JerOZ5NW99tNaYqkJh/Q7rpB7E1UXJjFWwegaMGR4DqHqySB6RAIlNf5HaCT+3KO +# ICGKrNS3wL9WtBYlLIIEm2//Fo3m2CPfp6D3bzDw4Gjb6+BZZBX/jc++OHFLkTEp +# hB9Z1SyLC3TJa3x+ze7p84q/eYs1xqjRIoy3mkQ9gAndWCktfaOp1wAwP4oySENY +# 0Ztionj+H/iydIQNKscWZ95uj/ZTm79OW67X2hLmGOv0ukNck+FE7tHN8I4Lh6VX +# TvjYh8p2SbGHd5v60wqYgrBm5k/r9cacjaptbfl0iP4lY4jqYKnpD3gAvegh5tA4 +# xCmikLbNT99M080eLf2ES/UGqF8THAfLHZXlrFFcJQ3WvwgoiRCTv2ifVlxUXwxB +# lMOfJY3zIEnrxag0ZMJciZX21rKW4ZFoU50q7Nd9+T830tfjwaJWfSNE9Sisr4id +# UvBU/gsB/5d1HPBlxQfXvxm/TMUDeT4= +# SIG # End signature block diff --git a/src/myenv/Scripts/activate b/src/myenv/Scripts/activate new file mode 100644 index 0000000..6359cec --- /dev/null +++ b/src/myenv/Scripts/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="C:\Users\ITkan\DjangoBlog\myenv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/Scripts:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(myenv) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(myenv) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/src/myenv/Scripts/activate.bat b/src/myenv/Scripts/activate.bat new file mode 100644 index 0000000..0b54583 --- /dev/null +++ b/src/myenv/Scripts/activate.bat @@ -0,0 +1,34 @@ +@echo off + +rem This file is UTF-8 encoded, so we need to update the current code page while executing it +for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do ( + set _OLD_CODEPAGE=%%a +) +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" 65001 > nul +) + +set VIRTUAL_ENV=C:\Users\ITkan\DjangoBlog\myenv + +if not defined PROMPT set PROMPT=$P$G + +if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% +if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% + +set _OLD_VIRTUAL_PROMPT=%PROMPT% +set PROMPT=(myenv) %PROMPT% + +if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% +set PYTHONHOME= + +if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% +if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% + +set PATH=%VIRTUAL_ENV%\Scripts;%PATH% +set VIRTUAL_ENV_PROMPT=(myenv) + +:END +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul + set _OLD_CODEPAGE= +) diff --git a/src/myenv/Scripts/deactivate.bat b/src/myenv/Scripts/deactivate.bat new file mode 100644 index 0000000..62a39a7 --- /dev/null +++ b/src/myenv/Scripts/deactivate.bat @@ -0,0 +1,22 @@ +@echo off + +if defined _OLD_VIRTUAL_PROMPT ( + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" +) +set _OLD_VIRTUAL_PROMPT= + +if defined _OLD_VIRTUAL_PYTHONHOME ( + set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" + set _OLD_VIRTUAL_PYTHONHOME= +) + +if defined _OLD_VIRTUAL_PATH ( + set "PATH=%_OLD_VIRTUAL_PATH%" +) + +set _OLD_VIRTUAL_PATH= + +set VIRTUAL_ENV= +set VIRTUAL_ENV_PROMPT= + +:END diff --git a/src/myenv/Scripts/pip.exe b/src/myenv/Scripts/pip.exe new file mode 100644 index 0000000000000000000000000000000000000000..c79d48f2aa2f67bbc8b3c8d66bcb8aac0657579c GIT binary patch literal 108409 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 literal 0 HcmV?d00001 diff --git a/src/myenv/Scripts/pip3.10.exe b/src/myenv/Scripts/pip3.10.exe new file mode 100644 index 0000000000000000000000000000000000000000..c79d48f2aa2f67bbc8b3c8d66bcb8aac0657579c GIT binary patch literal 108409 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 literal 0 HcmV?d00001 diff --git a/src/myenv/Scripts/pip3.exe b/src/myenv/Scripts/pip3.exe new file mode 100644 index 0000000000000000000000000000000000000000..c79d48f2aa2f67bbc8b3c8d66bcb8aac0657579c GIT binary patch literal 108409 zcmeFadw5jU)%ZWjWXKQ_P7p@IO-Bic#!G0tBo5RJ%;*`JC{}2xf}+8Qib}(bU_}i* zNt@v~ed)#4zP;$%+PC)dzP-K@u*HN(5-vi(8(ykWyqs}B0W}HN^ZTrQW|Da6`@GNh z?;nrOIeVXdS$plZ*IsMwwRUQ*Tjz4ST&_I+w{4fJg{Suk zDk#k~{i~yk?|JX1Bd28lkG=4tDesa#KJ3?1I@I&=Dc@7ibyGgz`N6)QPkD>ydq35t zw5a^YGUb1mdHz5>zj9mcQfc#FjbLurNVL)nYxs88p%GSZYD=wU2mVCNzLw{@99Q)S$;kf8bu9yca(9kvVm9ml^vrR!I-q`G>GNZ^tcvmFj1Tw`fDZD% z5W|pvewS(+{hSy`MGklppb3cC_!< z@h|$MW%{fb(kD6pOP~L^oj#w3zJ~Vs2kG-#R!FALiJ3n2#KKaqo`{tee@!>``%TYZ zAvWDSs+)%@UX7YtqsdvvwN2d-bF206snTti-qaeKWO__hZf7u%6VXC1N9?vp8HGbt z$J5=q87r;S&34^f$e4|1{5Q7m80e=&PpmHW&kxQE&JTVy_%+?!PrubsGZjsG&H_mA zQ+};HYAVAOZ$}fiR9ee5mn&%QXlmtKAw{$wwpraLZCf`f17340_E;ehEotl68O}?z z_Fyo%={Uuj?4YI}4_CCBFIkf)7FE?&m*#BB1OGwurHJ`#$n3Cu6PQBtS>5cm-c_yd zm7$&vBt6p082K;-_NUj{k+KuI`&jBbOy5(mhdgt;_4`wte(4luajXgG4i5JF>$9DH zLuPx#d`UNVTE7`D<#$S>tLTmKF}kZpFmlFe?$sV{v-Y20jP$OX&jnkAUs(V7XVtyb zD?14U)*?`&hGB*eDs)t|y2JbRvVO)oJ=15@?4VCZW>wIq(@~Mrk@WIydI@Ul!>+o3 z=M=Kzo*MI=be*)8{ISB{9>(!J__N-a=8R&n#W%-gTYRcuDCpB^^s3~-GP@@5&-(G& zdQS_V>w;D8SV2wM8)U9HoOaik`_z>Ep^Rpe3rnjb<}(rV`tpdmg4g@>h`BF#WAKLH zqTs?sEDwi<=6_WPwY&oS9!h@ge4(br)-Q{|OY*#YAspuHyx;~|kASS3FIH@oGSl?L zvQoe8yKukD)zqprHiFKlW%;G=hwx4l;FI%8m&(#zU|j&_bW@ThNpr9D0V}xa)%aIb zI$i2CA2mPU{0nJmK0dxe)dY-`z>ln($ z;r!UXuLDDi42|Zd3Erx&m8GqlFWbIX0V<*Gn6lVNq%gD>gw}da}r}ZQB~ns?p8uy4i0%1Ti$Vt|~OUth4=+yEmPu8{3(w zUDkd@?w?`_J9HBkx&ZF8v{+9phcT@3J8VI~wN7Ez)oJS6^dhb2N;;{RTXB`K*E$64 z3rDqRtY&&*}9yq2oUcvD7K)=@bWqC1X%l0jk)W<5-WBYC(#rn4H5)gp#eHMmwlLJq=^%|*gMQ*pq4VV(QhHA4CGj<;!d8i*#Z8CaN#*>VcCnj~;kkeUa{LUoKxFCaoQ) z(Lz++&x3Lwz;=6UnhwM!MvN17>{Qmb?dwgsTmzkLB~jD#wiGz73hc0bFE|C9KA#|= zH}%FQ>c&Y5z*TJD-<$$Y*WZx>5NNe-E-TfAt1!)%Wc@I;ZuNwxDGGasDIMyUNiVvG zq;Q70PYHcLO=Xgv2698@cJrkun-^>P2}|fMHlm7xaZmE<{&cQtb`{N9zj0bRmpW^T zzQV7oTs0ENHe&mxQ6DI7qd0SU4;3o*2qRd`X1>(=ew})X5Dx zx$lyzZM^emtdsbk^u+xwdSX$lp7h*2CkHCqDohShL)V4hM9k+UQLP(GN-H7!C8gyq zex`xuPQ(!g4}S>0r+CyH+xIAMP9Z&+?BT1!*kA<}dqRn*FwJPGe}l-sw(lGYN1b8} zWQQjQN`9tdtF?#aqMN?wu4E3)qGxzOhwr*vb;kX_%&U*-=KLr0raiGc^x8|=Wqt`N z?L0luR(~BF;DS@~yKDN7|*TJkj*-B%s1{65$`jY_(C#P&^rVi0?Ro4iaFbR)Z2NLxS0 zTL;%Kt22(A8JiL`U$i!iR&zLxx^E%H=*c-=+h@sisygu-_#m4J4LQqB?~vXvP4@yQo0-^oki(PiH+=FZl}&W)S-qI zk>W;2Zl-vl6rbe4X6feZb)l-Mv2oh^5t8q5@(Y-SPoUZ;N<5Tdl!h|=x!1}5)E;}=RcAXJ8(<$^13IV==^rU>wwq$hX3V4iuA0>h< zuxK^)myr=p7a)oeZ+g4u^9(OmpFl8J@{{UJfy=DjAf8lTTD00iSF3Kb9|GdM-PQp)0<* zZkW*V-TPpIXEKDks>&FQ?qoV&Tfa*;TJyB^yJa8xcch+*-cYj6E7HdBX!5)TIXSNM z4C2L57KVd0rioelfI{ELMrb&Y}?h%mk5iSTXrmJ zwlk6qsS{}3<}Uc!G}Wr;Tek1Tym8$SrWokvCzU(FVIAWTEa1pwE zBJ6JdS@$4RFBV*~g^Eo9MAFafx2rt|uRsR%xpNVyj8!g>2u0v=>eO zS~4nHBgR%cVxB-_OwP@%JN(CpY3qHvqsbt-TUGivY2Dr$b+=`6PJSkbWF)!Jn=iZJ zMt}mOG~-m{)L*SV+yRH!c@XR%)K^BqVRh zq&wib)2#d0V3BD*|F5o2J6$vbdJGh`O-30SrMI;e*Y&m8c0Bi^cD-$Daq1haK*i4o zS^0dLE!U;Du-W5i&*6##L30bjy7q7@lQPyCc8<%{>0)|vQlrFG_D_+v^1uh+p+bhA?!)dFEqi$(hoT?=hJt20DQXmOiJ``9LY)@=HE zO1esvSjV70vmITir9t{Om5D&<%?UTa#`5Sp-x@^?6JCK@(Y_-+ye_agHcB_zSUEYe zay}#@o~N5_?G>%q2t<~g3s!Y+G*Mj=P3Zn>mA2=HCm`lzap|)*f|(31R{)36WvAyz zfea$wK&B|2YxO{n>twI{fk3f0YVK4T;XDy#cUe=*$V6#=30zz**pkdJOUUdHcyGKx z={=%tU83}-sM&@LFz=EaBy8m5*VS4ZYhB<>lI{BnIk4cD&H_E|%!spiL(( z$1W0V$;KX^P(?<}XYHqoplpQo7H>!m)d{bdPaLde+h7(tf+ZB(6MxWZnoX6&>|)(q z*DB~wjMmL&u~F-ZIbJ>BJ5ZM6ik)gUbdlBM`Quqove#M~lf*ebB4nBg}NN8q8e!? zVj>HOMJZ@LQzOdvHUSih8gCt%IxvyHLmO^Ea(*!Nd-Zuw>`f87{SkAwbrcIp6hiff zt7^x@FVoBVwDl9eTxT2$))(-5-O9W=qunp;*yvYT{VJ=~FI-x;pN&=5ArA%W0()Z} z=?f87g#Y@j2_ct@T|gzY^?R)mq?NdksZ}7gJW^{18>hCuy{s)%iDWGzC?-DRKLl?l zlnO5zQf3*!v6nJ;)xm`Sjm!6zf=o%-07p#e5?cL}gBtB`Nq!dTtt@<7#(o8m8xm*XOvN65AL(=C_D} zJM9UyYteSSwriu8{DkKl6tSk&09e8kMrjh@N|SS;@9l|6^W@_Q=i{`@$NUzI6|VF> zN{Rev95oVSa&%)ew#+uKZf{3cFg?f64ASokLt$^COgO2#BW71L>H7~o2Zg;=Z|nCM zZ=N18^ET^uY+VpF$K*teqc&2xaTF!LhIKrwGne_WBX+B_9vi@rt2GKHy|kQxSUJ18@{fEswY{>va~$3%JGyYfr29k%@bck16c zdf9Hh?|r@PC`@3R-j=#7868z@m3)O|u0`Iw|bd&(6~U$UMGD@Vncn>Lm}{NqU9US&{gYu`~lU+m1n zi1g$#vC1#v|9B;ObTzhRor!#90$^5b(Gy`buihHrRfjV>-l^6#?Dg3lZ}@PRD|I(> zVcp1Kiyr8xABHMWk$xp&hFzvUhIKbDi1339ve8Ac5ON73NDM}^^I8O?+8zk+GVA0S zG|7G=o9JQQO;-x!z=zz5c@^<{-AWi)tG`b65v40t#CwnzKA}>?+z|q4`eNlNfRXZK%L4$WHQ)8Sgo0 zwE~@9)+4fUIf8fW?9TihJ6Hgttrta)MqB{FTBqxu|CDLzEKWn{Cn*>&wx$DtvzSvC z(4Jr-g8~qe!NL-;BVhBlx}Y;!It5;VT~^q_HdZcH!a^(MA3%zpy!zmpD(NfkvF=9= z6p^lmDSFnrRVn4npverH%%I5(CT}SgTNGB)0sCY%@`7%@lG#4Gt*2;3c3;0E8(QyS zoo-l-h2)DEIh-3t!@^Gefe~>Aq|Sbf{goW=Op7FDAB-5amdpAhatG_BQh1V>p|DF2 zoM~XblmiX(kl0U_veatKBQ+uz9@Z1{N|y`0j<11Sd^JtI@w2S`$mW?%;MWLc4%=HL zi!p2d7Nf9k{=Kw;xt19k$vh+UMEX9C2D?jRP0wn3ihvj zIKqjR_QyB+t|%#l=^@PkY$HlM{<4z$Jve9n{#ZUhYv#%_q#uJnen z7S7e0{d|oCJ_u>EJ_(yUqk*m3cisoGsENRi9?F=l*A~&-*(<$4vm*-sUaFT_dJdnX zrOQM7ERMPl>SbN2|4`NV9yZ$|0jqv#7_|5qM&SK>FdA$Qn}>sahte?IEg|!hNZ-Lw z+2M47yawJ6YgZhmd7`)o7cpN%77HvCf^&@h2FBhy;L2rI>K+Cp6&?pq zlFhyiSR(126>L@rL1c*79q1?uBeI5<%2ZP3K!*8bJ8n5Vkdy&9Re{a#rI- z6fv$Y@#|&(1pg>!eIKW$IeEqD_akO!YCNey`?q5Uh$a^MgG!T#n1>V}I*O@Oh-I-5 z%k{Du%Iw6?)MXzjh?<)@`1%M|Z2fN100q^u)YBKp;(8NX!a7BpNWL}bB60|{!@3IM z&!_-j!}^5^fVs3)8n2d}7M6&L95t6HGcO7O>k8tJiY2gy{mtC0V*s z;mM4hWAvYlP0?$+)i!p-gT`AH%yAiSovz=pXFBCU*-y1#y_wmwf!PgMrEDEyp_Y+h-3$ZW$Ny$8H)g+M&odOm3D+qCuDCyTVF4s8_v zmEyLRLz)cEXCoqszT`H8*!|T3k)9}efv(zxR?xmMPtJ#z>B&Eo77PE!jE`0XJbxM^ zJEbz?Lu5g--#l!-Y#gzXP3G6p>XOps?99>9SjC=T%MY0{>#J9bVPGK(CmAlr@LDVu zdtE8Cwy$lsu#8`O8L={lK%5}c`pb6GjOmh$5gX((WMNF8jU#kU?6HQLb+0+w?hE$3nE@wxIvFA6~zB7QMVyoEeHQuBH-S!>tRw89F zyIi51ALX;4mfyl>Gbw7NUa`Y^`9s-NepV{j;n;E-$Ceyj?qimR?nQpJ7Zt@YCfL5$ zX%(74|FeDDa8Ol;N-078H81eqW|LX(_9$cc`%a*!#=7{V2=)|lNG5a40)v6g4t z01XUUv68UZ2|@vkl?ceW7{YVw!nCy? z+sAnJ?mvd`Ab`J#GpRgV_N#doE}<~&Z?VHb%c3L;ua)NW2qzfhmeh>}dH zGKiE|U&0iVSyyQ$NO;+GkhAqI3{1v-UXl6k&ogShm<+H}bDWf8ZLbv`!7=F`^V*WW z%|fH`g0dA}vmj?dt{;}&QQW)P9h)H{A4EQ&PP7V>>J53l4KOcs^mIW( zWkEdG-lC&N1l;w9;87FIEh#42)wpNXA?u;BStwK2f%x9dIa=c%`6v*^^D7Rdeo3P2 zK9dB;uN>7oyTltCA%$60W`E3W-dBpg zuqcq@x{}^i&v~(2yR)n>8M=s-@@eAy%xR>v4&Y%h*z7^|kj=+ut-*SgnXpUQ2Za%i zw_32)!m77h`9S6v$7W)#c5Gu%xh%>rSYMFAD@|Kh-5MzR0ebF=8}-^F_#pg>cMe^Q z_fFTrqJD?X&Jg+pQE^7T9S;~YZ`N{LIq@lM=%?CSV`D_iRT3c{J=yaikxU5%rHT=TI9ln9_p;9*QY6sX)@dJei;QU6QC|w1dx9PPU z-k*1jcMjN$eZXl0=c@we30H5Z#G4Zf18#{O`?4|fubhbI#LpT6?u0J@S5*J&gl|g| zx>4w6bp!F}L5Qb)5yTF=Q~b_2auNe$u2af-1--x-Y8ugJ)$~A7xqyDQUb~z9yjp?2 zS$2CCh3xpcnb+1EDhBdlycVY?TH-GQhOBi1Em;xS%mih!zz5d%5ZTK)kgI(;YVM1) z9Y?6R=*3Ee3NQqA=9m}0tBfPY>WV^F{KDkb!>u=FvBx{<@$4HF#Ty?(D_|c16@7ar z?3sMj4pkIxD3B@pYY^(UW7-_E@LkG|E4F$T>^}02mQUF3kyHzn_+N+p{xB`ffEMeA9vW5-D%{ zZltI*4Xan_uaQoJoSn85x~zjwdZGe`c|L&8DFe`!Uzz7`w0>!xulJ>+=37i-p5mR> zWl?vJ+1b|P3AuYhVyI7#LAPEYZ87i$tRpmE}@el^F1lN0erixJ1-N#3v0fp0!puf z11^VLsS9qh<=8A zl(KovC21r`^>K0LV;-uDR<&qv-K@mIx|7<^+mo|TDsK^_F=k^064`x9BFi|CeU^vI zA`v->wGlB>5s}S`2Vld*+LS4GWdW#Z9=Ld+EhF-ng5iU)X7A68`i# zO|AEyO~DJK*d*(2vK_TGJ;J(KCFF$1nt-h(v%kz8V%#2jMxD`gWt|!-@k5${77Q@!{4z;ze=7&BScC z{l96Ke7GeU{#P5P(1-)>pb!x>_limI(??L33;=E&UU`S^Xg(o6V~Xzp2+b869oyFB~+oK91m(zDG}-Ce|yro;clXhx0fm zqA!a1;w8|CgOIS{tHtHPM)Qnv&@IQrVjZ>Cz6}8;hEX6s#`+#jXAT>_&8rE)U3h@u(3Rj2wHPF8HLr_+u|u2h!@v|soMqnSEk8Zd`9UErc zRN_h>v@U-yBXM8Ej^Rk$+sR6^P!=M|4(TT&#@8NU-8`?Hjo1~wjxi#DFXslCbHj#H zR5!NB>1Vtka3nsdw|a3-Y^?Qbif>?ajCQZ}h|~?V$4;Z2hvePt!VjWV5kP_Mdzd#2 z(Ya9OE~}OG95vq%MZN6^iVy-|(zl&p4c#oK!g~#g9ul0wCtz5||XBmlcb|@y+~5^oMA2 z%2&t|Z30b#v!su;P0>oP@n%l!68gTFk*t&4-cTiC(g?CTh0XM*M_NA`XrI~P!(S-N zL`<-L&IbV?K2X3qpYwnLW)JqoQsvmwRaiiIOAWlUuFCW7CR}XuDqc-j>a`x<)1Wa~ zw1+(1-L|GuLWkn}HjH3W>Zkjq4e-!WA;hn0iSIXW`S*t~{JgUpYShtg%LoE=slzv~<=K*WA*ElMAxu<+e5ER>PXppG$|uZeA(Temu%&q(p;3AFN2!kq zm=?vfxfpqDEN!LF)Xm0H1wg{HMEXo-l13}ryyuWqH$7J>Xgp69ORBMSo%EOR{GE@T zp6`=69Ftb3=ONylwdwgfFVgK&D$mcnFSmVb{~?FB$0_H`z~O7eOlSLUCm#&_o;kIB z^GO&pU!)Lg-zm3^a<;FL4;!T`wb1X9I%}R0*ioufT+j91NaBu?NMeOwVtj_4-Bj0@ z_j+s0>1Gh!;oi!cvc4Mg&8Yc4=Cmj3w59_z5~=-$9!bpUA~dL*qwByWnz05DbT{~4 z*jZ@K?vDlzYTtT-qUP-5@^1W$cjLZ1m)7`wc?;yk#>sw)Ni$-;5OH_f-AMb*3BElL zTXVmwcEz1Nab&8Q-#V9uW2Z6VdwH||2KhpVBR4w8!{_^EvduYpj=@m1wadC|nCyj2 zt$A%;w3fp&nPJJ87ID86l?_lyq<-5M`#ZFGH^n*bFxrb{B4*!>glHD=IX zaR4E?rmXV`e=Jb3r)umy9O_=}HG_<;wLag>;c-u)&Cx(xabWC&VP!^jmFM&Ib z$EM)|j1Ueju0pu}b54-q=pis$~y&T*+xHtN5ij^Dv z^%7mNlKsbrMJuxz??mDQn__!^I>*gYDhiq>gCh>6y-yP!!np!os_nT!v)geY)f(H$ zMdxVz82saUVjQ{l!Fyx32g`P8jl0P*QX^tlU_Sb?kt&IuWuyvXIfW6 zvj(<2h5p+D2H`EwSwH=TECv*ISR}=U4K0jI?@X;}rSnDnja37_hg1U|)xdV^hSx;N zR_l)tW>JcPb8F@5C~uO{c@SQX_Wc-vx12+X_zdyQjX9DVg;djzhq7W0o z))<;YTY1Kqwi$lJ9G%8d#&=Y2g-5J9EDiLvQu;DVkGayNG;o{qwO{JmzR6Uh$UG@x zPCO=Jtf)bg*6_lp#3+w^Tg=a7c|p*fGtm(jE${gPmO7HD77SR?ytQ3_Bxr`(@-qAT zWfSOxaSdnVed(w}=&i-FC`!Pi=?<=yrTgx#ws#DU@R`1IyXR+k0R7~IY6mXQnIYJ=|Dqf4+{O?83Q*D35 zm~q?{FH`;v)-R{BFDCMi3*t-k>{7fQ)8nw?9TyWqG3`Ursw{KR7s%pMMe3iM)dT*M`1?|}%AZgc@ zX30+IPfbP!7X!AEjBUyvWF0|-nESBQh0Mtj(=rdU9mNVG#;RgmWP&-P(zBuAracc- zp+(j}^q7=iuyEi?+-C&NiI3TU^)U0@n#|Xx-UoNc*6NmU3HqR;Wl%dL zkIaY`kZ}eU*h+@_w{SA-$LNPRs?I`9&yRXRk~$gghBqUHqL4xmtMtVD2F!n`DBU&Y zA@L!Y3w6XoW)F{rN=O!R5%FX>|1Ypcy+BCeYqX6PttY}QV(d8A+D=AhCvAj2I9Ci+ zE_xz1LN~*Y8IN@_s1s-}DbcJjI5vpO#CDDjrv=T!AxN@1Y#t5bfti^9CyoyfXpL_T z2V8Sei{e7KzA*ct9Fu(Nld9;CL z?d=gOO0=h4Y+4Jb!Gh3(cScOi?2L8L!@ zXRz-XiI$JM!z1>gk%aITI}Ha2`#~+lD$VpAZrrCeDp|VeRi;hXLX+MU&wulyCi{V@ zp~_QZXJ}92zB_-Nbp#$k+W_m_M`OPZC+5?&W-o>zKXw6;Mw zPZVMo6>O;(y{(rJ))j>Jj--v{g0^&C9d>R#xu`p+I!;{+20Fvd@~tlHPH#Z}#D#80 zwJKsBYO=M&SD3rt(@+KWTkw{8Sk2`v+CyWht11NA9@xI&HVQx{ji8>XzDsLtBV)te zncQFSH2RmvZZP^+XpO58RW`&kpI(%5tDHnrJ71E)Kc>S>es<7(F(N@%94gfc zt}u%Qr8lQ*gBzd@RpP2l;SukoBN6k<1H@t7b$bS(TH|}1=7p2j`DH3Rgr=l(6PIL> zoLb8o5hMoHL6p-P+JoNWY5<8%Jy_)&dQZbMH@;n1k5gZVSDG59CRwN@mS3YieR+R+ zBAkSWPvs4(spUN{Y+l|!Sg;6&bFUYtQyI6H=HmrUtM0Jb+GO9GuVy+uB51tb7Yv*T zYFD3tL}TJ3oc#GNW=rR=aO>o4-~yYIy{l>KgSZEC^?)4Dv_{}AeTN7(PtHQSsCppR z-O&ueZ%;ojbgn0xqy?c1=D}`fMTVQ+(Hf7#GMidk%E4&NTj|ys)55Ur?JSdKcj|Q# z@lkkIq~gI09sUQhXE1Oi`1G%+0*FVX$zZ^K;H)*Biv-5nT~_VsJQLwR!63B8U?hW)?=-Hdlqq`a)%WG*cKqMfqu&U6`6B@bTa*hHb`MGTvKIJRjs3NL+*6oUu`f zPz-+a;yzVqgUnl|_Ft%7(MqVuf;hXE{lHCF2ZJV3dw8A0ZK9=1GTeu=CHDQBU?IYD zYb`v2rzovi+{2bQ@h4?87jd5uw$%IJMg@8LZ1vzM6o{&c7{V%n5d_#@0$C223kja0 zjv%e6ch#8!Yiyzet6(Ps>o6M6;8nan=LVmWkAUisOgL8(UDj`QAml+b0wtTWQz})) zSJ`rn{zz=D(Z4h{djmEwSX!(^ZPaMhTGKdHXyg77DUCNG*u3gne57pNGR1|dUZ|DD zUz|F?3wuqfM>2#Z)dh{pi{q#ASe1LBs*PR_05B!hk@A>Ki}d9}v5yvdfiOihrQ8wUSumgQPT z^#CeUufkXX@5DLrvx5#hRD)I=NS3K=5*W_V>qWl{rNnBGEPPs!nOv=RtGrjq3z|oz z%TQ`338%qxgAOAc(jbx<>pSsBsbK8L>)Xq6SeSZ@BwFdhWMPA9H$=OVZ%8pZ3SwOU zve7>|_N5K7hM2X<8_siH#wcItPcL%K1u0ta&UGs3R;U zDFUi^?@j0u_Vu&Ua)bjE8WCg%lxXp`R{m?P8%2g!!Sm&i8ysliZz-Pe)W~iKi$2@- z%_3*UuodHBQkRe`Gg%(oKyxZiY$9Kkf}%9HjO|Gs??vP=@Th3JlaO^YUi*R06`J)L zM<&jp6-PabbnTBvoEC@yMN~q%Hte32CG^+Hq!Y-3#Bck`o&Ye^n)8gAcjrS3G3;f# ztlv78_U$6c{iV}g2vq6cNn)6j5UD?NVll)n<{W@3DD~vmQD0afGzl}{o*aCRADki_ z=2bm;e{nE5XBgAp9!e}Kj3yT4)qV7PJvnnErUkw1#M->mWvgOe+8O_dh*2zSE)^88 zHm|BVM?!u%g)5yXB(SvQ%{h1(*lmIK`cKw|O268HNamNIhp(p3)}H)Y zPDp#QH5Ayq^3-4%J5cMD$!OkkaoPKe-}-JTT@VzuHovho{+xMvA)b$wYN|zTDK{_A z!=;ipwz8(>5Q?(SiryT8!!Lqar~p8UnO`j=uM&6I*a>7SB%*^ANS&jk`adDWz7Sx2zfof8}0FuZtes9;}u zB+1-Zal>$baBaxDuX&9iE1ln=o-T=^!RCgr5bsJ~CbW6gB=GQPFj?(4`p2#G(oAxe zKV8Tn{kWAQX$9i_OdFVjLG*L=sG>-tI9wRH1Q$&*H~5=?sf z00n0WnNK)qk3fD%dRC{TQE?y+baCD^r9)P~=SLLO6W>vFO;58*F`ox*%F>k6!x3eP zc{T1$&hc9d;0GDo(7-vRvd2`T@-mUcE?7|-H>ONK0Yq}-H>J~aChwpa{&C^2T`ni| zz*%QM45LVV0&)-tQ>Q{NTp92^7BAbrnT{X= z{9VAVs&sD53A%Sg-2258V;u3+r`FgO<8l;^HMYd#YmI#r=S~9KckScO`lDlr5YJ*H zTi?`7<`$KC)kJX=7tUgxcLwDBKwjd8!cf(cQor`?hg6AB>D0=FrBh?)RW8VhP1ByN z)SlFH0!LQ*%68G_C6fTCp&&2fem+vRBmRkKB$Xxc=k(;|r)@Y%0}Wnp#Qlu=W?q%I zCiOVHU(Drsu?a?sn+Gsw=b_S!Z^?s&q(`@$B9FqBJoJ#Xr)3nW#N~ydM4dP7PTb(t zlMfWb={ATW2Afk+3ssZm9Am&uE$q-@f_UMx1Dod;oX)$GpGoCu2*2&EynoQJ>*{3a zoZ^Vt6|5|YO|SfVPV8Lm$x+&q!JI(%%5kuSFHH)rbqC$g2l1>Ux5m8#4#{F8PY=8VI@V4ed8Ja-K;lqb{X!#!&;aj>ZKK?0ZXiqsqd&(KwQ!=z@*^8i? z#a%onx%!-sH_EUGHPGr3#5%U+M#`Q?w}Uk52@(;DP87;v74K_x_RR*0!>X&5ktlO# zmEzeP1rG74R6Zc)k)ZLcZFSRy+?rG@s)+duS#@ktn@C|03e3*a8spHy20vtI^`9bT z_u`f)O#Ei@b@NBgI_(O!s3JdE!u(*Tcut&)y=WsL6Nwiyyej-%DU2D=c!%rQ?BN9R zn<^_3*dgnGGaw`s2nTI<@3*@soU1iqFLm{L9%O65oe^%}+Em03Ncf~gPHAW7B|LXy z0XAoQ6Q0}EOJTxui@bz$6>16rPWHPuQ*dpY}NlQP&(W~Yj6k}hp_|woF2JBV+Dt3<`-hr%Ezr=pxxW7j1 zQwQya#XN8`!r~?-DhW$G7|LP$7=SE~H0T%rEt}55mQ81YbJ9bhyDkeI2OSDJDZ<&H zfCpc7z{})0@Nt=f179eoSpdWVRPk$8P4*5(N=#E;;=Ie`upgiM9uKzS z@x}&0gFt?wmMqhh0#=h0PTsd*lS2lcL+|pf>WYJ00cC2+LrF&Ku@*@=<3Z4k@6y#! z1HMbnm)Yt|r(a~xO`^ssNf!ar*|t-Y`Oe|QKy0%RQc&v8h?=9KfjzMc^aKlRn{_^f zPOx^2NbYUce~}0pm&&~$NzXK7ifEu4c5>-SK}EYd6hM6C<_M=<>z^`Oj3k*G7N#-` zxyvde%Z#-Cp}s%T3I@_;8$>*}*5a{_4bhZ5PS`}wwZ3Xg`+J=Nw~gilc5$!BBVGAY zD&t7Tcn~`6DR*<+%e&|>X3_gVDM4CAw(lkKjiS9|fHYi7ehib9a)?dYa0xv1kYhY| zK1s8QHID&!cPqsnt$usgt_PNiBC$i=EUeC-oJTG8+^^rP-j9@t9;JJwN>$ z4<-AaP5#qrU)yC(0;$ZBDYK-ka?;jB*)PXZ=Ze?K%?i!Ktb-ew40db_8Q7VV*EtTO zdUh6LWukK?5E%5p%-dPvF~TA|IkI*G{jrh8Wn3>JB}N<@nAM*td3w9`L)w-lniZ-u zc$M{GEz?Alj4g%}{#i}WSxk1qGl~wxM_gCa>p1@eM+n3+@v-S<(TCEr%<+pqQ7xQ? zGQ;jyC|j5B74kB3+(IwtKkA%G?O`f>Qqfnj3f7$OTvI!j;|gTIK$q6|JB8Jn9_vO0 z_@W-;zA>)&S=##f=tfTy!#_^$B-!k5xF6oc-c@rjBk6M~M|wHubj3;$=AMofQ<_AOs>}JJ5>u%(%)41kNIq1IvFKc1K))za8*eVg&hY`m|wpzYQxnde<~ z0>F0FV=72u2bV~!IPY^z3hyaE&K20W0xTUoB(F?-BcLgo=QC)WAQ$vR`^$PY!pZ4@cA({mL4nip57 zdCG^p;&{{ayb!lpWN|AY_dYVga-|DRmxFPw@mJ2*&FX8R`r5DPFlu7wmpdZSrh4hXG*R{@B@?OJgoIBda|NU)=bHI zoUCH*`Sx;vs` zPpS@9wL>DBnYNtN0#XtqD+Z<19QA2O#!3`2H>av3C%Z1K->_Y=GO9r|_0?TF(ug(M zsfVgD>2Z;^IabF9Wh7QDV{@_5e`@_9uF=vT!SfDZzgBP77YHt~taOO48%DIb^uUh$ z`infoEYMh5Eqxxb9)of#dL0(3HGTkLB(HK?r`|5C7LpMKO)@-WK;T8j%OIznZiwbB>UnP8=V#ywX^ z#w%pd#G^D3+yFp;7Y+X%**j9Ug~Lnk%jW3BS_}vJqIQ=_yHuY?brm}Bto2{Fs__T8 z>m`%(QzwTF&)35W3APj?m@{JQo40Vp&ghxSY@oCQu1}i%Y^G~yrc>?!%GwSUbZPtE z`JSM$UpOC{HJjhnCYC-NJ=cy1Hhb%;Dq^GT&FVg(_S`i`KL)?`?}%Bdy1Myqr4=Ft z)m|;AP?7ZW#NlI?Tw^Wh|f_hvJC4dygPAxw|6lgr!oKdcOn%DRBs|th9xAZWd^SbKBpPvt@oi4p4n^m-7BH#T&!dE0YfwmPv zJvr9_xZ&mt8a@SddBG5X^FI&lR@2vs84pvpH}Kr*=JYUg(t6T3t2Vv*z-nBnO6}NE zd7O;h6zmPVa$?uX!^?4*Sy;-w*#D+hP*|`1P)`;;LRIC&r<+@dCU=5$4=m8#=W_95 z9$r6TS8#2ZQPdPShq=FYud1yz-Ugeq!-aNd#NHAyp792bt!@mP??z0FA2Vkw_-1e$ zFc%5V;5y)fhG@XskZJ;5K~{qJfOyyR?QP)%$eys(X!`_~u7!y9`0aNY8C#Pqn;O9) zHV(3XM>dH7)_*;5Za{8E&zB~v(*;JqJMNKpY=6-}Hh^_{2F%S6Fae{5=^|BJ@5~Db z;0P59g7!1|nqyvOS9?e&k39|Qw|(EGD!0KUe^x5=>4YiXF%YJxZn}qQ55!Upy%(K@ z<~L{lgng+3LFW)>Wk^rl5&0K-bTpl5L`;>+E#Q^(V$QsaqM_u^Eyz6-cq3@0gW47Q zgMs~Vq_Bar7K}V#VNjuQ?ySq&@jlx>);I}-OG)PvYaoGb&st}{GXTOlRh~YW`8{XK zCi!O&8%jRv05ItdVe*_@YgZf(29C$6{J#S6FL59%7jaI(AhDDH&{8WCD?)$#0*U1U zif=ejaG`mbg5nn$D88S>9m1==H>n7{S z-m<4;{-#Kz1XZOyO--#9yrgMw?PQ#+F}XR?6Uq7(IU_p z*UZ@^jji`;M$ZZU{z^LEm{a1HU~O|wvH0%FS+3Y}66jWgl5kevkUa$Fb1ZQfV^SBg z)~s7uhAeXr{66iM`zERZg8MVJTQ8v1(eKDRRM39wpb=*f=Yuiz3j0JdaH)}79jJ^bPd-8#dQb7oZ4CAoR2{*B&Yq;uo2y@+8FZ| z&34nQ-JV*`uQN$pq=D`8L=KVU&RjtdF$wI!^$qlh=Qw+LyDFS2pxOY(1!G1jS^{~Dde#<9}X zTh;FEOqiNIfN*GhA@?=5i`;6IJ_CnLzdCeZm;2I%{XJa@R#BtYy#(Fi08_?wT%6?G zN8}q53FEtj9)%%X@jGF|;@92I{Rlhb&r_+EN)QjC6Sr;n9EP5^1?f3rtY%N+B&s8Q?}lkqvyO=}aXDxXS++z+i%7g{o)&7W4e~2kZ8xiz11ICtT@a)-*m*yU3z*{=Nj2(#97} ziWm#jI2HEQwIMUdP)B#a3U7HsY_^}U<6QPH`N6RFKJh_Az5^He)_fo?j;zw zh@gUt2+okp1-!bth#+0e5xU$yV6&)&Ps#-YBe`H;R`bHC_W$92fq$`YA~b*Ib^&%F zE>!r`?E){8MTpQlJRni6ajSa4eYlkuxm}>fdS;i%iRaJzu` zVoHGjGV8n4Qnw3;Kxs9QN|dA@uvYS-CyNe3N`qGm&={u?;>Uo9I@p-VH65YTZICi} zv%tkpyYUL^T;4+5EO0h%kkdNyRjEnVspJk^EHGRpP8A3?|BsqLp_1yMJD&4*Matnt zEF})9GZ#)x%iJsQC@{dU(;I~T8|sCze8 zyG1AOj?}ipd5hImMY>ma&++yK-CC@WV^ufTU+RxU-Cfa&ZQMofY!^9?!vuk08i8-X z!H3;e0@8Arm(o~<@<_EKL~0Rf_nJq|Lj*lNz@F4CYw!}rE4LjkRbiCiR@v?34oJWG zQpoHQk>Cdit{Gem*+P}w0L6@Rhf`1;E(NGG$tfH&5ybcVbQndp_T|1j6XbW!L{L z5{)Z8}}E{XmeqjG2}{hcnqYd6KY8b0_hg z==3`dGPXA}I?Psdn8MBJeAdt7-HbEn^~c8I9Jv$g4tHbS&8T1>TH}X8vj{AB8kt=EsIb%i8orF&A`kcVoopxh&F_8Wyi|68R+Du~Bt( zb?es2VHdX>%N@iYi|=tk^C42IYA$M>dxn28V4+DGYHJ2m)ms_?Q`QmPV9OA-g=r$63(u%WQjm72$7 ze0Ht*G8#Mw+($ej>mYBcEOevu~(tx*WziE6D$ESpc{vf+36xm6@}2>cse zIlMZgm2b_sODzAo8N^7&sr4?a^S{NB;0ipkzgCP?*q_f)!xi4F-BV2~rw=afrTkX> zMyc>4D#&IrLlOydA|~`vLP_yH{^J=CSHj2YcmO0l7;c>Yn&|Iv?+l z>vkfjt)1;H{nm_c#XZ`_yGx4JJg6=*iBF(6Z_Ec&+{x-f=vUE9TBt1{aBB9|UhPTc zPM6TqWAG(!HF}DT*5ct;lo+>qhujjDJ^YmQ4HGKH`Pw_5EA~aH8T?~>3-sDHt~}`s z_dt|(V$s{e^~YItTQS?&iArlGFPV!AwhUv_ve~YhALlLLS&Po88ISOe#h9QEBIf@3 z0M`O@!p0Spjmg(R%Tr-_{P2I?6 zE)41(~C3dM|P)!0etmm?S)~ig9%2R3(F^1wW{Mn8njlaS1+%r9>fqN3|z(K z{=R=hJz-d{-7od_&M_O+kYKyz)!77>&jwoxgh)c=(0e0?hOV{I^5MZtIXFTc6&riw zw|NGeM`r5;xl}diekGFpYEC%0xG&TkDjyzhJP^A%TYv_tXdreCUTrna1=(!s==Nr+ z^h=ehU<3NY`Pq-uxm4;*qRzO%I!=WnRFyiHW~T*j^4D-fM1-5JtoF9gen2=YQAFTa zubuxI(M-*&d8bgITl>y8c*QKbdo?S@{T7|}%k0Xa8??rY_y{z)TH`}VQ_NRUu;I%E zVp=Kp=A}IiOUk{+BDK$8)R8}k=I+oFVM_(da~(Hk<03&1#-SPGwZ`}5{nBS*Mar2J zqflxGImm35Zg+7SuwrZ^8P1VQ5DC}WlAC^j!+_MUD8k4TNHQ`+y9F{dCsvzAGGm;e z#u(=gkngQl`$%2Y{jbGtVq8b=v+bdS(qrQr?q5(4J3Z7qIotBu@Pg*h^x^41gumG~ zLO#bm9qxj383g0>q;AW-ZYj=ae5BQ1(P~VS74Lb3SK7isHX69o(!N#5GDx#Z2Ju+! z;43#hTyUX=A2Roa%ie9ce=#0PyTPnjw;JVq8-LAScSGDubE!Wwcy+pv){LWh4~_-8 z`co)iZ`Pi4&#L^pYxy-?9`v^Mj?mr6@zd()%APv0vU4At(j zlsp@LJ8IrJH(2)iZVPwX8nZ(rQU08rcoxcEdcl^v<(t9}dPH=#eLW;#(FgD=6>zsf zIDvL^Q4b2+%x~KEl^H~G;ZtYW{dQt?xt{t@$~5iSD2p>zgd_f`|0_W*Rs?y=AVG4t z%HK8XhbGS_vo08TCdL7=8yzxNC@&@Q3Us*`VdbO{=6DE`KPprlAI|5z)PK>f(B?mR zX0er_&Akq7f^qc0Ex8%ueBeGsk|S;3$M?#c*7PF^K%kCr0}ai)_p?MAP@}7>n!lI7 zdO=|4+Av(oSqDO@Yr`)ONmgZNw0U0nrRk_paq&R?IB`{@)0Z$+dgo@@3t)h5>$|r= zTY^A(e{mIo3DVQ4>B4N@X33L)Qjh{&FV?;#!cF?jY)`@;2I#sF-*HgtpwJ<0CQ!(r zCh$qj8$mw%=D#z&$4+AIcnuGmuiL)VD#)|n6Q5xHmBSKeC$hTKE1cSu3SyTv`tOYA znQx^32l{xHPpNas#I7*jdXyA<%&Nhv(|=2ObuHwAfkV6-uFu@zi&%j9K{m?4T@p<{ zDBIin-1uqOvNv8yYZb2&czwn|v#CwMQt_(njX&otF!Qc=WpCs_0}^;IYWB$`tI_1l z6=V|_hAi+lcTDE>u^^*V8{WZjl>Hmc~ zud4Qj{MbT9;iS(A8eio8K7#Ij)>>6V0jP_R@5p5JLX8(S|R^)bin<3&Qf2Q-fdM;3B zw|UX(z7!dZ8;RvQ^HOdplAFr5@OL~{6k5CSHg&GO+N5IX1s-JNK|#jR1+l7Cqko|# z8Q)Yv(Y7l+#lF(J3MahWW>{jb_GDYyt8Ln9O~y)rxE9YF?oQ|0EL|rSp781D7ulSM zx@KVJE7fbc&mV907pvDkYj3xjm=@zQECfxjKKNb+r~yl|V>ud-TmRo;y1(qibYB=; zJ0zrgB;B%g(R2J1iRd2X*q#4;ne{PijDW7)|A%mHWz)&}hbyr!`G?YS>T@pKEgOmH z>1g3m!MSi#7aUD2{VJY&xk!ymv8psU0p0NDB{<#kSTGRF9VNAp|L0lZA7gh`7jv*A0o~-iX{SMpf8n=K!@o0r=sbuuu`oJEe|29ViRx#awqL9&lx8u_+ z@!Yj4o;zRoQGeXIi`3{}r8TwFP|I1APS3TwFd@mG$H9KYK0?Iyc76Aev>!wW0@k!E ze5MQRt`L7kCm+3^Qisd7v+L=p`)DT{)O}zesC$VM)QyI6@4~!mh@_fZ9!y?yn2`8u z(pP5#xewf19UhTJHg;kbtv{WcK^UYUo;1B%{6j;x6$VrC2PFkTPUyBduQZwo+P32P zLLY@I24c6*S5qskaR29)fq?C?PQZ4t${P}}t2&wPgk`pVIM41Y*2O-h)C~|XSs)#>ramEx4ajCWvW0r@? zme6R~dlbpWX){LLlK$+s`iXI78+uHIHOn%e%O{D`4wd??3y`I#f>bf<52 z4x;$**dbn0)ln)#D3V@-my3;s=YC4t$DD5SPBmf>P&mty~Xa~TEJa`D33TGJJrR1s&Z z_V1c?L*r~ka1bY=zdj^L{aLA>bxoYD2pEG>_M&#^BND6RcWLZwewT@v;P}e;ql%TM z9|<;8E{hkiHA=cL-3(_aPJfGEzq&>$xK{Rz1KNy>yCkG(g6kFvTN|L83hX(Ot6G8mRfCXYg@Ff(rQ~?S8!`sgy0Ie;ZjYlZJ!vmu~op0{J-bk z=b21Gu=ag_{q^(y{vEhE=ehemcR%;sa~WJG3uH(gFOV^Gq`*~lOM&Q4@c?B8DwJ03 z^E~v7o{p^5r?NCU4B22Yb6441;okU+RW3_dY|64Xj)v8u*Gzi8M>!<(SESc-@M_mV z+jm)kQTEeDaavkCyd7 zcv*PIk9h4jBY0cePdGc}9;KX&9d}2j_*L`%%+uBrKZV?~qEEJdrX%T#f3_~|^BKsH zQV}5)#C$R<7*~#pKO~Jr#z4;bWzeO`-$S@|jy#?gxeMg?IOlfW1F~Q5t1EH4zcAZ{>yl zn!Do*d3B%=tMID>F(0rYOw}909JXxPlvXx-9~{;XHOO9%?u>)z2w<-_*!s!+;Z5=V zpd@TId-oBN?HBrAjja{z@;FKM*v@W`?Tb++FFIgPyuTW3Z5a(G+DOFj2*%c!I6gm&sPu)rv`%3$%p8J;WdZ_xb#PsWZ%U97u#ii?3=^c9SA|t1)zbi1= zR^vw6lx8C(oErmNGnh9hBVC$heh%Td?&{Hy~(g(7P z8mdwFWBuQZSWDA|mt;46eN?WafeJ?JQQEO6R*2L+!KbW-h*{wX@CWN9fnspe^& zRJUt)wh5y_vN-|E*1B6{0Z`#tf0^t{v<|1qFnJhi-a&`c;TV{342w&{bAMY3u03^G z&2aV@={iOUoKQQM{YG|E)r&unHz=}gWmfIq5lvQ%P%<)Qi&VsjV%Z9_E}1aa-q{^( zyPU=vsV54_PIQc(K$q15N<-_hby=n8*ksv%(@YT z`^ywm-NQ`d>}6~PRc0SUpRayGHsLu<<+89@y+-s?!Nsf?yHxfyLf)^pU+HXY-dTN- z_MM&ZXLzQO3aXwRX;akGP)Cbpp3RC-QWb}isyJ5S70^JnZKBf%Da}qtN9cQ;J*{Gi z;B0#SJ({Zeil(Z}W1e|DJ`xyP-J7DSZkr#J9`vH9iree9rm7dTG9Z6gRh6g=)2gbn z*Z-OJ&t6a_;_QqG=n~+Ag9_ACWp9|!_VH(7Jyqx0daAxp9cCUiYN|Z*j?(-6J+xFk z{vuI0TB^$MuD3vd;ma1=P zPcKAz(&N%`TB^30#)O8d_E<9(%Ba}(?x&0d-L+LMZTr+%Mrx~CYP415X>C<`+q|?a zsZPBQ>P=gf-pssg&1R#+u+gQh3iVduUC<&p#-!bgwkkVx4539>@kFYs3cIPQdI(tp zVVCt#RaL0h(pDWilrB|O!u4I%K2ZY>OJy2u9}~`~PTr`ik{!^m@6}T`Jt=Gb!Bv-Q zbyb(>ZPj+6gPqyMB%qrnc`!<-Bmi;BZphQHfB`{vL`T=La-#J}PMN@&uEm?JwQ4$^ zB6MA~?~pnBOI29)Cj@iQdkJlEV4@AmC`Rfhv%febwtc_=!O)Q0_9qZgVRc9>aPo+j zs$NxCJ%o=Fs<8S2ju9%XHp*u?bTCS(zA2w<%I!}Xow}>Ax*VG(pV#=F&xd5%=$({_ zQj0gOGW#E+!b)=~tY&sM(5&q_hI6BBimj{O+UNp1>Z=g(^E4t|tU|{)Yw>F#jqcj3 z{B5j=S-a>hj=$|`omEkX)vNX@z1v|SC=@i>tCqCM5lnc~gH|kO(^Dtj{u%96i;2|T zevw4oK9|3)_AIHFI9M{Gy=tnXx~f75<7{}|HYGEQieza@v>`1RCd))kj4stxM}=w# zsrF&j78jg#ycVmS{w^(6i`GhKz5PU5tgP>F=3=i{&%a4(v@<*Xu3alFDHqJ@ygTo2yml~HLyoN zi`qP4NBeo%JU|@U`-m$U#u|4IzHmkPN+?rb4zm^~w@>OpvOs|-EHhf}gz zVR>kJ5Cm<`uy(rWkvHKW?JZ`&@x_imzSujX5WtEk_LEMrO~l0BmQCN{9-HT3WUA!l zn1jKO{D^#Ur>(O^;^oMCeRPs=HaFl82l+K3mKgzOurL9Q@horcg_$yhIQ#Isxp zle>zYDHmUguVSBeTdmXpNL@+6XqXZI93pA@MAEIZ{^duL_x(md=SX3igA4Y&y^N2zwh!*J33~ ziMY+t82jA)*pPFs297w$X+3=NF@XgV!EG{zp;Er7+7+1OFaAK&LS)UKe@4g=C!ye$ z!oqw>ri>52ujQgIlABaW$@`mz&yl!-4-m1|Pf3(_ApVipIPMD4;qjrpv87L$JEw*+ zS-s1~cHI}uYoxZU{f#258cG^O&aHVSMmKodVKQvjKT>+(Ge}`ibf%m`1);yqTqMj} zK4T;YveJBJqy~>T$OjYlV&yNkq?F}P3yC_Ul$<%DCWfiD#Tqg~8WFd$xb5@DuL(~1 z^#Sd1XQ4J9fyanAOAL(WDuY|}V&^7XKfI>16UEp^Sn5%7Bmo-dBqN|nn~+=h(%<|c z*SZY-AjX9HRjDz-aiJ{lEHCQC11Ymc3FtR#w1Bu-D(eRb_FI49+~XM{lkO)pkT}pC zKu_mB&?WjnQ};|G!{3cITyWwR?46IxSc$y9Tq;6>i7C$?+O%2POX#T?Gq{h~bbYgY z@!o}8@_Wzu=H=!X+@nR9SoYa6S>}a&Zdd_mALaw;%-CR3USqBsb!wk$Fd?$c(z*ZgJO4CKn1LyvCd zE9lu1~A_lJqhsi*}FsNpRhl#m^Aa2vrXxGMQ6#e}ra*+570)b|b_`z@SL`P^QwqFoi zU8V{Y$Qa=!bX~*{L2XiF&sz6NP%}i-b`23%jn;G215qjF~p89@W=ICI5n5pk)Jv7>LOEX)$ zki~kaGY5aXoV_u6L!7^Jujiqu;_{sJQm&pI2KMxTYgWVIz%X_Xzs{;V<_+}WZ{Oe@ z5=q}Z=ONMoPvq&Thar=v;g95^E|c@ay3D>o9!uNR{-L&)wV~V$;dP&xVag&`kP$ z_QWlv43cHmF747h0`quh**()6IB#a(z#Is2mgfof3VxwZC#B$#o{eO9moB^nwCT{E zfD;7SC3czy2<%-V)nU>>kWZ)6HV8X?$%RW%WATY@# zgvUbDp9A9=t(>>9Trv0TWoUb4PwYncChS);7D;;>F$&-Q##yfk4;6t?D2uLk7}N4b zlwa?i;HJY4bxxTcm#uYifH@l`u>OtoXMR|_)L+cGu^*K~wHKil|3iP~ff}ayr>t>L z;@?a;8F@{-AsdcYPbc=-)e2(G)&*^xHIl6OsPg9Q#t|Oy_Gr4SP=W3y8(H1xPrNqB z;(e%vdTC&i^)%?76gtFI%$cz)EA^y&IE=j~lWGP6iUQO92R_p)p={nyL30CEX?oJ_ zOzB6o%#2jzMbg19KmyU89ep|m9bAI3G}UXPityU#g$26XC&=a9pVo@7%13(s{2BIK zHE73y+4NSv%qT}uD;yClb`E6}I!o@z$lN8>?B#CTw*rK1npFqrU9X6ql$lUjzea|; z+=N^56~mcZc>YlA-M5e)V@kbr|-c!U+6=&ZF_U9RBW=FR=671 z9?IIVc8R}nZAVVSvjKPG+M~XQliTC68%vL7Z)9x9KV&^JR~n{g{i(3}waCT#j$rbU zJt`}XA!J6*p+Iy_{1>6;jQ$MR*s9q#W*({j_BWW z*U8zFY*btD&oOWvAo3VEJJiuWH0$slcfd`OiX`9ni2!9*J8~Hvq5MLgL2C9rP8IR? zRdQgW{23#EhRPpL{U=$$hMdff&?}x>c5?n7I)HZC&`a%coQ<_dgF19Xj+6|+v?ogovVvn4w9_vgQoKGHGtTB|qdh>e}B%|#|&{rSa#^c6@@d6V~_LoKT zJllS5)g7{4BMwU6+L`hWR;=}YX?+W;y()>)wBPQ_d@|U_SND8YdtXuU5CiJ=hZePl z60AXWgwz>+jXk8vuq~#}Tk|>bM5XB7Fy_6}V&bM*zSpSBc{hsx* z49{tR#q|rCny=yGKrob$gF=j_I<4^t>NMuGNUaXF`jEkO8R9#TPewX9fozitWN52u zTJ)mH!}7+pFIql!oDgKl^7^$eo)k>xVnz%8zndlJDxHDd#4gjc^;9d24J__AL3I{J zlZ8j5M{ienU;npYQYh!pn4Q6xgb&-J5;~~#oiz73vt*SSIF;=bU^HJ*x;tb6M)4J+ z^j0fI1xI9W$XU`pWV^g+XSbMmZs06wkCEZV^kjs+XhS|8pUV!dZEjrK;#vPwu|PtP zvNn&|L5wQP(;#Akg4PA9IrdpEOi6vWp+=C*KV6mVtN%Ras)_uKY_0zn>GhUb$C#XgCs79%uo<^bz9l^Fg+6P0 zkzCA@`~*kpv>BDG^tbF3Qb<9_rMF{F)&>~Y_F0rZu!@pzK|h&4)t8 znnHOR{%$OFt#?c}1q+_jCK|6GhUD7!xD+jvkXyW)u-rh5ZONIi+sZsuw;49LvgnF# z&B=W4y4Tv#WxlrAZu7+n*&9naF_1Ryt9$1`PHihPR$HW4OMwAJ^|yYtp<*SF4w>HypQ?1Xw6K*2b{e%eZ(gGp%9@*K#HV|)tS9v38 z6?#p5M|NCC1S!lD|lnbb=G&6jm9m2FO z|1J4Hi0IFlx*AaeiTaCu510{lIxBQ*GfpBn4s+^x>$~C)sY&~WX9J%sWt|(I z`O(AQXphbd{hr&M8Dp=T$(1-6>m=aUbS#|#9c6xGlv&-QJmbrwr)avT&b;tHG?u8DGWYjHP3}*Pi2Vsu(+#OQ@>`a~W0csd14u&hrowoz1X4+WRq3 zleJf@EnEf(wTLd-$C35yd@_^JYxa5`-qW7tFPd>+=# z$Mg-{RW#$c<&Ek7`Z(CQdZ+XX*|W}=DJ7@*i@0HSi4;;R=HpEsvsrT9vJUT;e)~OS zni0MsSORjdIUxE55;=Z8*e=0IM63T0*6Q|e>AhI}K9_$+QVFX&dLe6Bn|IQs>wJ-| zBotP(xeKGU&>Rd56gi-N*)SN!(YXULh!u=7d%Hr}#+K>PArA>v$u1f?S&g^KiAn5o zIWf7cHD^Zgpx_wUlK1gE1OcM6GfI!@3lkmoA%Z+hlDhBNvOp%jXDb@>}V@1N_D7B(R?s zdU<|rg)86f-V+^Gk0$Gi}*&?0`6a2LTD zJI}x4-DL0?;FE296!;Kh9p7*`xE-d7i_XR0WBTtG`tRrZ?`Qh&r~2yHO~#8%uPK1HsL%_q6bS${OZwaRKaA&}0M`Jw0AF+etMWz42&;qb&| zAE{LkPg^VWqTnk`!Tm>ITv2co4(6SioSWHlHIH(eLdW~Vgwkby^HIC(!a$UHo&iwp zjdsdkEMuk|bp-l3<=>SI=izl3bSfir6Fy=^e=-CRHJ*W)p`2=RM8;v@a2N}ZiNTm! zOOUeYt+begR$1P3&}{+ye^Atu?V5*E8p#(`m9y< zb;&1akruWdkk}f=%1SC5Rzx#UJ7+W8 zWRbxP9OV!KG~Exr1w7AiJJa~w%%`X*dl`4H)&cJVs0qWhQ%12|Oi_Q6urY=k4K4ZstiwB^m>oh`)LT*Z%PWU>!~~LzRg8X%B}UY>>}ZP(USyDH zc-Od#!V+6$3(r@!#>sM<8`HbAz82EZ35W)lzl$XbT;%5&$#BjO)Y0eSWpzDUBFqad zjF(lI*Wc)C%@Z{)q3n3>IWL6kA$nbW9atU>zDQyt+rGgl92wsx&LZWpw3-LE5ux&= z#>9J4v*WY;>vq)fO*UXrwuz5zS$yY(5>0w}o?U%0GXLkrCre_feC8&LU8>l5#V(C( zWr=;O*jr+6GKK;OY&*pEXz*9L>nuqD=@S8-ddZ~GB(t5$Jih$UU{h{1igCJEkiT=E zQ%Aaj{Pk^75tXDX2)meYB{>yT&{aY8ZEm5dCY&o6uAn$mK^*dgllY4DlO2ClDA7T} zQbDQIMY2>7gd1d%@gdCEKlqZa9v1iA%d6{$+4E{sKh%X(OSqa${p^USpFBG~q3=br=F%riMN739XU|CiOzBh-&#iTr zmeq48*KJ+%HR=5qBwODwNUBw45U+K)LDH;?4U%rtyF`QSssIASbYpqZGCZxPJEU1kw!v7Gs`mg2EpGj_$I;k8(hX0Yq!BS3%7<|9r)doK#c!|MV1z%!tOYl5{cL<(k@S}oH zGq`Yrtu%wX1s`s3{Qyj|!BfRP#^7GTk1i1+m?vf4Gq`@yrPbgW;^#$!%fj1gF}U1; zwH`CLJP2cLHF&k)KR5U)!EZBoo!~bbe1qV12Hzxjz~HwDUS{wz!Iv6*i{J$Y-zs>v z!M6#XVen?bPd9jr;9i687krSxHw*4I_#weRU#!dCDtL#%Ey3S0c!%JJ41QGbXABO< zR9VdimuI`J2MnGp_!fhw3Vyr6y@GEtc$(l122U4!mBBLvuP`{QSY;I&+%Nb-gBJ+y zH~134XBxav@N|Qh2|m`~)q#8tO_fHx-Y=jmH!d)QimkV-sy`(y(zG zn-3RBu`l2S!K7n1=xn}aY%;L<$k;q-j?C1ieG>kSq|d7-Cd4K!?{Yxc%Leb3$*yqKHjM77v|WJerfgMZ%CwH-dc zX;9zg>)!74EMNEOQP0&+vj|3sBTZyy@OQb7INRsE=!5?H4hn|mx~V&J*Y67KZTI+x zvEe(^xeLytta8{ek7tuS#@;XwlMS}Dio_aWRp#ELByibxJkiatelP`ak)V~`YSWy3NOkh&|yL|$KJD&j$KjJV1E{YqKx(^^OzN!8*cc6d$ zX9M8|1H0p*>bEuoQ~p zj8IY|M?0Yd@EE+I*mdC1Etv<_p2nk!T2u24n+brBN{gG97m>yHhLV=xsr?1(RnC8M z8)L?jvp8~g5`x>mbK^PlEsjIKCuxPAM@MjbY=~<}FJ->P!&PLtFIo1iPo)XvHR}9k zzU9$u$?Qg*%eF6M19?>Mfc>7?`~A`TQ2|)fU;JD|-i1}v96U+$jG8WH8hyDYSKOvcxr9gL-+`{B zrr}5Rk^b`&iM26S6l0;`t20F|H~HbfH}T?H%6-PMSUbKcFR z81cflrNl=)>t7PGG$sAaFZ9dT^pfu7Y51;mt)`S~aL}c>LozH5*XTaSUGu-5u6_8m z4>)+S*Ai)G$|~_FchR3W?#W^I<=TCTohiwVzZDWsV{9s(&}|)x^$5}rqz?!>{o^Dwa$C!grV3o9vo=$Lgp%IBNkB(u z%IP|(R#C|{QxZC>^JM|BSK;yb^eb?3@h3yG`C#LJOf0_67x5Bzm^%VUW1|%yg#(^Y z(mIJV^ZCFu-pvw$G5nm0T(4m~j>JQm?O|YN%7eBC_R#YB7=A)YBI4Yc@*~?NnQI5I znNW15z0gjY9ahiv48usxvYph53A*~8(9C(zhxUuAG_s-p91ME#!0Q$JSe%fv0pf`Iy`k-vUY&tiPqL?X zvbdHFYS-%QRTNw0a;_E}ofZE#A@+KUZ!$4dp*1|c4o(ssj&>wkjNm~aX$iNMcV14@ZI|{H zteO#9yn&@U{r+j|$KTficN6^epS51~xY&fSu_`(9-m4Oc$sEe1%lMrkgUjW+tc!5e zgK{8^X`#jX1dbAKLcU~WI1ZN@hgR(%0-TSU^Zzg(+AFW7aED6TPGE$v?$2xWANhN3 zW^=8_`jB8w;_b6g-wYRiU%+k67$s$3wB$Xs=d4%s)FPu#V6f=L>+hd{RBmFN6nK~Q zA^ONfNwq$`Yr+CA|pKr0h>E5yX|AZ((`Y_fSPl*yW&O<`6hpr$o84=fePl5_C zaAEblI|_9p=={%tjKW&}Qy)B05hJb3$n&TS>r9<>y=?g_8$~(U+kv0F5JIzmL=C|Y zZ)J4f@p-JT{x2itfeVp|Ey%yJbBS+bz>^`fePLGA;jI0~kn)bwvfi#>U*yiT&fXvT z4rhDNs-1*Z?WeU??I8oHfTyh&-;zr7G(5#-l0>GH$oZj|R=mf_>Gl0sTV>q8Vl3wn zdnv2JW@#f$u?hH`amgUb2{IfW&n>$;Q@%~zNn~pY1t+^N;^&?Q*%BichZ7V)-sAVM z`bpKsGH=pT&i!vuH0x=%)GL8)31qNbEr*FT7eaVPc5%> zpSU6JKHQejp@j%9+xp|%wukSC2Lw+t^xt&FptzLtz_Eqqf~G!ooqABDH)4e{92UxX zMrX>|0LWzQKOtB?ny+XZb^=4+M+5=f4>c;9Ej z7tu5vdBuH+=f+sr}mV#cafb!(7!3=m#mFD z_fnX*eH*epc{IzneS5Rx3ZQ|aZ|1dqqFdH!WBEMP_8uSFwjBftUrA^ogl_n>2W*^$!WUD&UoL(n6bH?yJyA+6E+Oy7Cl-d z*t+q5LmxrcebPxks(H>oiW7E!(|QSy3YqK)OrF`)cT>_IS*7|zi958qAz7j8nwEO^ z`gOEPNKGP&=L73boh(8E8x%Eb4b zzCsCqKgN_WpON=OB|MFS^ekbfl(0Vzx?I)bW1CPw`Y4B_T@^LCdx;WhZE~8UMWaMK z%03I?P-P1wuh|pXqop@jPoOUXq#rLL1;pD$P4W*WphWe+QQnqt>cn*J%P0?e1f6Rp^+8hqunvz;&Sx6HQKa3hu^Pxm{_Jlp?Umh)V2_!_b2+z(u zcHOpiR_segNsE@x6z*V}0y7Ty&>(SrGz8JD28qn_-zOuCpD~#2Ct1kRYrW2tIXVZ7^q;c=qU}w6z5VCR3nEV6wuJZbuMb_Fh^uaF_0jc?m?bbGyY)f%N3*m#X-rb81yl(n$b5OyH4h^jj z?;S>*F8#NTsyxwu`zS6w^xr;oqkHS{Nd33A(yL}}@yzu+)X;Z7uD%@>8n5(9>nI8; zWWMo*T3Et*8j8u8h>G9nHgK8^|8CpAX~WxX*gzIUq%yV^w8t3upxNUace9#R_-3US>Dy7DPR zH-)(8{clrsI!>Z{|SY-y7{zE zl2~;tT?%o}JK8P^aRFh4xZp84q4Rh&3#GaLe^7{f&ql_}6Dq_-9x>@zw!oTrkqU9s zhtdxIM+$LoB3j;6PL+6iQ;54@oX!^J)DhX;)xaF))?PH z#uF>V{p6=%Li-~X;(l_LPRdb;YgD_+(m1RU_xThA%r=hJ8gZwykYvIM#QW-x#-WCr zrP-G&$h~>GS!8~hg4|gsU@Z$w;;*A1cN5oL-cM+6tUJ4cI~AQfkN}=GnIX}UEB2_!we3-nJ4x(IQ1C9W+|zKfKvd)o z7Kn=6egaXE+eaX(9OYh;s5dHBKPasgRLU>A}1PDexrbo}5QDqzeS^fby<-qp+v|cr^tiSI#wx0<1w^RUtBPDx8gX9O_ES7s zPhJ*YIbNG>tH}N4;mG?&EYL;JRWuG~upaoiA1cE%;+@V$9agpqUSN2^Q-L6iU zbJBmXKT0Ncwkei{jHg-6x4{Sz-MCj}&dMaM+RARaakH`NZGR*eT+%3S#Qtc2eh0L$EcL`h|cCwTyo7meir45qW_ypeM~7y_JZ z!o4-OO5no44Mw7whm8*g&6N^i6-SLi^G4f7iHoo3`o5hAKhi0$yDG)Hg>ww&z#wln z-Dp=k3PBe!lIOQtcTY99OMLa;9Hcz!g{{VA#ti*NEh@III$w@_28a+m&$Pf=7e4g2 zzD+Ychgi++4r?lC-P)rnq~tnE_!fw4nd>A+^}7o%mwhrZr4v)|RLez(rprgOeS6d= zO?WMLNMwkL2;H`bZ@5+L_4@3MX8XmI5|qfxsj}$AfKM?%H|l})Yttw(<>zSf^}rqQ^MA}coYYVK(Q7>GhiUuc z${xCjvd`w&MIU}pfKRhb;XMsMXINmy2i-}^sUw=|1pn$$98FRi2rB9+R;a;6~fxl?~TJ;rMl$xRda5T${3Oy zd3HcHr@kNhl%wU)@8x_Z#hQLecs%;xTy`Fx5_w)|6e>%MdX`6KVIhaWG3nCOEP4Zc zd-0UnYP0|^pHUX&4^3ZECd?_G@4IEMKXdwgzJgU;s0@9;twqtX(*89#du}e1&FB~W zxU)H|w`<`#p%2|cPDbPn;=b1QYjjo68JYvb{1g7l*k-L~rzh%nWP=ro;f$?0Xia_J z-#8hPuJSide|3d)9@zT7Aa5Lph|XG?eXhijZ9Vz`F*e5TE`nKf_5H%GU%lG8>pso5 zueQ!u;?O`358-y-b@osD&mp!Lj`!Y@q{lS*-PTEUI?{PM<>mmKq%`PIU@{W)YAs0C z$Jc33XWO2BVmwWd&(H_br*8Cz`s7b|&mTILd*BOsAgwyT7?G^zK+Y3F`h3yTwO=aW zy#Hbv=Bh?;sNA5NJ!4v#r{NBKfF^>lzq zb$pN|ZU^7_g)Bk$*;kFFs=e0BnN0oS?Gody?T2{karT%c2aoy=41CE?U`<+E@hn+O zlbdqBhBeV6f+J~4DPrg4v@DAOSKpi)vqz59DP*iZW$o<_9b-s=3?DLb$R**>0pE6R zH?fFs=9V4@q$r^4b<9J@lzrO!?$l0sSMxj<5-Zb>m|=n?NT2|_D0xvAH7I0QtdNQO zJ(_tKvOPELAeGLPRQL_P-^s+nJ=g@#ux^GYXpUE{ZwY%4mtMy` zdD-kT#=b{X9jwOZtT&0DvoK!6%*}kuA9^XrlfM`1d(0Ud7u{|%Ik|RN`|DOdG1q6r z1{16?I=LhQ`+2%b^zuJvamYnhSH{cONPldZdayI)YQEYRt-cIG5jmdDW*H}iH2NvA zXgf!$iFMgbydF8^ABJ4ZTij0d*P{@5ob|{8DVHQnpw}3AsEltK@!{1nR%n)CuKi>d2T@PY-k9ymfU~yL<&J9ht@~pg zsbzbf*zY^=DK|Z`I8|Q)#5N!|KM<`AqzObvgjXQiA^fxJ@?7pZ4#J-1X1&T-$G6IG zwWs&6zh2u%wWs3C<-V>x*>NWm*ksh9a3>h2b<*&_(vjDOHIGxx3MDOMLMqg4%m2u< zG{pMJd}m0u7SG_YTUf2_@uAq!aCI78P`uu`56<9JF*em1t$8(4-nZr^QMU)K7yX6e z$OG3;c^em`w#}qp_VU1WdywMw^1$`3MHICA1J`3eavIco(vn!eGQfG;himmbayZOd zF+21mmL+5T*2{mEFA5+U{qO65&=u9G-(S%t(!U9u$k=_u#4Agc&UD^ zGa+fiXkX27H zll;60td$0~ShuqcVcI}V-QM<8lXBOjVC{hjqV&=bm-9K2MXRc$TmK#(B`Ad84-00! zBIKOUPopJ*M<^S2;j|FIWpNa_G4`${Qu5t?qnCl{`BrVg&HY3nNT5$=N+?!)N!!&q z&I0Wm_pbgc>~fOi&LgRM{h@bR*%w$JOb}s2b~jwpjC9GeUhL@tStLxM^@#0~9vNmk z!=bWPtm!2>Ct{ZaWhL_dg=sbxtI`?UY(s{cWdi36hm`YjV#_nu1YR2SRS^ z!Fzhk4da8dp7>^OPI}yycYu#0iI%6cHuUPGL#>Q(>QOw_6w1nva1Rr@{_#58*rSS#BR!2%5`H^JUW8LYM5t6CBi-t*er=)B!pCRzmQ8EXmAzy>l%Hj7up{f%TBR9RMK}mW|MUBQmIAG3NCQ{u z0~@L-=DVK_(`hN3LD;F!`p258yoJnVXF-f+t5AL#Gh)z(``7@hIuwzYQrmR zc)bmOXu~vFnD85H!#*~A?<`~gk?l`SGvA3e9BadwHoVY=SJ-fa4R5#MRvSKL!#8dC zfenw@aKLnv&M7v$(1wLJth8Z+4R5yLW*gpX!-s6R(}pkF@NFA**zi*u#-C}@_1f@s z8=hms`8NEz4XbUq!G@b`xY>sH+VBY*9d$J8PZ0NV)*KN4UhBw&odp7*J z4Ii-K9vi-9!)bOs>dNKMGj=^bWWz&Fy*eIF05^{lrEW?MDl)L}pn=caZD7w}?$3;U z-6_4hNBVaqeXvZvWhs-7X+5lf9K$B+5tt0KOO70fdIn~UFN*aWqGWIRR0(`9SQqm;?N zf}WCJu0`s6O4%h}PJRrmb5 z_^R#UZ!!5O(IxNhvJl^;5x(=Gab-l<1-N(rmV7wrDq5MOr<93bz9l{>hr}cKmhh~6 z{AaIRd3J5ML6z`3-J8$PE68eo_##~X9U$&QBAml&o8Rf zpQNiuOA)`st%y_N!&DM}wIVKwN6jr=rU;`J6a|7cB{=Y#TT^ah(4{O`Qycz*UZo|K zr4bejgXSy0s#5z}5VT=YK;n_`5=P-q;YZ;vNhnuTbWCiYICtOpgv6wNp5*=m1`bLY zJS27KNyCPZIC-RZ)aWr|$DJ}h?bOpIoIY{Vz5Z6Eh{c5UB05M{E90pR#sM3f1{>0 z5WMQ@RjaT0=9;zFUZ>_%)#R)y4;0i?6_-lwuB0s$Q};Erf>Je!mQ1^kQj$ap5>jf{=b z56da_3cf0J|1H;JTV!0~UQU|jxL5G^8rz@ro_O86O#I@n1ovX?Ek%|D6Jgeb?QlKSvM87ZZSbtSekQhK$|E6Kmfdw^aorI%W)CB_Qvr%Ely zPU4d~bxJ1VQx}~kYC5eXZ5dN#%<-x;W`ttCYSgKGEhoN8zNO5PC$W*1AoP?H9Z#uB zokwXwW)6_@Nehb%nXU6Aqp9R;lCE88PfmSL3DqbeZN0_i)ooDPv6H7R z`c6@2h2wMb^VRC}YSQXG#op`G&|wOrhLiuVo}Tn9>9hZx^rnZ?tEP>bHgFYj)extw zIx3*r@jc1un_U!h@;@yc-&fE7<>Xw}N~=gWKpz$gIbYHuom%Wl&8hD*)QoU?z14RW zwJP;xMndV|ReH3LQL~gWQbw&(9fQ-39B9gOMvwL+xsn)Vd@y5MC@_T%IE1|lKfkF|&gSBdxJJjbsld zzrtj*-;$G6{j?eC%Xx7YqY$^PD&X#8`vLjSVtZ@HWyzm5ds&J_Ut+hTu@w7*;9jl0+WuC~8N z+23_;()`k9?#x3GPbjc&-~JeK}L)U`k?&MDuWdjps?}#aHhxMYIGmf zCn`B6CnqOXe$&&5OFVir3YNsV)miE3iwoeNd%e1exeLn*`6;!kdKEu6K6rV-?FP8{ zC!hcMK>_b^|I!!-&A;Q_j<@ksGhgz_+~wSSQ@T(7$RMZxp=D*v4D z-v6|L>tB@XtNnArAK#+?S(|^<10RkcF}imB>egLf-?09MZ*6GY7`n0Prf+Zh&duMw z<<{?g|F$3e@JF}*_$NQze8-(X`}r^Kx_iqne|68jzy8f{xBl0C_doF9Ll1A;{>Y<` zJ^sY+ns@Bnwfo6Edt3HB_4G5(KKK0o0|#Gt@uinvIrQplufOs8H{WXg!`pv+=TCqB zi`DjS`+M(y@YjwH|MvHfK0bWp=qI0k_BpC+{>KcO6Ek4G5`*U7UH*S}`u}74|04$3 ziQP4W?B8AfSk8mxfZq9y;9F$LoF6iZ-M*Xnj$BLJ)Z?4mzunw7_4wuvcsKW(dwhSl z$G1FL8JV6uYZ>`1(kHT}ZpO$-{CTAguW@mCWl7c53j#%fa`>UxFRCrAnYZkU(&9jF z*`q0Mc+_&!}WE8Vq;m+tzW+$!l$R#71V7|Zk0AZqhN6z z>opd21qB-j>P@TLP)8`mvaYPG%X6^@^t?zN?XK!meeS#+g*)&@!_eR(BCFW1F#!gsk>1p~c#u=CgD4_bbS zzeUuG!zXcg%f-};a3_RUA-hr8K?uJ?ILLQ+pNIj<;)4aPup!stnXrRd~ya zDoZL#YrH+n*;RilN&{41dB9s-RZ{A$TJEiOc=Zy~B+^}laek9&Kegm&GVMTeF&Q`6 z)jPkORn>Gb(=trW6Yt8E6X0`$Usb$wOqb8}>qxrm+(r5?Db-CO(vLS-D}-6JaPCBN zVjSsTr#yblcyEzi3TZ`=p-JI*|D(o3+KP&*t0iIy-J>}eq8%5mdyV!;rI&PyYE}fL z!fU;0rB^Xhl`r>}uB;BMKJ_1`w~VG{4`M}Rw77`Y;524wu-=uWE351y!O?b49IZ!G z>4#o*ydC_r1=$O3T{GeF-?yBX^Mk`lj~;vLYw0eEI_K=AGC$QWy_iP0dMW2+GEvno ztu0?!T~T_uGY&5;DX$GI4V*b`Qgw+Lhz*%e_*dfYKhUiPmL#fy(-PFc`JVkr%?Z_S z%rWu;cY2k25|bqY{rsNtD)lDD`R;#Gj5=w`;OdmZLFp1k;@dY$slQ{sW`}VNjaNeh zNopu*3|*L@hEC(VCZ&1k#H8sXcYD;ZKtDC4B#HDBm1k;vO`q17{ZYcqSi>9$aK*={ zc*5XP?MiT|1WM)_6t4zN^Qb{nk~{jfChm`Kc2~z0_9^HuY3(MB0I;MlX}Q(V`6>II zytSOJ)E_VbCvUv(5kq|ahsUbnvs0T*NtAN@Z|uz2brSq&?pKBo0k!)_k5e?W6`fh#p$rBZLH)LSZbkUC%6 zSN9*(M-3`*QwMQU2fDpTxpHSJwFDC`SDz@=XMWU|){ErtGH%9vgn7r#PZaF4AsFYo zHyRe7%Xu-zNvnVVKB_-?>_0_XaD1Udt9!DPdLHxFFGz@AU)`Sis`&YR!uj6j<4k?F zQbRvC(1o6)L|1?1@+K;8Nq^;Cn5?|e#alDHMYWcpDQj(#kqc@`;E{~o8&%x%-G@%@t4 zZify%esd{8`b!yWoIFS!)kLKa9qA@b_Tn{N{Ym@RUni3*Pi z*Oe%BD`usgrpcG-A5I&c%QB(>v%&UL3NH6Iw?yW13TrdLxd&{Xi z1Z14Bavf_KCLDG^j2bX4Ne#F;p}?j4qutMj$D2B&Zim-&)t^JF*RMb`(3L2N?VgA9 zp%WA6D;KF@3k&Ek^VBfc`O4HhnOVblL8e^86V&iPD(zzk?PIVS?i!#>uf$D{iS%#k zb13y`_wVNZCuldnLJs9*1ZA9dWBNP&yu=<)=cjZ;_V?v1xqgNDi=FR@;JYwG>^|U1 zajO)@mK4U86xveCl>W{AkGI?J(BWq=>i>Y5;)K`vC+!l(*@fY8w%OGq|1KF{Ih1e> zaWlsERYMj6skoRm1Nj|E>M^dzzD~6AKg4<7vbFWlUo18OFRcY|4-h zLpxLF(oeRs6M7rtJ|-~{mmaGaqsUL{G`C8fV)sQU7jaO=Rx`VGjSWBk9%BQhD-Oa@ zC#lp)Ds&-^>Y?cgYUH%L)JWIus{3q1qSW>N7}6djeX}2ZGl{;Ls0Q7fT&-!bFrG1h zaey(v_+j26e}l;1p!v2R>d?curTyss>el_Wuh5P$$*F_ITTyR_DWDDny2i$Lh+95aM;2Ttu*(=%LpIGl%Y{gmgvglZ>USHCFLZ%Vv)(e0)u>`AZ3pI2%J zM%s$N{zKwvgRC_e2Zqca*x|GWhenGIDD_9oqc)99AB$K=F#kGzOyb;gkn!mSrCxPt zdNO1E%?Yi2_s2EIR>u@Z7eu8CO}l8(HNOu%GeM1;_KoOquI16awJGl~^7|$2_6My> zJ&keN?TO~TEB~O>Z!yl?XWDWJZTV}xw&fPatuIS=`}<10k8#pVm~)T#81>lyP;k5VVO8qHdferUe&1l`l!_)F}g66srs z^UeCuH8N3+4D?qcOOol+{nW^=G2dS6bQ?cfSp%IYudR~Tp;Hso=s>A!bV-S8^t58v zXxGz7)@6QM zrV8#-&5pb~Ulw+oqq_XqUN!iSe7vE{f8^s09sak;$B%SHii0+};JeN-{GmK{)Qi=G zm<6T6AS@^flr2`*@)gOgg?nc>xN3`{{{b*X*tc{w}+L*u_QVfw@&R z3t%)y6x>0Nv!l^KXP`BFU4aekD>Pi!;#1xt_TfT*hog?g9rEU?5EC__%Kb0~_J{PX8 zE>)T0I;X0#wyL6ZPN1g3#8RU!)%L-f8ki>83 zj#*S$rkg}b&Z=TWzX=Zkh*YWjrJN^pj*8B$%`ROQT(P3Grl6*@7GkJVV&(@bE-t5% ziYgXW!nb0-Gg9pGs;aIGR?mf1E(wrnVG5;+%bcQWO89(N@`42punm8KtTHlJ;YI8{#E8#scxLDh2n=VTL+@7t?@rvs7y&4dY@6qz+O86{UfmROHZWK}9L@ z{F9^e=HwSu(~4eHm z>RPTqEG#FTT1inb^=*565sSsj7oAsCRFYS|tcEKOl=?N@2IiLO_3<~_LlMN!&ee&RkDtBlgoV z^39a1zd26P-%M*d%zWE^femGLk@zpcNZKrZb-0y4FNUc}4acy+)cKcki2pi_M`QpfRX$lAEPCLe`0^%0hIjx93$!7jS+tjW28*aVZ{9vjJT&l6rqn8q07Ja zmwdvXN!NSA-@i6r|F>d4vGASA!HI>x{%_^*U!Tqin}9t_pRfsd|MhwMH>B{tyh#+~ znDv({Dn<_=`)vOY;s5zN-?{T7^`|?nJ2~j=@e9X)?HxMAMNB9cz4rCjyz27Tu6S)q z58sT(FC2Qa^%JGexYmS3RaWPm2w#5t-buC%vurrih8Z@TX2WzFrrFSI!&Do(ZFsbg zq4Rq-Y_;JVHauj*7j3xThR@ir#fH0W*lfecY`D#a57=<44Y%0vHXGh(!v-5V@vpJJ z12(L%VWAC|*wAmo3>&7~@N^q`ZRob)(O6UNzD)S82s(Gz_LdD>ZFtCr`)$}_!)6<9 zwc%zPZnEJj8y4EIz=jz%Ot)d04ZSu@wPCUi-8NJ67^?HGPnht$A)*?=`K|O{LVnuoY>z2TssI^0Ps5CKFk~7 z&j6E9R9ctjQiFiYFk8mDR0%L`2)ujz2%N`-=uO}Sz@=>5mx2pCG*YPtzy-dIkvNr? z^BzpW7?<(_zrZX6SED%3!bn;HVC-n(#NG|e!PJqi==^LH96vV#Cyp_AI&kh-(!#$V z*ou*~1b%OvDeq<=dcbs8fp=rX&lX_9cw?UkoMq!J!23@{R~d0W0PMtkB>6c_snalu z{G1LfJ{=x`&;*z;k>Y_T0#C&hh#%nBXaq~ZmjZWUq%6CE?_wkm9|6xzM=lThEZ{dW zLgzKWUt`42R^Z4plzNPp8@<4DFcNWNV zux2J@!A}4;->+am1XP&M*H9i5q}Ku zo3qhD1il7%6GrmC3HTbDjxy{;R_WCo@+mlQyB`@O@W+4y&nHgsrNA{92`lh+8yEOC zM)IaEpqerJ@t+R#V-A5A058J40bU3!!nA^y0H^06j|-jwtipT*UJZ=TC;!x4B9Lo1 zDj+X#0x!l$9+m+AhLL*z2v`SmOz0`F`cmq0Jn;ZeTS`9#KOOiOW+Ax1GcKp!flmVt zDB_F}96fnzCPw0~SfPi2)u3u>axM>fUYuQ9|L?9lY#vkz?5=hp9-90<9=Ys#%~1v4wH@lX5c3np~L6E zd#*6}y}-;0+8cfXz#n2H4=uoPRkSzoG~ksO$$tQNH%9zy0bT<$@m}yXz)vwP;GYAp zt2KBXFg9RtH*gb1>Pz6+LFyO(Gl36cWc=I)jJe7#FR%mSK9xAd?rPc!xWKqorXIb( zKC7uC?A^dTjFeH}6cji}|C$C|^G(WvAAvu_NdLMW*ol#{h`iJYjFiy}T#MO^|E<7d zn62PyEn4NTC7csuorkQM#|U%Z2AS?*lz+pd6%J23o!p~L)!x2w=fd_2H-x7ghel;ddJ2E zKJZK9U*J2xGGnR0`|mYl<^#ZA{Tf=4*1f>ZzcF))z(W|RFM-LwHMqcCm{$B3Y^7Y7 z_rPxf&fEt7cmiz(*l#=I2zWAZHb&~S8u&a$^0{B|M`<(o*$?dVn2FyDy!CNTeX-vR z{1Zm{y9J#5gu%0b7N!nA0`J=a9~}Gv;Q2eD8+ab@SGy=L_`Sf>c2j=vEMQI>x7rku!F9D8!#o%ec zGK}~an0d&w!A)nZ<0X~Kidx0O@_)*|RpHd&#F9hzx$e8d9Fzz$z2zzv)s?#tM zR_^J@y`#@*O9JJdkKh93uFO`(B7t%bM(hRdwsE-&Blk_jUZC775&r^*es1gqiVVK^ z5h(W^1Q#fG8w3|9_YedZ_%j=qy9jcRK4*h{2a#nJvb@yloP3GDZuz`pea_8lj%S3(5)7nyGI3GBTmuut#BUii0J*caT% z*bRKgB%m^W!5Bk+obSTB7)#w<-|pWs#!(55d-VgjkL&tQeT{D_*>P`v7yrcVe5d`D zZ_4C+Z{picB|G1@{f%)UBKs0G=XAPq+(wG`uFzr`X9(gjrpjK z_FrAv&cC#RzApYCf3>ImfUe8ufcq}EhZF<+PF+9$z=v569VjlzPus<7@JD_fl2=@! zAG1FAdvY~oDmW7gNdy>P7bv2I`E#>Uy+JV)B4FI9=huGACN(gM@qjymOP z`0RqLMdLappR=Ab9NVcZr{cb(DHr5w$TgAcB6|qs+zr`+d^0)k*s&wtql`D#4j!zY zc;X3-o00KFix#Qu>}<7Z(QqrGHd{VZ==Bd=Y z!n{~4U{Wzev3d9%#JuxXNW!?(?=M@o;6v(5r-zID5ER)6H9bUCb7asC)>WQO z9oA>ATgoT$C`j`OhUo^WMT-{7$Hxcn>F`ql0RbvF81D@Z57)dKG-!|-I&`Rd_~D1u zqmMqSW6gA=l zQ6WdM?RH$$paM}T$U{zw8vd=QQ71%={7%%YS+mqL&pe~j)6><9FTSXjELozKFJG=! zty-mCe)(lRzPY)%YV+pJ+LrCuu|pksD_70_LDc3VQE$KfwtDZq_tXa;e4q{;I;4&s zJ*ti$Kd!#`;tN%H`j|TMqo}XG`bx{i*|TTWrNVRS>z_mw6&0zgSFdVWAzN?r#X2(z zYXS(3>muwU~)GDr*gLnz~;|z8KkC3l3Fb3YO}0YN9B;Qr|KTSdq!dYPlWHj z2^BKU{7@CyOIr%0(o zfIkNKF9H8G;LifS5b#%=;C)+SehV($!{AyvcLCNqSbr701tmOOPsy=%O1>DR;4W z8a(E;sOo^f9q_dPUmNhSKSgkc#B#Tip6!&37^LKhBqcAVE7`JM$=v)O!SbZ;46_ zr9q;uB#FA3F6yWCqJB9fW$^a_z76050skQ2M*)5^;O7B;Nj*_(+KJjUNYtJrQTgej zK3^~D{Gn^`u7D>8?gjjPfNu);mVkc%@IC8^8rDu!(jZaulh9VWsJ!)}jvu-PU*0rW zOOx}>kdR)%y@Esat^TdOTQv9dY}u!*LU6B$@bJ*kh>*a*;81HDZ!fPF&HMCeW_45u z4aZ~r566elP^(WXR_N2m;&>hr84?x|h~~pWf&+U6TOVMB7A-uRI-Z9|1_eh1hlGXz zo|v_Lz{jVj14mYgh>S3)_kHdStZh+&Ej0IRYH0=}!`XgBM0j9?wMqSk?s$$0-i`__ zt$+^B10p4-{0t7M;GY9+RD34f6&kNLv$C0Yt|m_K!I1_ zpKiZh_eiifGAtre6QOZ5{0IKl&~`yYAtEx;+NgK$`t|FBh_dI_&bM2Ge8bSi06f3H zcW+H)G7FjkHFFheeiR2@_J9 z!b3Xt3h}M(QhR{o0eKZ33UlRnZtd#pR_k^bmwJ7HAyJHs2!tSAdmh}SlV68g)m-lm z$HS0bkmZgYLqe`Sw_1Az5A0s2W{uA1A;iko!$Yt2Zw;Z`4eWl`Z8d}VJR}g}8ezb} zKk^3x7Ia6wIs>gCk%8SIYh{n|+@!v4^}0p|gOA}(kIDZ~T?6;KbO`AVRdIf-+sF7u zghCL*!#Z`V@Ytz+40L#KxAM@?kMb$9T;s5>vMaT?Zd%|8I2XH+(@OK7@#j*}M#U_Z z`EqG0u!28#V?V|Ca}PfFp!P>*#`G!kJzkmGQ_YJDQ;U;B)TY^CDtGBj)ny-i3fQ^6 zpNJX?-+?~ERFp5g@Pb;lY?)fIVui}c$WW_Suhzc8#*G`bKlsKQZz%c-AMJWYErY+2 z3twUH-o5I>4?ont!Ksr4>g36j>eQ)I>iqfh>bvj0Q$PMtsC|RWmoKYdfBjXZUlp|; zzQUnjob0S`Q%rPSFwsFT7mdP1Hw_crLQHgPR6W_G+R08eNcO2DIik|#bG2ShWB$H2 z&nRC^h*9o<$7H+c0l;?#d=TIx06!e?Nq~PE@GAhn0r2kuzMyQL`LCY>_WjpS`9JBW zG*YFhQKLpsBJ5)}x{1JDSz5Rm%tKfTk_(tJWA#0n!B5=-;RDJzmXxe0_bqfIs?g?c>wj$ET@} zrmr!Q1*vIMeW_CGVlTcXD#BOR*#%`~iT6z%)(4#OKx_~N+;jC9vWv3d@1IJy0U92= z{`vyEc3sv$$)5jzr0Y?CqrQpn9mw~3cz85L$=n>dWvyDZumV!B4CEACX#9K`@?FS= zF0#oTl9!i5W`Q5Iafbg2;Q z(o>&*{`vZM-+lKvoEP%x(W3_>U(Z&C7 z;lqaqc1A+w`>>C=5bJ2k%gd8*zx`G)p9=O}xIrZa1qFijx9~Y=5Ujz!ICJLASX?fe&R<2*ae#X|VTW7%S3!5`%&cMXP#Lxu`7DU0XCT^{eeHt`q&;ffJkF;;!p1gAp z4Gr~XyZHQv@V|KRqQI9)Z`Q0?C-88=xpU_Pd%9(O&d<-6Pe1)slj(D^Uu zTPNVKfUtiQfA~{^zREK3H<^4jnPXxy@h7iXn$NsuHgvEgJ|+WaF&SenCYyVvycPW4 zdh4zE7)MXY#9Y#{XU`r@1LXtzO~Nt2K9iOg;z*r<4(syBkt4!P-Qakzq<&C$2=o5? z?`s-3hR}cEIAE`yTexKj*T3L?37TkmB< z?b`)oF7TPP4s#4RCfHZgnYi!Uw@;`m$BrG-pGgC40OgH(!Z9LlTfP^GIW7|Tp-9K~ zM10>C@!2iXc9+P5`$e)(i(I~P~XL|GI%{#$|UjTb47!OSYZ4SqP zd?h^`3*yet9RDxB{8DFrCeO)l;!9Z|EySDrr;KKPClZR^5Dz*m(j8myT@W+qun#os z6=@9`TD~db`G&}X&k-9aDOp|> zUQ&+8|H|kf-^p{15#@&RPrHjXn?`6L@bC9oDGj~wJwB>qOuDgPV~$`xh9&_W%joKfzn8>XaQFb)1nWB_Q0 zfc*Cczez*DL6NQ>l+xe_UBEuMc<1Trw4+r?8h_a0bnNkW!Z<9Tp3$~cMhE5Hc}$F4 zu_XWbobv)a>m>8GCr^Gvx+8ca5H zR5m7t7H2ws`Q;Zm^!f^Ud3uB_O$wA3CIo539{EOO$k!r+PKZR3hEE;(44LU+^qDpZ z`b?YD5_&*=ZUcFuPXC5DTO~AT{4w71v3KDKyFQn+&>pfhG_a)HQ?4u0VdRUlPFX5A zut{E-(nGQ)1;|S&Jv9v^;L)=p4?AdxDiG-poce->GJPfu4t;J*n*^KM0ye2BXzuX9df;C%a2FXm}YkWK1l@Kc!IQvGbtejL2U> z1NE6UDFQah=rd_B`b-*(KGP<7Z`b(aBvHB4kb1`X1%6gpCJiQ6q{HZnGadX)8kn}G zc9jj&yUDsIy34CmLBr$#xm#&LBozE6Xa4Mgr^GWEa;5KnD}#k1`Wo?@Y-1lT#oy&|FjoI{=0SS){iAq~zxhSq1;B&N#nhrQSG4}2C7cjCp8I1z8^1Nm&m zgf`yUKc+uK8Y%y8&Fd*|f`)CNVbcuI09~LylLqQDZPF6hRnoACG>q*fPmci&v7s{a z@i0vT@uZKz`PujwoiM*4{Z9UAGdKs+k6>QAcCFZKHlbf?vKbGSH_{XA%gVu*qGk7- zF0x}5Xn-zk1P!k_^qDp(V^U99k=#=jCkD#%2|==Oe6TzR8lD0Tvq8fQ(7;sD$M_p` z1L=46hxkJlwERP^=aQDEo_b2`cDts>(7^I~bdV;>7GcjE+9-SGc9C76VLNEh`V1Oq zlZ-yEf=ybU94Id)2Fbrb!vfIoENJ*Uq3Mr$3tH`c35h3bN13h8%Ouzv(!~5Rs6Ou%Eb6AE^T~XU^0#ILlW> zS}KkSuW1vg3pY!j>|IypV^E*};n3#_a5waSh(E`^Z{NNg2YL0?SGDg&p9cE_GI{dk zG8%3~$Bo8>_|PV8qD{J)K7$6PN_bt_t%l?i&f4sT?G!IBFR@y!g0mSyzb+*uMJ7(1 zDD&sf*LsaDA8mK&3spu7dC$3lwu-(G{ZXz5n5fVBYbHs_&sc9{P1f_XvyZXFq0ft; z3rv+EYwlsMqAq;>hj6X&Z_}ntXB_QGz)!WGhaauSAu}^m1`Zr3I9nwD_{Tp4F(rBa z`R6r0T;G%5q=9n*=LyOcGuJSrf%cSg$hnX_+WTCn9DarPUy=Q*Ctve1pbPXdsL!-X zOQ%K>{hO}8u=n>2LZP!LKallwtou9R9E=@0EO;jP?L>i22(SC~>!NJ`hm*~uWwX#?b`Kuoaxc~ zAneN4`b*B4G?;AYV9EBFDO09x+SU(NjFcUJ55oLh z>SJ(zw#^8Ut^XP=pS{0Du3Wx+1mj)h{Rf<-l(}=~Uc?zCnLT^<->u1QzAKL$Hu|u?a~GV_utUYTz7Z(AdGt^_MOfEX~ujQ*QrX<{)3T0 z`ag!R#HXO3K;wh475ElBRjl;^<1LUkEd!jR$Y<7P<~3=c9VJg`|2J&dAnVqx({tc! zuf3+>xJLj!oq=t86m#Ts;Jyd3w51vX1KPdg{#W-?)DXK0Iy z*X#c%?xa!UZ~TAodoF1(cG1vcXkbZx(>7u5*6Rey6z5uJ{t{PS6Mv44@gW%3q1;oJ z$aCrtY{p{XaVxl&;qNT}v=PqZQQ4S~F7C097%@U{E?3L9;kk3kdXy!~I`4B1AnqnU zf;G~LKY_c(pM9A1FXo;FluOP*q=Pz0KGA;A)^R>^9ux9*%a$#&bm>wp&*Znsq?@us z-J##aYsw7U<6Hon`3hdaaI1VL?o4|B!FgUJ{w9+KlW#O8qzPxD^?XGcBMfOHzLc#z z*iO=7aEE`o<`(6>6zgk$_5Kg^ORs-1f6pZ?H*|&HM;+^GUH4^L-Nz?f5J|b?f;Ml&YkpMX#Xe&oR2tnlE++glJ^`3 z`T}Mgcukv6TT45JHHD6Afad=+?xaJ@zq4#qlyh@!^wzngtn-?6I2M$7@|iSJ)*(l~ z!ACfQvEsbSGZuejZX$j+OLwCJ&mjE2%BMzX;lX({aa?r^L2eijPxvI?1IY`Waq?dMpP>9 z2co%36Z>+P6VxBe0uquwlkb8E<5qi}4lii8DU*(It_CpuGyQUJu%7>?=fL z?`bG;pzp-BC+A1nbE`31(P7~f&spD{M3>k*Ip z9$W)p4Vr-U!dT9~oY%O{Wxw>J$rA_t+IK2#vGgzFWshL*mhnEu$~F}_KdtmLgILb; zp!?@~kUj!4abTu>XC_ZLe_c3zTwa+oRL3Q_AI>!L0^(igO5>)3fdk{dJOjbKea3u^ zJTl(PIAaf#AC;F$56&rAzedB)9ZEb5E*yXQz{G{~&-NwpdQY1%XYR?H{)xlifCJ;6 zjGt?H1P+X~F~&oAWQ>ZjPR0ozhK?ZZbBu`x=W*=2B;fmZV>vf78yx6!kSFx5*Ub#k zu|CFUxR1!hzL^<&W&D^iKWBO5Sr*1<80%w8Bb0&EP@^ z?kjUIFbn&cjQQfbbTSJ%=8Oa7acBj3#QA-#6Ff^pd8`np^I-por$l34VW=}6?3;4V zl8Ny)#z+`1WSnZti-|Xl8J_!DB)I@FPvF4wJk$xsY?%!G^ed)Ko0gE8nmU#~D6_$X z`zGWC6Zh8{YhsLpu`w0HW(vgd};cq()_~gI3W&N9uapnv2V8L+&^sq zGkqIoXFP}t$A9v#2H?OwP40~|#zJ{yoQv@l#u{~u%hCUg$9uCYtrJEb1HcEQ zvhk;HhB!ntzIQp4wt(w>`sbvZxR3`d4`;>cxFO?JjNLM3$k_8rN4$_a!T1Pcg=vUU z86L1RXB*AfEmE2MQ@8Nl-UQhHvD8J{9j*(wwj)nyd&rMVXFiqv|BBLa0`5;!9vQ>u z-n7vP#+exFW1KA2fde~>DkzQPk3ip_U*(-L8{|R%k}^m=q|eWicrcS6%*2Cy;yBzW z9oh4mj+ru6Hy1cCURojj&i=9g=bn2m`lXj%8p_xZ{c++!o^xHzH8uB5i3fQ>d&Fl< zjNkGcfYFJ{ zO5xWZUz11Z1mo9~$7M4gX7`oO|FEs}w>{WDVi8{j2GXmWqGQu|r-D5D?6Z1(N1SLE zsRzV^JmC0{Hy6(wmk%?>$?J21^;rNTkD1eY%Z7g^%K1}pPOiyXIX3j{*|P)od#8~O z#4#s~969n`e1BWUjT@);fpJDs?_HR+4|z_0kpEoYQ5MJ#;$JbbP1HLK*>aw1%k|F- z#Knd{@AW#6b3NlSj4i>>5}fEGQbd2kI1 z|Kl8EiHu9&d#1wuSK^SAl%)Aid%$^+Z5tV2U$hHGS20hNATBc+vYY}R32&KEJo9w3 zHI)Z>1>P?jGiJ;?jPY=f9$)wujs@dun3r`w^asdy_Rmb8j6RwvF<1Qzem;q|Rw7<; z0ey2RaI>W4k2=V<=-ZLs+{>j5axa~64eAH+G<#PZ1KI_`5f}1;cAGYnc;@BhEkwt4 zZq_-TYyC(3HX7ff8_K@fCdZjL;5`9?_X@~>0RuE{#DST0r~|A=xuKs#d%<&w*bC1< zyId<&C29Lh`-5}zW7%E-_T)L|)8;U?fOi(?7&G;P_%V?WW{;QtGGi+A+d;!}bXwKE ziJ$J@$TRuMOgxy`ALk>yBSG5+o>e97lsS$Uc}==$ld<=*<`4F*`)5C}HQN4HhKKQi z@tp&~_Z{_KG5tdBIZ+<}MBlo9(re~l$`a{io6NL%)H&)l>7Ah^jGA&GygdT%(T6@J6N2)00sCRkpbhSy+-l-?P26rVQ@?Iz->!>Si3h&3 z>r(c8U5_;I0@(#wRUxyUf$;zcb0F@SoQluPl2*w(Skiv?yRD;Hm1C;!y05T{5qa%48+=M8PkqCyxb~s1%k_qR`7^>cxDM|NJUD)M_l%Bn(}s}`C-!E@ zmb3u9o}}+izn$v_uAOGY^wE6`E)ZFZbuGT9wR@ZM}!@DfqO0W3-wcGHFbzJq^*Q()J z=@s9-Rvm9N;*~|ed98+{CazHDc1KN%e(PFIyjzX#-cU7IS@Aav?_n8?x5o@r18^OV zzrFF9>CNhe@C{w`KN=){Vj0MXNY(KVXq8K`@FHE%-bDObR-8&uqtRL%eo-q1Ehng0 zH37AI={kPOAKRr}kvrl&G(cWOz#VZ>A*d6Nx4T56{xo0{jjI^l$6&O?Cv44v_t!+L zNvJal?~aQ>Zx%H~rRYzr{5O2H)upWOP~1;JP2!XY+~UwjLY3MK!~1b!067Uz(ZG{^ zGquMreO+sRsO~q#soi#X(>k-CF3E3h4!Z(|e3+=~C!odgP83eWGN2>s$LK!sUToD2 z_50&`w600cB!VZTVl=3ph&F$J&jM#U1YfMQU=*s(FMo~OKu6mgIpSu_lf(*OpY0E8PC4~p3y1c z36m#`i%IshOo|I0H3~nY8{iomJw7GIvu6+AvbM_um$mOy<9EIH@GXaH58pDP0p7#c z@Uv%Ba8z(;X!w9W!-nAlM~Yv{K9+d`zwcC(>6+!16_qtCOS$14)40EH)v;A)Ru!*u z&2Z1~&hXFZn=ve7OvcoVc^Qi{)@E$W*q3oE<4i_zhHIvKCV#o%C}7kow^eDcF3Mh< zy()Wcc5e2z>|NRWvJYk-%RZ5PCc7xRI9u6VZEiMqo5kjB^RxNef^2G}}DeBHLoyD%)CHu5Fubmu;WzpzWCLgss@7?5=h(SDj=|%}mRjm$@i& zaptPbwVAn@+cI}$?#n!wc}(-II8$YjckWr1EblD8EdQ*&nvaiVjmb*PnwpiCMaKL$ z{g(s(yExFSX-KybLsIaI(Idhi8W)`~B6MtY!srRT#!nbMB7Ry-!sHQyMB9@-|!jz+Pm(@6*E_&R%?88Tms&amN>1MrA_NL z7X0Qqe$Km_WzxjhHvYU*J1#zPLh?jQa*X+uGA*TcZ1RM7OJZDNTdV%L_Jrv1ZAXod zYsg literal 0 HcmV?d00001 diff --git a/src/myenv/Scripts/python.exe b/src/myenv/Scripts/python.exe new file mode 100644 index 0000000000000000000000000000000000000000..8655d9d5a5ce275d03737ec3dc733c7591ca4b8d GIT binary patch literal 268568 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 literal 0 HcmV?d00001 diff --git a/src/myenv/Scripts/pythonw.exe b/src/myenv/Scripts/pythonw.exe new file mode 100644 index 0000000000000000000000000000000000000000..b19f1fde5e2f997e341a6e7ae14897785b1ad757 GIT binary patch literal 257304 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

      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/owntracks/__init__.py b/src/owntracks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/owntracks/admin.py b/src/owntracks/admin.py new file mode 100644 index 0000000..655b535 --- /dev/null +++ b/src/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/owntracks/apps.py b/src/owntracks/apps.py new file mode 100644 index 0000000..1bc5f12 --- /dev/null +++ b/src/owntracks/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OwntracksConfig(AppConfig): + name = 'owntracks' diff --git a/src/owntracks/migrations/0001_initial.py b/src/owntracks/migrations/0001_initial.py new file mode 100644 index 0000000..9eee55c --- /dev/null +++ b/src/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/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py new file mode 100644 index 0000000..b4f8dec --- /dev/null +++ b/src/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/owntracks/migrations/__init__.py b/src/owntracks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/owntracks/models.py b/src/owntracks/models.py new file mode 100644 index 0000000..760942c --- /dev/null +++ b/src/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/owntracks/tests.py b/src/owntracks/tests.py new file mode 100644 index 0000000..3b4b9d8 --- /dev/null +++ b/src/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/owntracks/urls.py b/src/owntracks/urls.py new file mode 100644 index 0000000..c19ada8 --- /dev/null +++ b/src/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/owntracks/views.py b/src/owntracks/views.py new file mode 100644 index 0000000..4c72bdd --- /dev/null +++ b/src/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/plugins/__init__.py b/src/plugins/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/plugins/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/plugins/article_copyright/__init__.py b/src/plugins/article_copyright/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/plugins/article_copyright/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/plugins/article_copyright/plugin.py b/src/plugins/article_copyright/plugin.py new file mode 100644 index 0000000..317fed2 --- /dev/null +++ b/src/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/plugins/external_links/__init__.py b/src/plugins/external_links/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/plugins/external_links/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/plugins/external_links/plugin.py b/src/plugins/external_links/plugin.py new file mode 100644 index 0000000..5b2ef14 --- /dev/null +++ b/src/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/plugins/reading_time/__init__.py b/src/plugins/reading_time/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/plugins/reading_time/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/plugins/reading_time/plugin.py b/src/plugins/reading_time/plugin.py new file mode 100644 index 0000000..35f9db1 --- /dev/null +++ b/src/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/plugins/seo_optimizer/__init__.py b/src/plugins/seo_optimizer/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/plugins/seo_optimizer/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/plugins/seo_optimizer/plugin.py b/src/plugins/seo_optimizer/plugin.py new file mode 100644 index 0000000..b5b19a3 --- /dev/null +++ b/src/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/plugins/view_count/__init__.py b/src/plugins/view_count/__init__.py new file mode 100644 index 0000000..8804fdf --- /dev/null +++ b/src/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/plugins/view_count/plugin.py b/src/plugins/view_count/plugin.py new file mode 100644 index 0000000..15e9d94 --- /dev/null +++ b/src/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/requirements.txt b/src/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/servermanager/MemcacheStorage.py b/src/servermanager/MemcacheStorage.py new file mode 100644 index 0000000..38a7990 --- /dev/null +++ b/src/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/servermanager/__init__.py b/src/servermanager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/servermanager/admin.py b/src/servermanager/admin.py new file mode 100644 index 0000000..f26f4f6 --- /dev/null +++ b/src/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/servermanager/api/__init__.py b/src/servermanager/api/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/servermanager/api/__init__.py @@ -0,0 +1 @@ + diff --git a/src/servermanager/api/blogapi.py b/src/servermanager/api/blogapi.py new file mode 100644 index 0000000..8a4d6ac --- /dev/null +++ b/src/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/servermanager/api/commonapi.py b/src/servermanager/api/commonapi.py new file mode 100644 index 0000000..83ad9ff --- /dev/null +++ b/src/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/servermanager/apps.py b/src/servermanager/apps.py new file mode 100644 index 0000000..03cc38d --- /dev/null +++ b/src/servermanager/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ServermanagerConfig(AppConfig): + name = 'servermanager' diff --git a/src/servermanager/migrations/0001_initial.py b/src/servermanager/migrations/0001_initial.py new file mode 100644 index 0000000..bbdbf77 --- /dev/null +++ b/src/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/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py new file mode 100644 index 0000000..4858857 --- /dev/null +++ b/src/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/servermanager/migrations/__init__.py b/src/servermanager/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/servermanager/models.py b/src/servermanager/models.py new file mode 100644 index 0000000..4326c65 --- /dev/null +++ b/src/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/servermanager/robot.py b/src/servermanager/robot.py new file mode 100644 index 0000000..7b45736 --- /dev/null +++ b/src/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/servermanager/tests.py b/src/servermanager/tests.py new file mode 100644 index 0000000..22a6689 --- /dev/null +++ b/src/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/servermanager/urls.py b/src/servermanager/urls.py new file mode 100644 index 0000000..8d134d2 --- /dev/null +++ b/src/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/servermanager/views.py b/src/servermanager/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/src/servermanager/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/src/templates/account/forget_password.html b/src/templates/account/forget_password.html new file mode 100644 index 0000000..3384531 --- /dev/null +++ b/src/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/templates/account/login.html b/src/templates/account/login.html new file mode 100644 index 0000000..cff8d33 --- /dev/null +++ b/src/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/templates/account/registration_form.html b/src/templates/account/registration_form.html new file mode 100644 index 0000000..65e7549 --- /dev/null +++ b/src/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/templates/account/result.html b/src/templates/account/result.html new file mode 100644 index 0000000..23c9094 --- /dev/null +++ b/src/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/templates/blog/article_archives.html b/src/templates/blog/article_archives.html new file mode 100644 index 0000000..959319e --- /dev/null +++ b/src/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/templates/blog/article_detail.html b/src/templates/blog/article_detail.html new file mode 100644 index 0000000..a74a0db --- /dev/null +++ b/src/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/templates/blog/article_index.html b/src/templates/blog/article_index.html new file mode 100644 index 0000000..0ee6150 --- /dev/null +++ b/src/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/templates/blog/error_page.html b/src/templates/blog/error_page.html new file mode 100644 index 0000000..d41cfb6 --- /dev/null +++ b/src/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/templates/blog/links_list.html b/src/templates/blog/links_list.html new file mode 100644 index 0000000..ccecbea --- /dev/null +++ b/src/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/templates/blog/tags/article_info.html b/src/templates/blog/tags/article_info.html new file mode 100644 index 0000000..3deec44 --- /dev/null +++ b/src/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/templates/blog/tags/article_meta_info.html b/src/templates/blog/tags/article_meta_info.html new file mode 100644 index 0000000..cb6111c --- /dev/null +++ b/src/templates/blog/tags/article_meta_info.html @@ -0,0 +1,59 @@ +{% load i18n %} +{% load blog_tags %} + + + + + diff --git a/src/templates/blog/tags/article_pagination.html b/src/templates/blog/tags/article_pagination.html new file mode 100644 index 0000000..95514ff --- /dev/null +++ b/src/templates/blog/tags/article_pagination.html @@ -0,0 +1,17 @@ +{% load i18n %} + \ No newline at end of file diff --git a/src/templates/blog/tags/article_tag_list.html b/src/templates/blog/tags/article_tag_list.html new file mode 100644 index 0000000..c8ba474 --- /dev/null +++ b/src/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/templates/blog/tags/breadcrumb.html b/src/templates/blog/tags/breadcrumb.html new file mode 100644 index 0000000..67087d5 --- /dev/null +++ b/src/templates/blog/tags/breadcrumb.html @@ -0,0 +1,19 @@ + + diff --git a/src/templates/blog/tags/sidebar.html b/src/templates/blog/tags/sidebar.html new file mode 100644 index 0000000..f70544c --- /dev/null +++ b/src/templates/blog/tags/sidebar.html @@ -0,0 +1,136 @@ +{% load blog_tags %} +{% load i18n %} + diff --git a/src/templates/comments/tags/comment_item.html b/src/templates/comments/tags/comment_item.html new file mode 100644 index 0000000..ebb0388 --- /dev/null +++ b/src/templates/comments/tags/comment_item.html @@ -0,0 +1,34 @@ +{% load blog_tags %} +
    1. +
      + + + +

      {{ comment_item.body|escape|comment_markdown }}

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

      + {% if comment_item.parent_comment %} +

      + {% endif %} +

      + +

      {{ comment_item.body|escape|comment_markdown }}

      + + +
      + +
    4. +{% 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/templates/comments/tags/comment_list.html b/src/templates/comments/tags/comment_list.html new file mode 100644 index 0000000..4092161 --- /dev/null +++ b/src/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/templates/comments/tags/post_comment.html b/src/templates/comments/tags/post_comment.html new file mode 100644 index 0000000..3ae5a27 --- /dev/null +++ b/src/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/templates/oauth/bindsuccess.html b/src/templates/oauth/bindsuccess.html new file mode 100644 index 0000000..4bee77c --- /dev/null +++ b/src/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/templates/oauth/oauth_applications.html b/src/templates/oauth/oauth_applications.html new file mode 100644 index 0000000..a841ad2 --- /dev/null +++ b/src/templates/oauth/oauth_applications.html @@ -0,0 +1,13 @@ +{% load i18n %} + diff --git a/src/templates/oauth/require_email.html b/src/templates/oauth/require_email.html new file mode 100644 index 0000000..3adef12 --- /dev/null +++ b/src/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/templates/owntracks/show_log_dates.html b/src/templates/owntracks/show_log_dates.html new file mode 100644 index 0000000..7dbba21 --- /dev/null +++ b/src/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/templates/owntracks/show_maps.html b/src/templates/owntracks/show_maps.html new file mode 100644 index 0000000..3aeda36 --- /dev/null +++ b/src/templates/owntracks/show_maps.html @@ -0,0 +1,135 @@ + + + + + + + 运动轨迹 + + + +
      + + + + + + + + \ No newline at end of file diff --git a/src/templates/search/indexes/blog/article_text.txt b/src/templates/search/indexes/blog/article_text.txt new file mode 100644 index 0000000..4f9ca76 --- /dev/null +++ b/src/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/templates/search/search.html b/src/templates/search/search.html new file mode 100644 index 0000000..1404c60 --- /dev/null +++ b/src/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/templates/share_layout/adsense.html b/src/templates/share_layout/adsense.html new file mode 100644 index 0000000..8f99c55 --- /dev/null +++ b/src/templates/share_layout/adsense.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/templates/share_layout/base.html b/src/templates/share_layout/base.html new file mode 100644 index 0000000..75d0df5 --- /dev/null +++ b/src/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/templates/share_layout/base_account.html b/src/templates/share_layout/base_account.html new file mode 100644 index 0000000..c00d842 --- /dev/null +++ b/src/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/templates/share_layout/footer.html b/src/templates/share_layout/footer.html new file mode 100644 index 0000000..cd86a29 --- /dev/null +++ b/src/templates/share_layout/footer.html @@ -0,0 +1,56 @@ + + + diff --git a/src/templates/share_layout/nav.html b/src/templates/share_layout/nav.html new file mode 100644 index 0000000..24d4da6 --- /dev/null +++ b/src/templates/share_layout/nav.html @@ -0,0 +1,30 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/src/templates/share_layout/nav_node.html b/src/templates/share_layout/nav_node.html new file mode 100644 index 0000000..c266880 --- /dev/null +++ b/src/templates/share_layout/nav_node.html @@ -0,0 +1,19 @@ + + +

      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' % + (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/oauth/apps.py b/src/oauth/apps.py new file mode 100644 index 0000000..17fcea2 --- /dev/null +++ b/src/oauth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OauthConfig(AppConfig): + name = 'oauth' diff --git a/src/oauth/forms.py b/src/oauth/forms.py new file mode 100644 index 0000000..0e4ede3 --- /dev/null +++ b/src/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/oauth/migrations/0001_initial.py b/src/oauth/migrations/0001_initial.py new file mode 100644 index 0000000..3aa3e03 --- /dev/null +++ b/src/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/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py new file mode 100644 index 0000000..d5cc70e --- /dev/null +++ b/src/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/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/oauth/migrations/0003_alter_oauthuser_nickname.py new file mode 100644 index 0000000..6af08eb --- /dev/null +++ b/src/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/oauth/migrations/__init__.py b/src/oauth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/oauth/models.py b/src/oauth/models.py new file mode 100644 index 0000000..be838ed --- /dev/null +++ b/src/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/oauth/oauthmanager.py b/src/oauth/oauthmanager.py new file mode 100644 index 0000000..2e7ceef --- /dev/null +++ b/src/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/oauth/templatetags/__init__.py b/src/oauth/templatetags/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/oauth/templatetags/__init__.py @@ -0,0 +1 @@ + diff --git a/src/oauth/templatetags/oauth_tags.py b/src/oauth/templatetags/oauth_tags.py new file mode 100644 index 0000000..7b687d5 --- /dev/null +++ b/src/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/oauth/tests.py b/src/oauth/tests.py new file mode 100644 index 0000000..bb23b9b --- /dev/null +++ b/src/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/oauth/urls.py b/src/oauth/urls.py new file mode 100644 index 0000000..c4a12a0 --- /dev/null +++ b/src/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/oauth/views.py b/src/oauth/views.py new file mode 100644 index 0000000..12e3a6e --- /dev/null +++ b/src/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 = _(''' +