176 Commits
Author SHA1 Message Date
liangliang 3b61b80598 修改侧边栏功能,增加分页测试。 close #59 2017-12-17 01:58:31 +08:00
liangliang 451f3983e0 closes #60, closes #61 2017-12-16 02:29:14 +08:00
车亮亮 75525b107a Merge pull request #56 from liangliangyy/dev
Fix #55
2017-12-10 00:04:23 +08:00
liangliang 10f2c66bb8 Fix #55 2017-12-09 23:55:10 +08:00
liangliangyy a58e5fb014 修改百度站长提交链接 2017-12-08 18:17:14 +08:00
车亮亮 fa049a5ea4 Update README.md 2017-12-07 16:40:14 +08:00
车亮亮 7e54996527 Merge pull request #51 from liangliangyy/dev
增加错误页面🙅‍♂️
2017-11-25 14:46:45 +08:00
liangliang 956ba2a167 增加错误页面🙅‍♂️ 2017-11-25 14:33:33 +08:00
车亮亮 6d30d40b86 Merge pull request #50 from liangliangyy/dev
升级依赖包
2017-11-25 13:40:28 +08:00
liangliang 6ccba22b91 升级依赖包 2017-11-25 13:26:25 +08:00
车亮亮 63c5ae8797 更新readme Fix #49 2017-11-15 18:30:24 +08:00
车亮亮 68ba79add6 Merge pull request #46 from liangliangyy/dev
修改用户默认头像👮‍♀️
2017-11-12 12:22:33 +08:00
liangliang bbbcb175c9 修改用户默认头像👮‍♀️ 2017-11-12 12:14:32 +08:00
车亮亮 062ad09ae0 Merge pull request #45 from liangliangyy/dev
删掉无用输出,增加文章标签长度判断,Fix #44
2017-11-11 20:58:53 +08:00
liangliang 521486d405 删掉无用输出,增加文章标签长度判断,Fix #44 2017-11-11 20:51:01 +08:00
车亮亮 2a7a96d566 Merge pull request #42 from liangliangyy/dev
修改oauth用户
2017-11-04 12:59:23 +08:00
liangliang b8e54d131b 修改oauth用户 2017-11-04 11:30:11 +08:00
liangliang 51f6b48b98 增加gitbook链接 2017-10-31 22:51:53 +08:00
车亮亮 b39ed27543 Merge pull request #40 from liangliangyy/dev
完善测试功能,覆盖更多功能✌️
2017-10-28 16:24:23 +08:00
liangliang 5ce213862d 完善测试功能,覆盖更多功能✌️ 2017-10-28 16:09:16 +08:00
车亮亮 ed4f96b1d4 Merge pull request #38 from liangliangyy/dev
修改cnzz统计为百度统计
2017-10-10 09:08:40 -05:00
liangliang e7ed787ab0 修改cnzz统计为百度统计 2017-10-10 21:45:19 +08:00
liangliang 90ac06a675 修改cnzz统计为百度统计 2017-10-10 21:43:39 +08:00
车亮亮 b7cd5e9a2d Merge pull request #37 from liangliangyy/dev
增加Facebook登录支持
2017-10-09 08:37:14 -05:00
liangliang d65284fb80 增加Facebook登录支持 2017-10-09 21:26:56 +08:00
车亮亮 1c7759738e Merge pull request #36 from liangliangyy/dev
自动清除session文件,修复创建oauth用户名过长bug Fix #34
2017-09-29 10:48:05 -05:00
liangliang 4076b99ff7 自动清除session文件,修复创建oauth用户名过长bug Fix #34 2017-09-29 23:35:04 +08:00
车亮亮 a360b6a1b8 Merge pull request #33 from liangliangyy/dev
界面修改
2017-09-24 01:06:53 +08:00
liangliang 711259beef 界面修改 2017-09-24 00:55:36 +08:00
车亮亮 b80eb18510 Update .coveragerc 2017-09-08 18:21:33 +08:00
车亮亮 58b32031be Merge pull request #31 from liangliangyy/dev
增加测试
2017-09-07 22:50:22 +08:00
liangliang 3067586803 增加测试 2017-09-07 22:40:30 +08:00
车亮亮 f36d5b4856 Update README.md 2017-09-03 17:20:30 +08:00
liangliang 1326c4ea15 update helpinfo 2017-09-03 16:22:09 +08:00
liangliang 1af0823d65 update encode 2017-09-03 16:20:41 +08:00
车亮亮 09f3e50be0 Merge pull request #30 from liangliangyy/weixin
增加微信功能
2017-09-03 15:36:13 +08:00
liangliang 9ba3393afb update travis 2017-09-03 15:25:59 +08:00
liangliang 0268b4beb5 update 2017-09-03 15:20:25 +08:00
liangliang b41b5c210d 完善微信管理功能 2017-09-03 14:51:16 +08:00
liangliang 2e4c61a5c8 增加微信功能 2017-08-27 02:58:32 +08:00
车亮亮 86d2318fed Update README.md 2017-08-24 16:44:17 +08:00
liangliang 1805f8e81e Merge branch 'master' of github.com:liangliangyy/DjangoBlog 2017-08-13 04:33:48 +08:00
liangliang 588d38d127 Update Readme, fixes #28 2017-08-13 04:33:34 +08:00
车亮亮 75243b4b84 Merge pull request #29 from liangliangyy/dev
精简代码,将文章,评论等保存后续动作修改为信号方式
2017-08-13 04:20:51 +08:00
liangliang f6ab2b7ccb 精简代码,将文章,评论等保存后续动作修改为信号方式 2017-08-13 04:12:17 +08:00
liangliang 7e5c3eac12 修改邮件服务器至zoho 2017-08-04 21:04:00 +08:00
车亮亮 2a9b9cb97c Merge pull request #26 from liangliangyy/dev
完善oauth登录功能
2017-07-22 12:31:26 +08:00
liangliang 249fdf10f3 完善oauth登录功能 2017-07-22 12:21:36 +08:00
车亮亮 8fb436649c Create README.md 2017-06-13 16:58:20 +08:00
车亮亮 85a8e21bf8 Merge pull request #25 from liangliangyy/dev
评论添加markdown支持
2017-06-11 10:53:33 +08:00
liangliang c9c89db93e 评论添加markdown支持 2017-06-11 10:36:09 +08:00
车亮亮 68097d71be Merge pull request #24 from liangliangyy/dev
update requirements
2017-06-09 17:37:18 +08:00
liangliangyy 850ab5b7b0 update requirements 2017-06-09 17:22:34 +08:00
车亮亮 e057feff9b Merge pull request #23 from liangliangyy/dev
修复文章摘要截取错误
2017-06-08 14:38:38 +08:00
liangliangyy f6e75f8060 修复文章摘要截取错误 2017-06-08 14:30:20 +08:00
liangliang 42f3b4697a 修复上传路径错误 2017-05-20 11:35:32 +08:00
liangliang 12227a4534 升级依赖 2017-05-20 11:09:27 +08:00
车亮亮 f280d3f5b9 Merge pull request #22 from liangliangyy/dev
为上传的图片文件自动压缩
2017-05-20 11:06:56 +08:00
车亮亮 6c204f13f3 Update views.py 2017-05-20 11:04:41 +08:00
liangliang 63986ae152 升级依赖,修改文件上传路径 2017-05-20 11:03:21 +08:00
liangliang d9a3b29534 为上传的图片文件自动压缩 2017-05-20 10:57:54 +08:00
车亮亮 544a49c6d4 Merge pull request #21 from liangliangyy/dev
update requirements
2017-05-12 09:55:49 +08:00
liangliangyy 041c5c79ff update requirements 2017-05-12 09:46:20 +08:00
车亮亮 6f787e68bb Merge pull request #20 from liangliangyy/dev
优化js加载顺序
2017-05-11 19:10:27 +08:00
liangliangyy dc2085a694 优化js加载顺序 2017-05-11 18:17:46 +08:00
车亮亮 fcc34bb48a Merge pull request #19 from liangliangyy/dev
修复link错误
2017-05-09 23:35:24 +08:00
liangliang 2f68f3612a 修复link错误 2017-05-09 23:34:45 +08:00
车亮亮 b797e8b105 Merge pull request #18 from liangliangyy/dev
升级依赖,修改travis测试
2017-05-09 23:15:14 +08:00
liangliang 7d30ddfef8 升级依赖,修改travis测试 2017-05-09 22:54:40 +08:00
车亮亮 4135c0bed2 Merge pull request #17 from liangliangyy/dev
添加返回顶部功能
2017-05-09 18:52:54 +08:00
liangliangyy 4bbba36259 添加返回顶部功能 2017-05-09 18:22:29 +08:00
车亮亮 cec36b5133 Merge pull request #16 from liangliangyy/dev
删除无用代码
2017-04-21 23:15:48 +08:00
liangliang bef0ff7f4f 删除无用代码 2017-04-21 23:05:03 +08:00
车亮亮 e597d7fdcb Merge pull request #14 from liangliangyy/dev
添加oauth提示页面
2017-04-15 23:39:28 +08:00
liangliang 67bf36d738 添加oauth提示页面 2017-04-15 23:38:19 +08:00
车亮亮 4130b6ed23 Merge pull request #13 from liangliangyy/dev
优化后台文章显示界面
2017-04-09 16:58:13 +08:00
liangliang 6b69e62a71 优化后台文章显示界面 2017-04-09 16:51:23 +08:00
车亮亮 31fe7a9a5d Merge pull request #12 from liangliangyy/dev
添加标签云功能
2017-04-09 16:40:39 +08:00
liangliang 818b8fe7de 测试 2017-04-09 16:35:49 +08:00
liangliang d2df0107a2 完善测试 2017-04-09 16:26:36 +08:00
liangliang 3e9fe20ddc 添加标签云功能 2017-04-09 15:37:58 +08:00
liangliangyy 0e0c63bd6d 添加migration 2017-04-06 19:44:34 +08:00
车亮亮 460271895c Merge pull request #11 from liangliangyy/dev
修改sitemap以及管理后台用户过滤功能
2017-04-06 15:12:38 +08:00
liangliangyy ae079aa000 修改sitemap以及管理后台用户过滤功能 2017-04-06 15:00:07 +08:00
车亮亮 1a31513a7f Merge pull request #10 from liangliangyy/dev
优化页面链接
2017-04-05 23:35:23 +08:00
liangliang 43e638611a 优化页面链接 2017-04-05 23:29:23 +08:00
车亮亮 3b9ba0e43d Merge pull request #9 from liangliangyy/dev
完善测试,为非本站链接自动添加nofollow
2017-03-30 23:20:35 +08:00
liangliang 5e0d7c40a5 完善测试,为非本站链接自动添加nofollow 2017-03-30 23:11:40 +08:00
车亮亮 30615ed61d Update README.md 2017-03-29 17:04:31 +08:00
车亮亮 3bd6916f48 Update .travis.yml 2017-03-28 16:36:52 +08:00
车亮亮 bd61109358 Update .travis.yml 2017-03-28 16:29:21 +08:00
liangliangyy 1f0515018c Merge branch 'master' of github.com:liangliangyy/DjangoBlog 2017-03-28 15:58:11 +08:00
liangliangyy 1c0ceacfd7 删除项目无用文件 2017-03-28 15:57:43 +08:00
车亮亮 aa5fd5d587 Update README.md 2017-03-27 17:39:08 +08:00
车亮亮 af010c59f5 Update README.md 2017-03-27 17:34:49 +08:00
车亮亮 aca4e69042 Merge pull request #7 from liangliangyy/dev
修复oauth用户绑定错误bug
2017-03-26 22:06:47 +08:00
liangliang bb4cde4f28 修复oauth用户绑定错误bug 2017-03-26 21:49:24 +08:00
车亮亮 f25e16e509 Update README.md 2017-03-26 17:43:05 +08:00
车亮亮 2131f3efba Merge pull request #6 from liangliangyy/dev
修复分类目录中文字符编码错误
2017-03-26 17:28:06 +08:00
liangliangyy f722e43b4d 修复分类目录中文字符编码错误 2017-03-26 17:21:25 +08:00
车亮亮 27ac073bba Merge pull request #5 from liangliangyy/dev
分类目录页面优化,部分代码格式调整
2017-03-26 17:13:45 +08:00
liangliangyy d7274473fc 分类目录页面优化,部分代码格式调整 2017-03-26 16:59:58 +08:00
liangliangyy af9ea12a55 Merge branch 'master' into dev 2017-03-25 15:12:05 +08:00
liangliangyy 9bfb161516 修复oauth用户重复bug 2017-03-25 15:06:43 +08:00
车亮亮 e30b974629 Merge pull request #4 from liangliangyy/dev
修复oauth用户重复bug
2017-03-24 21:58:36 +08:00
liangliangyy 41ebc1ce0b 修复oauth用户重复bug 2017-03-24 21:56:24 +08:00
车亮亮 f2ced177f0 Merge pull request #3 from liangliangyy/dev
微博oauth暂存
2017-03-24 21:25:07 +08:00
liangliangyy c8567d0a1a 微博oauth暂存 2017-03-24 15:05:46 +08:00
车亮亮 efd165d83c Update README.md 2017-03-24 14:08:20 +08:00
车亮亮 ff02be904b Merge pull request #2 from liangliangyy/dev
修改后台评论界面
2017-03-24 10:57:50 +08:00
liangliangyy 28fa800453 修改后台评论界面 2017-03-24 10:49:40 +08:00
liangliangyy 387f1b6192 Travis settings 2017-03-11 16:17:07 +08:00
liangliangyy 99be726e95 Merge branch 'dev' 2017-03-11 16:09:14 +08:00
liangliangyy eb8e63a3d4 “修复url编码错误” 2017-03-11 15:45:13 +08:00
liangliangyy 840878574d 添加nprogress进度条 2017-03-11 15:20:32 +08:00
liangliangyy 69819ff7a5 第三方登录 2017-03-11 14:55:50 +08:00
liangliangyy 0d01e58038 完善oauth功能,删除注释掉的无用代码 2017-03-11 02:25:03 +08:00
liangliangyy dcf7dd31d3 oauth邮箱绑定功能 2017-03-07 22:32:16 +08:00
liangliangyy ebde6b6271 oauth暂存 2017-03-07 17:41:20 +08:00
liangliangyy 2fe5faf089 修改robots.txt 2017-03-07 11:20:00 +08:00
liangliangyy ea09e9abc2 oauth功能,暂存 2017-03-05 22:14:00 +08:00
liangliangyy 92348510d9 description优化 2017-03-01 16:52:42 +08:00
liangliangyy 75f5ff0031 侧边栏. 2017-03-01 14:17:24 +08:00
liangliangyy 74f0005901 description 优化 2017-02-28 23:23:31 +08:00
车亮亮 b90c902e37 seo优化 2017-02-28 22:10:32 +08:00
liangliangyy 56f64d5fd3 修复侧边栏缓存 2017-02-28 17:58:11 +08:00
liangliangyy 0db6a2805e 取消全站缓存.完善缓存功能 2017-02-28 17:43:56 +08:00
liangliangyy 9719c279cd 修复分类目录等分页错误bug 2017-02-27 16:03:54 +08:00
车亮亮 62bcf35f1a Update README.md 2017-02-24 12:49:08 +08:00
liangliangyy 936e9e65e9 update requirements 2017-02-24 12:44:14 +08:00
liangliangyy 1105b883b8 update requirements.txt 2017-02-24 11:53:40 +08:00
车亮亮 f150f54744 Update README.md 2017-02-24 11:44:10 +08:00
车亮亮 ef9c2bbe32 Update README.md 2017-02-24 11:17:22 +08:00
liangliangyy 1fab508aa6 测试travis.修改测试 2017-02-24 11:08:18 +08:00
liangliangyy d67206cc87 测试travis 2017-02-24 11:00:46 +08:00
车亮亮 3742457ad7 暂时删除travis 2017-02-24 00:56:13 +08:00
车亮亮 e2e14a9053 updae travis 2017-02-23 21:53:50 +08:00
车亮亮 ddd79e57e1 Merge branch 'dev' 2017-02-23 21:38:50 +08:00
车亮亮 a90642e80c Merge branch 'dev' of github.com:liangliangyy/DjangoBlog into dev 2017-02-23 21:31:58 +08:00
车亮亮 5459c2c14c 添加travis 2017-02-23 21:27:24 +08:00
liangliangyy 3510c5e24d Merge branch 'dev' 2017-02-23 09:28:16 +08:00
liangliangyy aebd9a33f3 修复标签页错误 2017-02-23 09:24:47 +08:00
车亮亮 4d8b50b472 Merge branch 'dev' 2017-02-21 23:19:25 +08:00
车亮亮 f80faa3672 部分功能优化,修复文章内页上篇下篇未显示bug 2017-02-21 23:16:26 +08:00
车亮亮 da4df295ed Merge remote-tracking branch 'origin/dev' 2017-02-19 12:32:36 +08:00
车亮亮 3115f0ab83 缓存完善 2017-02-19 12:25:04 +08:00
车亮亮 8b5aab59a8 关闭debug 2017-02-17 23:01:17 +08:00
车亮亮 a99446fcac 修复评论若干bug及界面显示错误 2017-02-17 22:55:47 +08:00
车亮亮 af16e083c9 谷歌oauth 2017-02-15 20:57:01 +08:00
车亮亮 1c1449c4f3 Merge pull request #1 from liangliangyy/dev
修复侧边栏评论地址显示bug
2017-02-13 17:40:16 +08:00
liangliangyy 8b33d28795 修复侧边栏评论地址显示bug 2017-02-13 17:39:40 +08:00
车亮亮 9994e84597 添加js/css压缩 2017-02-12 14:41:40 +08:00
车亮亮 c42f708cb5 添加js/css压缩 2017-02-12 14:25:31 +08:00
liangliangyy 6de281b96c 不显示谷歌广告 2017-02-07 16:31:46 +08:00
车亮亮 50d18e3c2e 修复ping_baidu命令错误 2017-02-05 22:17:26 +08:00
车亮亮 99284605ee 为分类目录等名称添加惟一 2017-01-29 20:27:36 +08:00
liangliangyy f2a7f59e12 修改缓存配置 2017-01-23 14:16:19 +08:00
liangliangyy b86eb3747d 添加log处理 2017-01-23 14:08:28 +08:00
liangliangyy dd2245dc63 Merge branch 'master' of github.com:liangliangyy/DjangoBlog 2017-01-19 20:55:05 +08:00
车亮亮 7a6f44a1c4 修复部分代码合并问题 2017-01-19 20:50:56 +08:00
车亮亮 efb1487b6d 修复统计bug 2017-01-19 20:43:04 +08:00
liangliangyy 0d5619aab8 Merge branch 'dev' 2017-01-19 18:32:28 +08:00
liangliangyy 544d56b364 添加页面加载耗时记录 2017-01-19 17:27:24 +08:00
liangliangyy 95c7460fea Merge branch 'dev' 2017-01-19 16:37:55 +08:00
liangliangyy e43a68de17 页面头部url错误修复 2017-01-19 16:33:11 +08:00
liangliangyy 3cab6e4586 Merge branch 'dev' 2017-01-19 16:12:06 +08:00
liangliangyy 36c45f7ad8 缓存优化 2017-01-19 16:08:07 +08:00
liangliangyy 00f995eb02 缓存优化 2017-01-19 16:01:35 +08:00
车亮亮 6093274d68 完善自定义命令 2017-01-17 23:23:46 +08:00
liangliangyy b70f811d13 评论邮件创建线程发送 2017-01-17 18:15:14 +08:00
liangliangyy 24468243bf 评论邮件创建线程发送 2017-01-17 18:10:50 +08:00
liangliangyy 21961fa540 关闭debug.未处理异常邮件通知 2017-01-17 17:11:39 +08:00
liangliangyy f9be69f8c0 删除无用文件 2017-01-17 15:47:48 +08:00
liangliangyy 3892ad0aa7 添加ping_baidu 命令 2017-01-17 15:41:11 +08:00
车亮亮 02ea37086e 完善评论功能 2017-01-16 22:28:49 +08:00
liangliangyy 1629d09088 修复评论bug 2017-01-16 14:45:03 +08:00
96 changed files with 4480 additions and 2321 deletions
+13
View File
@@ -0,0 +1,13 @@
[run]
source = .
include = *.py
omit =
*migrations*
*tests*
*.html
*whoosh_cn_backend*
*apps*
*commands*
*oauth*
*travis_test*
*settings.py*
+5 -1
View File
@@ -70,4 +70,8 @@ collectedstatic/
DjangoBlog/whoosh_index/
google93fd32dbd906620a.html
baidu_verify_FlHL7cUyC9.html
BingSiteAuth.xml
BingSiteAuth.xml
cb9339dbe2ff86a5aa169d28dba5f615.txt
werobot_session
django.jpg
uploads/
+36
View File
@@ -0,0 +1,36 @@
dist: precise
language: python
python:
- "3.4"
- "3.5"
- "3.6"
services:
- mysql
env:
global:
- DJANGO_SETTINGS_MODULE="travis_test.travis_settings"
matrix:
- DJANGO="Django==1.8"
- DJANGO="Django==1.9"
- DJANGO="Django==1.10"
- DJANGO="Django==1.11"
branches:
only:
- master
# command to install dependencies
install:
- pip install -r travis_test/requirements.txt
- pip install "$DJANGO"
- pip install python-coveralls
- pip install coverage
before_script:
- mysql -e 'CREATE DATABASE `djangoblog` /*!40100 DEFAULT CHARACTER SET utf8 */;'
- python manage.py makemigrations
- python manage.py migrate
- python manage.py collectstatic --noinput
- python manage.py compress --force
# command to run tests
script:
- coverage run manage.py test
after_success:
- coveralls
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: blog_signals.py
@time: 2017/8/12 上午10:18
"""
import django.dispatch
from django.dispatch import receiver
from django.conf import settings
from DjangoBlog.utils import cache, send_email, expire_view_cache, logger
from DjangoBlog.spider_notify import SpiderNotify
from django.contrib.sites.models import Site
comment_save_signal = django.dispatch.Signal(providing_args=["comment_id", "username", "serverport"])
article_save_signal = django.dispatch.Signal(providing_args=['id', 'is_update_views'])
user_login_logout_signal = django.dispatch.Signal(providing_args=['id', 'type'])
@receiver(article_save_signal)
def article_save_callback(sender, **kwargs):
id = kwargs['id']
is_update_views = kwargs['is_update_views']
type = sender.__name__
obj = None
from blog.models import Article, Category, Tag
if type == 'Article':
obj = Article.objects.get(id=id)
elif type == 'Category':
obj = Category.objects.get(id=id)
elif type == 'Tag':
obj = Tag.objects.get(id=id)
if obj is not None:
if not settings.TESTING and not is_update_views:
try:
notify_url = obj.get_full_url()
SpiderNotify.baidu_notify([notify_url])
except Exception as ex:
logger.error("notify sipder", ex)
print(ex)
@receiver(comment_save_signal)
def comment_save_callback(sender, **kwargs):
from comments.models import Comment
serverport = kwargs['serverport']
username = kwargs['username']
comment = Comment.objects.get(id=kwargs['comment_id'])
site = Site.objects.get_current().domain
article = comment.article
# if not settings.DEBUG:
if True:
subject = '感谢您发表的评论'
article_url = "https://{site}{path}".format(site=site, path=comment.article.get_absolute_url())
html_content = """
<p>非常感谢您在本站发表评论</p>
您可以访问
<a href="%s" rel="bookmark">%s</a>
来查看您的评论,
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
%s
""" % (article_url, comment.article.title, article_url)
tomail = comment.author.email
send_email([tomail], subject, html_content)
if comment.parent_comment:
html_content = """
您在 <a href="%s" rel="bookmark">%s</a> 的评论 <br/> %s <br/> 收到回复啦.快去看看吧
<br/>
如果上面链接无法打开,请将此链接复制至浏览器。
%s
""" % (article_url, article.title, comment.parent_comment.body, article_url)
tomail = comment.parent_comment.author.email
send_email([tomail], subject, html_content)
path = article.get_absolute_url()
site = Site.objects.get_current().domain
if site.find(':') > 0:
site = site[0:site.find(':')]
expire_view_cache(path, servername=site, serverport=serverport, key_prefix='blogdetail')
if cache.get('seo_processor'):
cache.delete('seo_processor')
comment_cache_key = 'article_comments_{id}'.format(id=article.id)
cache.delete(comment_cache_key)
from django.core.cache.utils import make_template_fragment_key
key = make_template_fragment_key('sidebar', [username])
cache.delete(key)
-56
View File
@@ -1,56 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: common_markdown.py
@time: 2017/1/14 上午2:30
"""
import mistune
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html
def block_code(text, lang, inlinestyles=False, linenos=False):
if not lang:
text = text.strip()
return u'<pre><code>%s</code></pre>\n' % mistune.escape(text)
try:
lexer = get_lexer_by_name(lang, stripall=True)
formatter = html.HtmlFormatter(
noclasses=inlinestyles, linenos=linenos
)
code = highlight(text, lexer, formatter)
if linenos:
return '<div class="highlight">%s</div>\n' % code
return code
except:
return '<pre class="%s"><code>%s</code></pre>\n' % (
lang, mistune.escape(text)
)
class BlogMarkDownRenderer(mistune.Renderer):
def block_code(self, text, lang):
# renderer has an options
inlinestyles = self.options.get('inlinestyles')
linenos = self.options.get('linenos')
return block_code(text, lang, inlinestyles, linenos)
class common_markdown():
@staticmethod
def get_markdown(value):
renderer = BlogMarkDownRenderer(inlinestyles=False)
mdp = mistune.Markdown(escape=True, renderer=renderer)
return mdp(value)
+2 -2
View File
@@ -17,7 +17,7 @@ from django.contrib.syndication.views import Feed
from blog.models import Article
from django.conf import settings
from django.utils.feedgenerator import Rss201rev2Feed
from DjangoBlog.common_markdown import common_markdown
from DjangoBlog.utils import CommonMarkdown
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
@@ -44,7 +44,7 @@ class DjangoBlogFeed(Feed):
return item.title
def item_description(self, item):
return common_markdown.get_markdown(item.body)
return CommonMarkdown.get_markdown(item.body)
def feed_copyright(self):
# print(Site.objects.get_current().name)
+136 -16
View File
@@ -9,7 +9,7 @@ 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 sys
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -19,13 +19,15 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '&3g0bdza#c%dm1lf%5gi&0-*53p3t0m*hmcvo29cn^$ji7je(c'
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# DEBUG = True
DEBUG = False
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
# ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1']
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1', 'example.com']
# Application definition
INSTALLED_APPS = [
@@ -42,21 +44,24 @@ INSTALLED_APPS = [
'blog',
'accounts',
'comments',
'oauth'
'oauth',
'servermanager',
'compressor'
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.gzip.GZipMiddleware',
'django.middleware.cache.UpdateCacheMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
# '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'
@@ -141,6 +146,8 @@ HAYSTACK_CONNECTIONS = {
}
# 自动更新搜索索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 允许使用用户名或密码登录
AUTHENTICATION_BACKENDS = ['accounts.user_login_backend.EmailOrUsernameModelBackend']
STATIC_ROOT = os.path.join(SITE_ROOT, 'collectedstatic')
@@ -154,12 +161,12 @@ TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
DATE_TIME_FORMAT = '%Y-%m-%d'
SITE_NAME = '且听风吟'
SITE_URL = 'http://blog.lylinux.org'
SITE_URL = 'http://www.lylinux.net'
SITE_DESCRIPTION = '大巧无工,重剑无锋.'
SITE_SEO_DESCRIPTION = '小站主要用来分享和记录学习经验,教程,记录个人生活的点滴以及一些随笔.欢迎大家访问小站'
SITE_SEO_KEYWORDS = 'linux,apache,mysql,服务器,ubuntu,shell,web,csharp,.net,asp,mac,swift'
SITE_SEO_KEYWORDS = 'linux,apache,mysql,服务器,ubuntu,shell,web,csharp,.net,asp,mac,swift,python,django'
ARTICLE_SUB_LENGTH = 300
SHOW_GOOGLE_ADSENSE = True
SHOW_GOOGLE_ADSENSE = False
# bootstrap颜色样式
BOOTSTRAP_COLOR_TYPES = [
'default', 'primary', 'success', 'info', 'warning', 'danger'
@@ -179,23 +186,136 @@ CACHE_CONTROL_MAX_AGE = 2592000
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'TIMEOUT': 10800,
'LOCATION': '127.0.0.1:11211',
},
'localmem': {
'locmemcache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'TIMEOUT': 10800,
'LOCATION': 'unique-snowflake',
}
}
CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 10
CACHE_MIDDLEWARE_KEY_PREFIX = "djangoblog"
CACHE_MIDDLEWARE_ALIAS = 'default'
# SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# SESSION_CACHE_ALIAS = 'default'
OAHUTH = {
'sina': {
'appkey': '3161614143',
'appsecret': 'ee17c099317f872eeddb25204ea46721',
'callbackurl': 'http://blog.lylinux.org/oauth/weibo'
'appkey': os.environ.get('SINA_APP_KEY'),
'appsecret': os.environ.get('SINA_APP_SECRET'),
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=weibo'
},
'google': {
'appkey': os.environ.get('GOOGLE_APP_KEY'),
'appsecret': os.environ.get('GOOGLE_APP_SECRET'),
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=google'
},
'github': {
'appkey': os.environ.get('GITHUB_APP_KEY'),
'appsecret': os.environ.get('GITHUB_APP_SECRET'),
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=github'
},
'facebook': {
'appkey': os.environ.get('FACEBOOK_APP_KEY'),
'appsecret': os.environ.get('FACEBOOK_APP_SECRET'),
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=facebook'
}
}
SITE_ID = 2
BAIDU_NOTIFY_URL = "http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn&type=original"
SITE_ID = 1
BAIDU_NOTIFY_URL = "http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn"
# Emial:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
# EMAIL_USE_SSL = True
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')
# 设置debug=false 未处理异常邮件通知
ADMINS = [('liangliang', 'liangliangyy@gmail.com')]
# 微信管理员密码(两次md5获得)
WXADMIN = '995F03AC401D6CABABAEF756FC4D43C7'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s',
},
'simple': {
'format': '%(levelname)s %(asctime)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.RotatingFileHandler',
'filename': 'djangoblog.log',
'maxBytes': 16777216, # 16 MB
'formatter': 'verbose'
},
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'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'
]
+2 -1
View File
@@ -19,6 +19,7 @@ from accounts.models import BlogUser
from django.contrib.sitemaps import GenericSitemap
from django.core.urlresolvers import reverse
class StaticViewSitemap(Sitemap):
priority = 0.5
changefreq = 'daily'
@@ -68,7 +69,7 @@ class UserSiteMap(Sitemap):
priority = "0.3"
def items(self):
return BlogUser.objects.all()
return list(set(map(lambda x: x.author, Article.objects.all())))
def lastmod(self, obj):
return obj.date_joined
+13 -7
View File
@@ -12,25 +12,31 @@
@file: spider_notify.py
@time: 2017/1/15 下午1:41
"""
from django.contrib.sitemaps import ping_google
import requests
from django.conf import settings
class sipder_notify():
def __baidu_notify(self, url):
class SpiderNotify():
@staticmethod
def baidu_notify(urls):
try:
result = requests.post(settings.BAIDU_NOTIFY_URL, data=url)
data = '\n'.join(urls)
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
print(result.text)
except Exception as e:
print(e)
def __google_notify(self):
@staticmethod
def __google_notify():
try:
ping_google('/sitemap.xml')
except Exception as e:
print(e)
def notify(self, url):
self.__baidu_notify(url)
self.__google_notify()
@staticmethod
def notify(url):
SpiderNotify.baidu_notify(url)
SpiderNotify.__google_notify()
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.net/
@software: PyCharm
@file: tests.py
@time: 2017/10/25 下午10:16
"""
from django.test import Client, RequestFactory, TestCase
from blog.models import Article, Category, Tag
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
import datetime
from DjangoBlog.utils import *
class DjangoBlogTest(TestCase):
def setUp(self):
pass
def test_utils(self):
md5 = get_md5('test')
self.assertIsNotNone(md5)
c = CommonMarkdown.get_markdown('''
# Title1
```python
import os
```
[url](https://www.lylinux.org/)
[ddd](http://www.baidu.com)
''')
self.assertIsNotNone(c)
d = {
'd': 'key1',
'd2': 'key2'
}
data = parse_dict_to_url(d)
self.assertIsNotNone(data)
+15 -11
View File
@@ -20,6 +20,7 @@ from DjangoBlog.sitemap import StaticViewSitemap, ArticleSiteMap, CategorySiteMa
from DjangoBlog.feeds import DjangoBlogFeed
from django.views.decorators.cache import cache_page
from django.conf import settings
from django.conf.urls.static import static
sitemaps = {
@@ -30,16 +31,19 @@ sitemaps = {
'static': StaticViewSitemap
}
handler404 = 'blog.views.page_not_found_view'
handler500 = 'blog.views.server_error_view'
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'', include('blog.urls', namespace='blog', app_name='blog')),
url(r'^admin/', admin.site.urls),
url(r'', include('blog.urls', namespace='blog', app_name='blog')),
url(r'', include('comments.urls', namespace='comment', app_name='comments')),
url(r'', include('accounts.urls', namespace='account', app_name='accounts')),
url(r'', include('oauth.urls', namespace='oauth', app_name='oauth')),
url(r'^sitemap\.xml$', cache_page(60 * 60 * 10)(sitemap), {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
url(r'^feed/$', cache_page(60 * 60 * 10)(DjangoBlogFeed())),
url(r'^search', include('haystack.urls'), name='search'),
]
url(r'', include('comments.urls', namespace='comment', app_name='comments')),
url(r'', include('accounts.urls', namespace='account', app_name='accounts')),
url(r'', include('oauth.urls', namespace='oauth', app_name='oauth')),
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
url(r'^feed/$', DjangoBlogFeed()),
url(r'^search', include('haystack.urls'), name='search'),
url(r'', include('servermanager.urls', namespace='servermanager', app_name='servermanagers'))
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: utils.py
@time: 2017/1/19 上午2:30
"""
from django.core.cache import cache
from django.contrib.sites.models import Site
from hashlib import md5
import mistune
from mistune import escape, escape_link
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html
import logging
import _thread
from django.core.mail import EmailMultiAlternatives
from django.conf import settings
logger = logging.getLogger('djangoblog')
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_md5(str):
m = md5(str.encode('utf-8'))
return m.hexdigest()
def cache_decorator(expiration=3 * 60):
def wrapper(func):
def news(*args, **kwargs):
key = ''
try:
view = args[0]
key = view.get_cache_key()
except:
key = None
pass
if not key:
unique_str = repr((func, args, kwargs))
m = md5(unique_str.encode('utf-8'))
key = m.hexdigest()
value = cache.get(key)
if value:
logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key))
return value
else:
logger.info('cache_decorator set cache:%s key:%s' % (func.__name__, key))
value = func(*args, **kwargs)
cache.set(key, value, expiration)
return value
return news
return wrapper
def expire_view_cache(path, servername, serverport, key_prefix=None):
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
def block_code(text, lang, inlinestyles=False, linenos=False):
if not lang:
text = text.strip()
return u'<pre><code>%s</code></pre>\n' % mistune.escape(text)
try:
lexer = get_lexer_by_name(lang, stripall=True)
formatter = html.HtmlFormatter(
noclasses=inlinestyles, linenos=linenos
)
code = highlight(text, lexer, formatter)
if linenos:
return '<div class="highlight">%s</div>\n' % code
return code
except:
return '<pre class="%s"><code>%s</code></pre>\n' % (
lang, mistune.escape(text)
)
class BlogMarkDownRenderer(mistune.Renderer):
def block_code(self, text, lang=None):
# renderer has an options
inlinestyles = self.options.get('inlinestyles')
linenos = self.options.get('linenos')
return block_code(text, lang, inlinestyles, linenos)
def autolink(self, link, is_email=False):
text = link = escape(link)
if is_email:
link = 'mailto:%s' % link
if not link:
link = "#"
site = Site.objects.get_current()
nofollow = "" if link.find(site.domain) > 0 else "rel='nofollow'"
return '<a href="%s" %s>%s</a>' % (link, nofollow, text)
def link(self, link, title, text):
link = escape_link(link)
site = Site.objects.get_current()
nofollow = "" if link.find(site.domain) > 0 else "rel='nofollow'"
if not link:
link = "#"
if not title:
return '<a href="%s" %s>%s</a>' % (link, nofollow, text)
title = escape(title, quote=True)
return '<a href="%s" title="%s" %s>%s</a>' % (link, title, nofollow, text)
class CommonMarkdown():
@staticmethod
def get_markdown(value):
renderer = BlogMarkDownRenderer(inlinestyles=False)
mdp = mistune.Markdown(escape=True, renderer=renderer)
return mdp(value)
def send_email(emailto, title, content):
msg = EmailMultiAlternatives(title, content, from_email=settings.DEFAULT_FROM_EMAIL, to=emailto)
msg.content_subtype = "html"
_thread.start_new_thread(msg.send, (msg,))
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
+77 -14
View File
@@ -1,25 +1,56 @@
# DjangoBlog
基于`python3.5``Django1.10`的博客。
基于`python3.5``Django1.10`的博客。
[![Build Status](https://travis-ci.org/liangliangyy/DjangoBlog.svg?branch=master)](https://travis-ci.org/liangliangyy/DjangoBlog) [![Coverage Status](https://coveralls.io/repos/github/liangliangyy/DjangoBlog/badge.svg?branch=master)](https://coveralls.io/github/liangliangyy/DjangoBlog?branch=master) [![Requirements Status](https://requires.io/github/liangliangyy/DjangoBlog/requirements.svg?branch=master)](https://requires.io/github/liangliangyy/DjangoBlog/requirements/?branch=master) [![license](https://img.shields.io/github/license/liangliangyy/djangoblog.svg)]() [![GitHub release](https://img.shields.io/github/release/liangliangyy/djangoblog.svg)]() [![python3.5](https://img.shields.io/badge/python-3.5-brightgreen.svg)]() [![django1.10](https://img.shields.io/badge/django-1.10-brightgreen.svg)]()
## 主要功能:
- 文章,页面,分类目录,标签的添加,删除,编辑等。文章及页面支持`Markdown`,支持代码高亮。
- 支持文章全文搜索。
- 完整的评论功能,包括发表回复评论,以及评论的邮件提醒,支持`Markdown`
- 侧边栏功能,最新文章,最多阅读,标签云等。
- 支持Oauth登陆,现已有Google,GitHub,facebook,微博登录。
- 支持`Memcache`缓存,支持缓存自动刷新。
- 简单的SEO功能,新建文章等会自动通知Google和百度。
- 集成了简单的图床功能。
- 集成`django-compressor`,自动压缩`css``js`
- 基于`python3`,支持`Django`多版本。`Django`的1.8,1.9,1.10,1.11均测试通过。
- 网站异常邮件提醒,若有未捕捉到的异常会自动发送提醒邮件。
- 集成了微信公众号功能,现在可以使用微信公众号来管理你的vps了。
## 安装
使用pip安装:
`pip install -r requirements.txt`
`pip install -Ur requirements.txt`
如果你没有pip,使用如下方式安装:
OS X / Linux 电脑,终端下执行:
curl http://peak.telecommunity.com/dist/ez_setup.py | python
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
windows电脑:
下载 http://peak.telecommunity.com/dist/ez_setup.py 和 https://raw.github.com/pypa/pip/master/contrib/get-pip.py 这两个文件,双击运行。
### 配置
配置都是在`setting.py`中.
很多`setting`配置我都是写在环境变量里面的.并没有提交到`github`中来.例如`SECRET_KEY`,`OAHUTH`,`mysql`以及邮件部分的配置等.你可以直接修改代码成你自己的,或者在环境变量里面加入对应的配置就可以了.
`test`目录中的文件都是为了`travis`自动化测试使用的.不用去关注.或者直接使用.这样就可以集成`travis`自动化测试了.
`bin`目录是在`linux`环境中使用`Nginx`+`Gunicorn`+`virtualenv`+`supervisor`来部署的脚本和`Nginx`配置文件.可以参考我的文章:
>[使用Nginx+Gunicorn+virtualenv+supervisor来部署django项目](https://www.lylinux.org/%E4%BD%BF%E7%94%A8nginxgunicornvirtualenvsupervisor%E6%9D%A5%E9%83%A8%E7%BD%B2django%E9%A1%B9%E7%9B%AE.html)
有详细的部署介绍.
为了安全起见,没有把`SECRET_KEY`上传到Github中而是在环境变量中配置的,如果你想要正常运行的话,需要修改`settings.py`中的`SECRET_KEY`为你自己的就可以了。
如:`SECRET_KEY = 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'`
若本地部署后发现静态文件无法加载.请将`settings.py`中的`DEBUG=False`修改为`DEBUG=True`即可.
## 运行
修改`DjangoBlog/setting.py` 修改数据库配置,如下所示:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
@@ -30,16 +61,48 @@ windows电脑:
'PORT': 3306,
}
}
### 创建数据库
终端下执行:
终端下执行:
./manage.py makemigrations
./manage.py migrate
./manage.py migrate
### 创建超级用户
终端下执行:
./manage.py createsuperuser
### 创建测试数据
终端下执行:
./manage.py create_testdata
### 收集静态文件
终端下执行:  
./manage.py collectstatic --noinput
./manage.py compress --force
### 开始运行:
执行:
执行:
`./manage.py runserver`
浏览器打开: http://127.0.0.1:8000/ 就可以看到效果了。
## 问题相关
有任何问题欢迎提Issue,或者将问题描述发送至我邮箱 `liangliangyy#gmail.com`.我会尽快解答.推荐提交Issue方式.
## 捐赠
如果您觉得本项目对您有所帮助,欢迎您请我喝杯咖啡,您的支持是我最大的动力,您可以扫描下方二维码为我付款,谢谢。
### 支付宝:
<div align="center">
<img src="https://resource.lylinux.net/image/2017/12/16/IMG_0207.jpg" width="300" height="300" />
</div>
### 微信:
<div align="center">
<img src="https://resource.lylinux.net/image/2017/12/16/IMG_0206.jpg" width="300" height="300" />
</div>
+9 -1
View File
@@ -3,4 +3,12 @@ from django.contrib import admin
# Register your models here.
from .models import BlogUser
admin.site.register(BlogUser)
class BlogUserAdmin(admin.ModelAdmin):
list_display = ('id', 'nickname', 'username','email','last_login','date_joined')
list_display_links = ('id', 'username')
admin.site.register(BlogUser, BlogUserAdmin)
+9 -6
View File
@@ -21,18 +21,21 @@ from django.contrib.auth import get_user_model
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.fields['username'].widget = widgets.TextInput(attrs={'placeholder': "用户名", "class": "form-control"})
self.fields['password'].widget = widgets.PasswordInput(attrs={'placeholder': "密码", "class": "form-control"})
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': "用户名", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(attrs={'placeholder': "邮箱", "class": "form-control"})
self.fields['password1'].widget = widgets.PasswordInput(attrs={'placeholder': "密码", "class": "form-control"})
self.fields['password2'].widget = widgets.PasswordInput(attrs={'placeholder': "确认密码", "class": "form-control"})
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"})
class Meta:
model = get_user_model()
+11 -12
View File
@@ -1,28 +1,27 @@
from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.core.urlresolvers import reverse
from django.contrib.sites.models import Site
from django.utils.timezone import now
# Create your models here.
"""
class BlogUserManager(BaseUserManager):
def create_user(self, email, username, password=None):
user = self.model(
username=username, email=email, nickname=username
)
user.set_password(password)
user.save(using=self._db)
return user
"""
class BlogUser(AbstractUser):
nickname = models.CharField('昵称', max_length=50, blank=True)
mugshot = models.ImageField('头像', upload_to='upload/mugshots', blank=True)
#objects = BlogUserManager()
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
# objects = BlogUserManager()
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 = Site.objects.get_current().domain
url = "https://{site}{path}".format(site=site, path=self.get_absolute_url())
return url
+82 -1
View File
@@ -1,3 +1,84 @@
from django.test import TestCase
from django.test import Client, RequestFactory, TestCase
from blog.models import Article, Category, Tag
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
import datetime
from accounts.models import BlogUser
from django.core.urlresolvers import reverse
# Create your tests here.
class AccountTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_validate_account(self):
site = Site.objects.get_current().domain
user = BlogUser.objects.create_superuser(email="liangliangyy1@gmail.com",
username="liangliangyy1", password="liangliangyy1")
self.client.login(username='liangliangyy1', password='liangliangyy1')
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
category = Category()
category.name = "categoryaaa"
category.created_time = datetime.datetime.now()
category.last_mod_time = datetime.datetime.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',
'password2': 'password123',
})
self.assertEquals(1, len(BlogUser.objects.filter(email='user123@user.com')))
self.client.login(username='user1233', password='password123')
user = BlogUser.objects.filter(email='user123@user.com')[0]
user.is_superuser = True
user.is_staff = True
user.save()
article = Article()
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])
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302])
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
})
self.assertIn(response.status_code, [301, 302])
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
+1 -1
View File
@@ -22,5 +22,5 @@ from .forms import LoginForm
urlpatterns = [
url(r'^login/$', views.LoginView.as_view(success_url='/'), name='login', kwargs={'authentication_form': LoginForm}),
url(r'^register/$', views.RegisterView.as_view(success_url="/"), name='register'),
url(r'^logout/$', views.logout, name='logout')
url(r'^logout/$', views.LogoutView.as_view(), name='logout')
]
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: user_login_backend.py
@time: 2017/2/17 下午8:45
"""
from django.conf import settings
from django.contrib.auth import get_user_model
class EmailOrUsernameModelBackend(object):
"""
允许使用用户名或邮箱登录
"""
def authenticate(self, username=None, password=None):
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
+54 -11
View File
@@ -1,15 +1,21 @@
from django.shortcuts import render
from django.contrib.auth.views import login, logout
from .forms import RegisterForm, LoginForm
from django.contrib.auth import authenticate
from django.views.generic.edit import FormView
from django.contrib.auth import authenticate, login, logout
# from django.views.generic.edit import FormView
from django.views.generic import FormView, RedirectView
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.views.decorators.csrf import csrf_protect
from django.contrib import auth
from django.views.decorators.cache import never_cache
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.http import is_safe_url
# Create your views here.
@@ -20,29 +26,66 @@ class RegisterView(FormView):
def form_valid(self, form):
user = form.save(False)
user.save(True)
return HttpResponseRedirect('/')
url = reverse('accounts:login')
return HttpResponseRedirect(url)
def LogOut(requests):
logout(request=requests)
return HttpResponseRedirect("/")
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):
from DjangoBlog.utils import cache
cache.clear()
logout(request)
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
@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():
# login(self.request, form.get_user())
from DjangoBlog.utils import cache
if cache and cache is not None:
cache.clear()
print(self.redirect_field_name)
redirect_to = self.request.GET.get(self.redirect_field_name)
auth.login(self.request, form.get_user())
return HttpResponseRedirect('/')
return super(LoginView, self).form_valid(form)
# return HttpResponseRedirect('/')
else:
return self.render_to_response({
'form': form
})
def get_success_url(self):
print(self.redirect_field_name)
redirect_to = self.request.POST.get(self.redirect_field_name)
if not is_safe_url(url=redirect_to, host=self.request.get_host()):
redirect_to = self.success_url
return redirect_to
+2 -2
View File
@@ -3,8 +3,8 @@
NAME="djangoblog" # Name of the application
DJANGODIR=//var/www/DjangoBlog # Django project directory
SOCKFILE=/var/www/DjangoBlog/run/gunicorn.sock # we will communicte using this unix socket
USER=www-data # the user to run as
GROUP=www-data # the group to run as
USER=root # the user to run as
GROUP=root # the group to run as
NUM_WORKERS=3 # how many worker processes should Gunicorn spawn
DJANGO_SETTINGS_MODULE=DjangoBlog.settings # which settings file should Django use
DJANGO_WSGI_MODULE=DjangoBlog.wsgi # WSGI module name
+56 -5
View File
@@ -1,9 +1,28 @@
from django.contrib import admin
# Register your models here.
from .models import Article, Category, Tag, Links
from .models import Article, Category, Tag, Links, SideBar
from pagedown.widgets import AdminPagedownWidget
from django import forms
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
class ArticleListFilter(admin.SimpleListFilter):
title = _("作者")
parameter_name = 'author'
def lookups(self, request, model_admin):
authors = list(set(map(lambda x: x.author, Article.objects.all())))
for author in authors:
yield (author.id, _(author.username))
def queryset(self, request, queryset):
id = self.value()
if id:
return queryset.filter(author__id__exact=id)
else:
return queryset
class ArticleForm(forms.ModelForm):
@@ -16,10 +35,42 @@ class ArticleForm(forms.ModelForm):
class ArticlelAdmin(admin.ModelAdmin):
form = ArticleForm
list_display = ('id', 'title', 'author', 'created_time', 'views', 'status', 'type')
list_display_links = ('id', 'title')
list_filter = (ArticleListFilter, 'status', 'type', 'category', 'tags')
filter_horizontal = ('tags',)
exclude = ('slug', 'created_time', 'last_mod_time')
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)
from DjangoBlog.utils import cache
cache.clear()
class TagAdmin(admin.ModelAdmin):
exclude = ('slug', 'last_mod_time', 'created_time')
class CategoryAdmin(admin.ModelAdmin):
exclude = ('slug', 'last_mod_time', 'created_time')
class LinksAdmin(admin.ModelAdmin):
exclude = ('last_mod_time', 'created_time')
class SideBarAdmin(admin.ModelAdmin):
list_display = ('name', 'content', 'is_enable', 'sequence')
exclude = ('last_mod_time', 'created_time')
admin.site.register(Article, ArticlelAdmin)
#admin.site.register(BlogPage, ArticlelAdmin)
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Links)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Links, LinksAdmin)
admin.site.register(SideBar, SideBarAdmin)
+24 -11
View File
@@ -14,17 +14,30 @@
"""
from .models import Category, Article, Tag
from django.conf import settings
from comments.models import Comment
from DjangoBlog.utils import logger, cache
def seo_processor(requests):
return {
'SITE_NAME': settings.SITE_NAME,
'SHOW_GOOGLE_ADSENSE': settings.SHOW_GOOGLE_ADSENSE,
'SITE_SEO_DESCRIPTION': settings.SITE_SEO_DESCRIPTION,
'SITE_DESCRIPTION': settings.SITE_DESCRIPTION,
'SITE_KEYWORDS': settings.SITE_SEO_KEYWORDS,
'SITE_BASE_URL': 'http://' + requests.get_host() + '/',
'ARTICLE_SUB_LENGTH': settings.ARTICLE_SUB_LENGTH,
'nav_category_list': Category.objects.all(),
'nav_pages': Article.objects.filter(type='p', status='p')
}
key = 'seo_processor'
value = cache.get(key)
if value:
logger.info('get processor cache.')
return value
else:
logger.info('set processor cache.')
value = {
'SITE_NAME': settings.SITE_NAME,
'SHOW_GOOGLE_ADSENSE': settings.SHOW_GOOGLE_ADSENSE,
'SITE_SEO_DESCRIPTION': settings.SITE_SEO_DESCRIPTION,
'SITE_DESCRIPTION': settings.SITE_DESCRIPTION,
'SITE_KEYWORDS': settings.SITE_SEO_KEYWORDS,
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
'ARTICLE_SUB_LENGTH': settings.ARTICLE_SUB_LENGTH,
'nav_category_list': Category.objects.all(),
'nav_pages': Article.objects.filter(type='p', status='p'),
# 'MAX_COMMENTID': Comment.objects.latest().pk,
# 'MAX_ARTICLEID': Article.objects.latest().pk
}
cache.set(key, value, 60 * 60 * 10)
return value
View File
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: clear_cache.py
@time: 2017/2/17 下午10:30
"""
from DjangoBlog.utils import cache
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'clear the whole cache'
def handle(self, *args, **options):
cache.clear()
self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
@@ -0,0 +1,54 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: create_testdata.py
@time: 2017/3/11 上午1:58
"""
from django.core.management.base import BaseCommand
from blog.models import Article, Tag, Category
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
import datetime
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='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'))
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: ping_baidu.py
@time: 2017/1/17 下午15:29
"""
from django.core.management.base import BaseCommand, CommandError
from blog.models import Article, Tag, Category
from DjangoBlog.spider_notify import SpiderNotify
from django.contrib.sites.models import Site
site = Site.objects.get_current().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'))
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: middleware.py
@time: 2017/1/19 上午12:36
"""
import time
from ipware.ip import get_real_ip
from DjangoBlog.utils import cache
class OnlineMiddleware(object):
def process_request(self, request):
self.start_time = time.time()
def process_view(self, request, view_func, view_args, view_kwargs):
"""
处理当前在线人数
"""
http_user_agent = request.META.get('HTTP_USER_AGENT', [])
if 'Spider' in http_user_agent or 'spider' in http_user_agent:
return
online_ips = cache.get("online_ips", [])
if online_ips:
online_ips = cache.get_many(online_ips).keys()
online_ips = list(online_ips)
ip = get_real_ip(request)
cache.set(ip, 0, 5 * 60)
if ip not in online_ips:
online_ips.append(ip)
s = type(online_ips)
cache.set("online_ips", online_ips)
def process_response(self, request, response):
cast_time = 0.921
if self.__dict__ and 'start_time' in self.__dict__:
cast_time = time.time() - self.start_time
response.content = response.content.replace(b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
return response
+122 -120
View File
@@ -1,12 +1,40 @@
from django.db import models
from django.core.urlresolvers import reverse
from django.conf import settings
# from django.template.defaultfilters import slugify
from uuslug import slugify
from DjangoBlog.spider_notify import sipder_notify
from django.contrib.sites.models import Site
from DjangoBlog.utils import cache_decorator, logger, cache
from django.utils.functional import cached_property
from django.utils.timezone import now
class Article(models.Model):
class BaseModel(models.Model):
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
def save(self, *args, **kwargs):
from DjangoBlog.blog_signals import article_save_signal
if not self.slug or self.slug == 'no-slug' or not self.id:
slug = self.title if 'title' in self.__dict__ else self.name
self.slug = slugify(slug)
super().save(*args, **kwargs)
# type = self.__class__.__name__
is_update_views = 'update_fields' in kwargs and len(kwargs['update_fields']) == 1 and kwargs['update_fields'][
0] == 'views'
article_save_signal.send(sender=self.__class__, is_update_views=is_update_views, id=self.id)
def get_full_url(self):
site = Site.objects.get_current().domain
url = "https://{site}{path}".format(site=site, path=self.get_absolute_url())
return url
class Meta:
abstract = True
class Article(BaseModel):
"""文章"""
STATUS_CHOICES = (
('d', '草稿'),
@@ -20,12 +48,9 @@ class Article(models.Model):
('a', '文章'),
('p', '页面'),
)
title = models.CharField('标题', max_length=200)
title = models.CharField('标题', max_length=200, unique=True)
body = models.TextField('正文')
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
pub_time = models.DateTimeField('发布时间', blank=True, null=True,
help_text="不指定发布时间则视为草稿,可以指定未来时间,到时将自动发布。")
pub_time = models.DateTimeField('发布时间', blank=True, null=True)
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='p')
comment_status = models.CharField('评论状态', max_length=1, choices=COMMENT_STATUS, default='o')
type = models.CharField('类型', max_length=1, choices=TYPE, default='a')
@@ -35,8 +60,6 @@ class Article(models.Model):
category = models.ForeignKey('Category', verbose_name='分类', on_delete=models.CASCADE, blank=True, null=True)
tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
def __str__(self):
return self.title
@@ -44,39 +67,28 @@ class Article(models.Model):
ordering = ['-pub_time']
verbose_name = "文章"
verbose_name_plural = verbose_name
get_latest_by = 'created_time'
def get_absolute_url(self):
return reverse('blog:detail', kwargs=
{
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
'year': self.created_time.year,
'month': self.created_time.month,
'day': self.created_time.day,
'slug': self.slug
'day': self.created_time.day
})
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
names = []
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
def parse(category):
names.append((category.name, category.get_absolute_url()))
if category.parent_category:
parse(category.parent_category)
parse(self.category)
return names
def save(self, *args, **kwargs):
# self.summary = self.summary or self.body[:settings.ARTICLE_SUB_LENGTH]
if not self.slug or self.slug == 'no-slug' or not self.id:
# Only set the slug when the object is created.
self.slug = slugify(self.title)
try:
notify = sipder_notify()
notify.notify(self.get_absolute_url())
except Exception as e:
print(e)
super().save(*args, **kwargs)
def viewed(self):
@@ -84,83 +96,35 @@ class Article(models.Model):
self.save(update_fields=['views'])
def comment_list(self):
comments = self.comment_set.all()
parent_comments = comments.filter(parent_comment=None)
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.all()
cache.set(cache_key, comments)
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,))
@cached_property
def next_article(self):
# 下一篇
return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first()
'''
class BlogPage(models.Model):
"""文章"""
STATUS_CHOICES = (
('d', '草稿'),
('p', '发表'),
)
COMMENT_STATUS = (
('o', '打开'),
('c', '关闭'),
)
title = models.CharField('标题', max_length=200)
body = models.TextField('正文')
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
pub_time = models.DateTimeField('发布时间', blank=True, null=True,
help_text="不指定发布时间则视为草稿,可以指定未来时间,到时将自动发布。")
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='o')
comment_status = models.CharField('评论状态', max_length=1, choices=COMMENT_STATUS)
# summary = models.CharField('摘要', max_length=200, blank=True, help_text="可选,若为空将摘取正文的前300个字符。")
views = models.PositiveIntegerField('浏览量', default=0)
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者', on_delete=models.CASCADE)
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
class Meta:
ordering = ['-pub_time']
verbose_name = "页面"
verbose_name_plural = verbose_name
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:pagedetail', kwargs=
{
'page_id': self.id,
'year': self.created_time.year,
'month': self.created_time.month,
'day': self.created_time.day,
'slug': self.slug
})
def save(self, *args, **kwargs):
# self.summary = self.summary or self.body[:settings.ARTICLE_SUB_LENGTH]
if not self.slug or self.slug == 'no-slug' or not self.id:
# Only set the slug when the object is created.
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def viewed(self):
self.views += 1
self.save(update_fields=['views'])
def comment_list(self):
comments = self.comment_set.all()
parent_comments = comments.filter(parent_comment=None)
def get_category_tree(self):
return []
'''
@cached_property
def prev_article(self):
# 前一篇
return Article.objects.filter(id__lt=self.id, status='p').first()
class Category(models.Model):
class Category(BaseModel):
"""文章分类"""
name = models.CharField('分类名', max_length=30)
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
name = models.CharField('分类名', max_length=30, unique=True)
parent_category = models.ForeignKey('self', verbose_name="父级分类", blank=True, null=True)
class Meta:
@@ -169,32 +133,60 @@ class Category(models.Model):
verbose_name_plural = verbose_name
def get_absolute_url(self):
return reverse('blog:category_detail', kwargs={'category_name': self.name})
return reverse('blog:category_detail', kwargs={'category_name': self.slug})
def __str__(self):
return self.name
def save(self, *args, **kwargs):
try:
notify = sipder_notify()
notify.notify(self.get_absolute_url())
except Exception as e:
print(e)
super().save(*args, **kwargs)
@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(models.Model):
class Tag(BaseModel):
"""文章标签"""
name = models.CharField('标签名', max_length=30)
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
name = models.CharField('标签名', max_length=30, unique=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:tag_detail', kwargs={'tag_name': self.name})
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()
@@ -203,22 +195,14 @@ class Tag(models.Model):
verbose_name = "标签"
verbose_name_plural = verbose_name
def save(self, *args, **kwargs):
try:
notify = sipder_notify()
notify.notify(self.get_absolute_url())
except Exception as e:
print(e)
super().save(*args, **kwargs)
class Links(models.Model):
"""友情链接"""
name = models.CharField('链接名称', max_length=30)
name = models.CharField('链接名称', max_length=30, unique=True)
link = models.URLField('链接地址')
sequence = models.IntegerField('排序', unique=True)
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
class Meta:
ordering = ['sequence']
@@ -227,3 +211,21 @@ class Links(models.Model):
def __str__(self):
return self.name
class SideBar(models.Model):
"""侧边栏,可以展示一些html内容"""
name = models.CharField('标题', max_length=100)
content = models.TextField("内容")
sequence = models.IntegerField('排序', unique=True)
is_enable = models.BooleanField('是否启用', default=True)
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
class Meta:
ordering = ['sequence']
verbose_name = '侧边栏'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
-1
View File
@@ -21,7 +21,6 @@ class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
# title = indexes.CharField(document=True, use_template=True)
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Article
+74
View File
@@ -0,0 +1,74 @@
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: red;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: red;
border-left-color: red;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
+305
View File
@@ -0,0 +1,305 @@
.icon-sn-google {
background-position: 0 -28px;
}
.icon-sn-bg-google {
background-color: #4285f4;
background-position: 0 0;
}
.fa-sn-google {
color: #4285f4;
}
.icon-sn-github {
background-position: -28px -28px;
}
.icon-sn-bg-github {
background-color: #333;
background-position: -28px 0;
}
.fa-sn-github {
color: #333;
}
.icon-sn-weibo {
background-position: -56px -28px;
}
.icon-sn-bg-weibo {
background-color: #e90d24;
background-position: -56px 0;
}
.fa-sn-weibo {
color: #e90d24;
}
.icon-sn-qq {
background-position: -84px -28px;
}
.icon-sn-bg-qq {
background-color: #0098e6;
background-position: -84px 0;
}
.fa-sn-qq {
color: #0098e6;
}
.icon-sn-twitter {
background-position: -112px -28px;
}
.icon-sn-bg-twitter {
background-color: #50abf1;
background-position: -112px 0;
}
.fa-sn-twitter {
color: #50abf1;
}
.icon-sn-facebook {
background-position: -140px -28px;
}
.icon-sn-bg-facebook {
background-color: #4862a3;
background-position: -140px 0;
}
.fa-sn-facebook {
color: #4862a3;
}
.icon-sn-renren {
background-position: -168px -28px;
}
.icon-sn-bg-renren {
background-color: #197bc8;
background-position: -168px 0;
}
.fa-sn-renren {
color: #197bc8;
}
.icon-sn-tqq {
background-position: -196px -28px;
}
.icon-sn-bg-tqq {
background-color: #1f9ed2;
background-position: -196px 0;
}
.fa-sn-tqq {
color: #1f9ed2;
}
.icon-sn-douban {
background-position: -224px -28px;
}
.icon-sn-bg-douban {
background-color: #279738;
background-position: -224px 0;
}
.fa-sn-douban {
color: #279738;
}
.icon-sn-weixin {
background-position: -252px -28px;
}
.icon-sn-bg-weixin {
background-color: #00b500;
background-position: -252px 0;
}
.fa-sn-weixin {
color: #00b500;
}
.icon-sn-dotted {
background-position: -280px -28px;
}
.icon-sn-bg-dotted {
background-color: #eee;
background-position: -280px 0;
}
.fa-sn-dotted {
color: #eee;
}
.icon-sn-site {
background-position: -308px -28px;
}
.icon-sn-bg-site {
background-color: #00b500;
background-position: -308px 0;
}
.fa-sn-site {
color: #00b500;
}
.icon-sn-linkedin {
background-position: -336px -28px;
}
.icon-sn-bg-linkedin {
background-color: #0077b9;
background-position: -336px 0;
}
.fa-sn-linkedin {
color: #0077b9;
}
[class*=icon-sn-] {
display: inline-block;
background-image: url('../img/icon-sn.svg');
background-repeat: no-repeat;
width: 28px;
height: 28px;
vertical-align: middle;
background-size: auto 56px;
}
[class*=icon-sn-]:hover {
opacity: .8;
filter: alpha(opacity=80);
}
.btn-sn-google {
background: #4285f4;
}
.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover {
background: #2a75f3;
}
.btn-sn-github {
background: #333;
}
.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover {
background: #262626;
}
.btn-sn-weibo {
background: #e90d24;
}
.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover {
background: #d10c20;
}
.btn-sn-qq {
background: #0098e6;
}
.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover {
background: #0087cd;
}
.btn-sn-twitter {
background: #50abf1;
}
.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover {
background: #38a0ef;
}
.btn-sn-facebook {
background: #4862a3;
}
.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover {
background: #405791;
}
.btn-sn-renren {
background: #197bc8;
}
.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover {
background: #166db1;
}
.btn-sn-tqq {
background: #1f9ed2;
}
.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover {
background: #1c8dbc;
}
.btn-sn-douban {
background: #279738;
}
.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover {
background: #228330;
}
.btn-sn-weixin {
background: #00b500;
}
.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover {
background: #009c00;
}
.btn-sn-dotted {
background: #eee;
}
.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover {
background: #e1e1e1;
}
.btn-sn-site {
background: #00b500;
}
.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover {
background: #009c00;
}
.btn-sn-linkedin {
background: #0077b9;
}
.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover {
background: #0067a0;
}
[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover {
border: none;
color: #fff;
}
.btn-sn-more {
padding: 0;
}
.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover {
box-shadow: none;
}
[class*=btn-sn-] [class*=icon-sn-] {
background-color: transparent;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+52
View File
@@ -17,3 +17,55 @@ function cancel_reply() {
$("#id_parent_comment_id").val('')
$("#commentform").appendTo($("#respond"));
}
NProgress.start();
NProgress.set(0.4);
//Increment
var interval = setInterval(function () {
NProgress.inc();
}, 1000);
$(document).ready(function () {
NProgress.done();
clearInterval(interval);
});
/** 侧边栏回到顶部 */
var rocket = $('#rocket');
$(window).on('scroll', debounce(slideTopSet, 300));
function debounce(func, wait) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(func, wait);
};
};
function slideTopSet() {
var top = $(document).scrollTop();
if (top > 200) {
rocket.addClass('show');
} else {
rocket.removeClass('show');
}
}
$(document).on('click', '#rocket', function(event) {
rocket.addClass('move');
$('body, html').animate({
scrollTop: 0
}, 800);
});
$(document).on('animationEnd', function() {
setTimeout(function() {
rocket.removeClass('move');
}, 400);
});
$(document).on('webkitAnimationEnd', function() {
setTimeout(function() {
rocket.removeClass('move');
}, 400);
});
+480
View File
@@ -0,0 +1,480 @@
/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
* @license MIT */
;(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.NProgress = factory();
}
})(this, function() {
var NProgress = {};
NProgress.version = '0.2.0';
var Settings = NProgress.settings = {
minimum: 0.08,
easing: 'linear',
positionUsing: '',
speed: 200,
trickle: true,
trickleSpeed: 200,
showSpinner: true,
barSelector: '[role="bar"]',
spinnerSelector: '[role="spinner"]',
parent: 'body',
template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
};
/**
* Updates configuration.
*
* NProgress.configure({
* minimum: 0.1
* });
*/
NProgress.configure = function(options) {
var key, value;
for (key in options) {
value = options[key];
if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
}
return this;
};
/**
* Last number.
*/
NProgress.status = null;
/**
* Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
*
* NProgress.set(0.4);
* NProgress.set(1.0);
*/
NProgress.set = function(n) {
var started = NProgress.isStarted();
n = clamp(n, Settings.minimum, 1);
NProgress.status = (n === 1 ? null : n);
var progress = NProgress.render(!started),
bar = progress.querySelector(Settings.barSelector),
speed = Settings.speed,
ease = Settings.easing;
progress.offsetWidth; /* Repaint */
queue(function(next) {
// Set positionUsing if it hasn't already been set
if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
// Add transition
css(bar, barPositionCSS(n, speed, ease));
if (n === 1) {
// Fade out
css(progress, {
transition: 'none',
opacity: 1
});
progress.offsetWidth; /* Repaint */
setTimeout(function() {
css(progress, {
transition: 'all ' + speed + 'ms linear',
opacity: 0
});
setTimeout(function() {
NProgress.remove();
next();
}, speed);
}, speed);
} else {
setTimeout(next, speed);
}
});
return this;
};
NProgress.isStarted = function() {
return typeof NProgress.status === 'number';
};
/**
* Shows the progress bar.
* This is the same as setting the status to 0%, except that it doesn't go backwards.
*
* NProgress.start();
*
*/
NProgress.start = function() {
if (!NProgress.status) NProgress.set(0);
var work = function() {
setTimeout(function() {
if (!NProgress.status) return;
NProgress.trickle();
work();
}, Settings.trickleSpeed);
};
if (Settings.trickle) work();
return this;
};
/**
* Hides the progress bar.
* This is the *sort of* the same as setting the status to 100%, with the
* difference being `done()` makes some placebo effect of some realistic motion.
*
* NProgress.done();
*
* If `true` is passed, it will show the progress bar even if its hidden.
*
* NProgress.done(true);
*/
NProgress.done = function(force) {
if (!force && !NProgress.status) return this;
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
};
/**
* Increments by a random amount.
*/
NProgress.inc = function(amount) {
var n = NProgress.status;
if (!n) {
return NProgress.start();
} else if(n > 1) {
return;
} else {
if (typeof amount !== 'number') {
if (n >= 0 && n < 0.2) { amount = 0.1; }
else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
else { amount = 0; }
}
n = clamp(n + amount, 0, 0.994);
return NProgress.set(n);
}
};
NProgress.trickle = function() {
return NProgress.inc();
};
/**
* Waits for all supplied jQuery promises and
* increases the progress as the promises resolve.
*
* @param $promise jQUery Promise
*/
(function() {
var initial = 0, current = 0;
NProgress.promise = function($promise) {
if (!$promise || $promise.state() === "resolved") {
return this;
}
if (current === 0) {
NProgress.start();
}
initial++;
current++;
$promise.always(function() {
current--;
if (current === 0) {
initial = 0;
NProgress.done();
} else {
NProgress.set((initial - current) / initial);
}
});
return this;
};
})();
/**
* (Internal) renders the progress bar markup based on the `template`
* setting.
*/
NProgress.render = function(fromStart) {
if (NProgress.isRendered()) return document.getElementById('nprogress');
addClass(document.documentElement, 'nprogress-busy');
var progress = document.createElement('div');
progress.id = 'nprogress';
progress.innerHTML = Settings.template;
var bar = progress.querySelector(Settings.barSelector),
perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
parent = document.querySelector(Settings.parent),
spinner;
css(bar, {
transition: 'all 0 linear',
transform: 'translate3d(' + perc + '%,0,0)'
});
if (!Settings.showSpinner) {
spinner = progress.querySelector(Settings.spinnerSelector);
spinner && removeElement(spinner);
}
if (parent != document.body) {
addClass(parent, 'nprogress-custom-parent');
}
parent.appendChild(progress);
return progress;
};
/**
* Removes the element. Opposite of render().
*/
NProgress.remove = function() {
removeClass(document.documentElement, 'nprogress-busy');
removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
var progress = document.getElementById('nprogress');
progress && removeElement(progress);
};
/**
* Checks if the progress bar is rendered.
*/
NProgress.isRendered = function() {
return !!document.getElementById('nprogress');
};
/**
* Determine which positioning CSS rule to use.
*/
NProgress.getPositioningCSS = function() {
// Sniff on document.body.style
var bodyStyle = document.body.style;
// Sniff prefixes
var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
('MozTransform' in bodyStyle) ? 'Moz' :
('msTransform' in bodyStyle) ? 'ms' :
('OTransform' in bodyStyle) ? 'O' : '';
if (vendorPrefix + 'Perspective' in bodyStyle) {
// Modern browsers with 3D support, e.g. Webkit, IE10
return 'translate3d';
} else if (vendorPrefix + 'Transform' in bodyStyle) {
// Browsers without 3D support, e.g. IE9
return 'translate';
} else {
// Browsers without translate() support, e.g. IE7-8
return 'margin';
}
};
/**
* Helpers
*/
function clamp(n, min, max) {
if (n < min) return min;
if (n > max) return max;
return n;
}
/**
* (Internal) converts a percentage (`0..1`) to a bar translateX
* percentage (`-100%..0%`).
*/
function toBarPerc(n) {
return (-1 + n) * 100;
}
/**
* (Internal) returns the correct CSS for changing the bar's
* position given an n percentage, and speed and ease from Settings
*/
function barPositionCSS(n, speed, ease) {
var barCSS;
if (Settings.positionUsing === 'translate3d') {
barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
} else if (Settings.positionUsing === 'translate') {
barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
} else {
barCSS = { 'margin-left': toBarPerc(n)+'%' };
}
barCSS.transition = 'all '+speed+'ms '+ease;
return barCSS;
}
/**
* (Internal) Queues a function to be executed.
*/
var queue = (function() {
var pending = [];
function next() {
var fn = pending.shift();
if (fn) {
fn(next);
}
}
return function(fn) {
pending.push(fn);
if (pending.length == 1) next();
};
})();
/**
* (Internal) Applies css properties to an element, similar to the jQuery
* css method.
*
* While this helper does assist with vendor prefixed property names, it
* does not perform any manipulation of values prior to setting styles.
*/
var css = (function() {
var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
cssProps = {};
function camelCase(string) {
return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
return letter.toUpperCase();
});
}
function getVendorProp(name) {
var style = document.body.style;
if (name in style) return name;
var i = cssPrefixes.length,
capName = name.charAt(0).toUpperCase() + name.slice(1),
vendorName;
while (i--) {
vendorName = cssPrefixes[i] + capName;
if (vendorName in style) return vendorName;
}
return name;
}
function getStyleProp(name) {
name = camelCase(name);
return cssProps[name] || (cssProps[name] = getVendorProp(name));
}
function applyCss(element, prop, value) {
prop = getStyleProp(prop);
element.style[prop] = value;
}
return function(element, properties) {
var args = arguments,
prop,
value;
if (args.length == 2) {
for (prop in properties) {
value = properties[prop];
if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
}
} else {
applyCss(element, args[1], args[2]);
}
}
})();
/**
* (Internal) Determines if an element or space separated list of class names contains a class name.
*/
function hasClass(element, name) {
var list = typeof element == 'string' ? element : classList(element);
return list.indexOf(' ' + name + ' ') >= 0;
}
/**
* (Internal) Adds a class to an element.
*/
function addClass(element, name) {
var oldList = classList(element),
newList = oldList + name;
if (hasClass(oldList, name)) return;
// Trim the opening space.
element.className = newList.substring(1);
}
/**
* (Internal) Removes a class from an element.
*/
function removeClass(element, name) {
var oldList = classList(element),
newList;
if (!hasClass(element, name)) return;
// Replace the class name.
newList = oldList.replace(' ' + name + ' ', ' ');
// Trim the opening and closing spaces.
element.className = newList.substring(1, newList.length - 1);
}
/**
* (Internal) Gets a space separated list of the class names on the element.
* The list is wrapped with a single space on each end to facilitate finding
* matches within the list.
*/
function classList(element) {
return (' ' + (element && element.className || '') + ' ').replace(/\s+/gi, ' ');
}
/**
* (Internal) Removes an element from the DOM.
*/
function removeElement(element) {
element && element.parentNode && element.parentNode.removeChild(element);
}
return NProgress;
});
+83 -46
View File
@@ -15,15 +15,19 @@
from django import template
from django.conf import settings
import markdown2
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
import random
from blog.models import Article, Category, Tag, Links
from django.core.urlresolvers import reverse
from blog.models import Article, Category, Tag, Links, SideBar
from django.utils.encoding import force_text
from django.shortcuts import get_object_or_404
import hashlib
import urllib
from comments.models import Comment
from DjangoBlog.utils import cache_decorator, logger
from django.contrib.auth import get_user_model
from oauth.models import OAuthUser
register = template.Library()
@@ -49,8 +53,8 @@ def datetimeformat(data):
@register.filter(is_safe=True)
@stringfilter
def custom_markdown(content):
from DjangoBlog.common_markdown import common_markdown
return mark_safe(common_markdown.get_markdown(content))
from DjangoBlog.utils import CommonMarkdown
return mark_safe(CommonMarkdown.get_markdown(content))
@register.filter(is_safe=True)
@@ -66,6 +70,14 @@ def truncatechars_content(content):
return truncatechars_html(content, settings.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):
"""
@@ -110,15 +122,26 @@ def load_sidebar(user):
加载侧边栏
:return:
"""
logger.info('load sidebar')
recent_articles = Article.objects.filter(status='p')[:settings.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')[:settings.SIDEBAR_ARTICLE_COUNT]
dates = Article.objects.datetimes('created_time', 'month', order='DESC')
links = Links.objects.all()
commment_list = Comment.objects.order_by('-id')[:settings.SIDEBAR_COMMENT_COUNT]
show_adsense = settings.SHOW_GOOGLE_ADSENSE
# tags=
# 标签云 计算字体大小
# 根据总数计算出平均值 大小为 (数目/平均值)*步长
increment = 5
tags = Tag.objects.all()
sidebar_tags = None
if tags and len(tags) > 0:
s = list(map(lambda t: (t, t.get_article_count()), tags))
count = sum(map(lambda t: t[1], s))
dd = 1 if count == 0 else count / len(tags)
sidebar_tags = list(map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s))
return {
'recent_articles': recent_articles,
'sidebar_categorys': sidebar_categorys,
@@ -127,7 +150,9 @@ def load_sidebar(user):
'sidabar_links': links,
'sidebar_comments': commment_list,
'user': user,
'show_adsense': show_adsense
'show_adsense': show_adsense,
'sidebar_tags': sidebar_tags,
'extra_sidebars': extra_sidebars
}
@@ -144,6 +169,51 @@ def load_article_metas(article, 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('nav.html')
def load_nav_info():
@@ -162,8 +232,6 @@ def load_article_detail(article, isindex, user):
:param isindex:是否列表页,若是列表页只显示摘要
:return:
"""
print()
return {
'article': article,
'isindex': isindex,
@@ -176,9 +244,14 @@ def load_article_detail(article, isindex, user):
@register.filter
def gravatar_url(email, size=40):
"""获得gravatar头像"""
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 = "https://avatar.duoshuo.com/avatar-50/928/120117.jpg".encode('utf-8')
default = "https://resource.lylinux.net/image/2017/03/26/120117.jpg".encode('utf-8')
return "https://www.gravatar.com/avatar/%s?%s" % (
hashlib.md5(email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)}))
@@ -202,39 +275,3 @@ def query(qs, **kwargs):
{% endfor %}
"""
return qs.filter(**kwargs)
"""
article = Article.objects.get(pk=4)
comments = Comment.objects.filter(article=article)
for c in comments.filter(parent_comment=None):
datas = parse_commenttree(comments, c)
print(datas)
"""
"""
@register.tag
def parseCategoryName(parser,token):
tag_name, category = token.split_contents()
print(category)
print(tag_name)
return CategoryNametag(category)
class CategoryNametag(template.Node):
def __init__(self,category):
self.category=category
self.names=[]
def parseCategory(self,category):
self.names.append(category.name)
if category.parent_category:
self.parseCategory(category.parent_category)
def render(self, context):
self.parseCategory(self.category)
print(self.names)
return " > ".join(self.names)
#if self.category.parent_category:
"""
+157 -1
View File
@@ -1,3 +1,159 @@
from django.test import TestCase
from django.test import Client, RequestFactory, TestCase
from blog.models import Article, Category, Tag, SideBar
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from blog.forms import BlogSearchForm
from django.core.paginator import Paginator
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
import datetime
from accounts.models import BlogUser
from django.core.files.uploadedfile import SimpleUploadedFile
from django.conf import settings
import os
# Create your tests here.
class ArticleTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_validate_article(self):
site = Site.objects.get_current().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)
s = SideBar()
s.sequence = 1
s.name = 'test'
s.content = 'test content'
s.is_enable = True
s.save()
category = Category()
category.name = "category"
category.created_time = datetime.datetime.now()
category.last_mod_time = datetime.datetime.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()
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)
p = Paginator(Article.objects.all(), 2)
self.__check_pagination__(p, '', '')
p = Paginator(Article.objects.filter(tags=tag), 2)
self.__check_pagination__(p, '分类标签归档', tag.slug)
p = Paginator(Article.objects.filter(author__username='liangliangyy'), 2)
self.__check_pagination__(p, '作者文章归档', 'liangliangyy')
p = Paginator(Article.objects.filter(category=category), 2)
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()])
rsp = self.client.get('/refresh/')
self.assertEqual(rsp.status_code, 200)
from blog.templatetags.blog_tags import gravatar_url, gravatar
u = gravatar_url('liangliangyy@gmail.com')
u = gravatar('liangliangyy@gmail.com')
def __check_pagination__(self, p, type, value):
s = load_pagination_info(p.page(1), type, value)
self.assertIsNotNone(s)
response = self.client.get(s['previous_url'])
self.assertEqual(response.status_code, 200)
response = self.client.get(s['next_url'])
self.assertEqual(response.status_code, 200)
s = load_pagination_info(p.page(2), type, value)
self.assertIsNotNone(s)
response = self.client.get(s['previous_url'])
self.assertEqual(response.status_code, 200)
response = self.client.get(s['next_url'])
self.assertEqual(response.status_code, 200)
def test_validate_feed(self):
user = BlogUser.objects.get_or_create(email="liangliangyy12@gmail.com", username="liangliangyy")[0]
user.set_password("liangliangyy")
user.save()
response = self.client.get('/feed/')
self.assertEqual(response.status_code, 200)
response = self.client.get('/sitemap.xml')
self.assertEqual(response.status_code, 200)
def test_image(self):
import requests
rsp = requests.get('https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2909203028,3998034658&fm=96')
imagepath = os.path.join(settings.BASE_DIR, 'django.jpg')
with open(imagepath, 'wb') as file:
file.write(rsp.content)
with open(imagepath, 'rb') as file:
imgfile = SimpleUploadedFile('django.jpg', file.read(), content_type='image/jpg')
form_data = {'django.jpg': imgfile}
rsp = self.client.post('/upload', form_data, follow=True)
self.assertEqual(rsp.status_code, 200)
"""
data = SimpleUploadedFile(imagepath, b'file_content', content_type='image/jpg')
rsp = self.client.post('/upload', {'django.jpg': data})
self.assertEqual(rsp.status_code, 200)
SimpleUploadedFile()
"""
def test_errorpage(self):
self.client.get('/eee')
self.client.get('/refresh_memcache')
+18 -5
View File
@@ -23,15 +23,28 @@ from haystack.views import SearchView
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^page/(?P<page>\d+)$', views.IndexView.as_view(), name='index_page'),
url(r'^article/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<article_id>\d+)-(?P<slug>\S+).html$',
url(r'^article/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<article_id>\d+).html$',
views.ArticleDetailView.as_view(),
name='detail'),
url(r'^blogpage/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<page_id>\d+)-(?P<slug>\S+).html$',
name='detailbyid'),
url(r'^blogpage/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<page_id>\d+)-(?P<slug>[\w-]+).html$',
views.ArticleDetailView.as_view(),
name='pagedetail'),
url(r'^category/(?P<category_name>\S+).html$', views.CategoryDetailView.as_view(), name='category_detail'),
url(r'^category/(?P<category_name>[\w-]+).html$', views.CategoryDetailView.as_view(), name='category_detail'),
url(r'^category/(?P<category_name>[\w-]+)/(?P<page>\d+).html$', views.CategoryDetailView.as_view(),
name='category_detail_page'),
# url(r'^category/(?P<category_name>[\w-]+)/(?P<page>\d+).html$', views.CategoryDetailView.as_view(),
# name='category_detail'),
url(r'^author/(?P<author_name>\w+).html$', views.AuthorDetailView.as_view(), name='author_detail'),
url(r'^tag/(?P<tag_name>.+).html$', views.TagDetailView.as_view(), name='tag_detail'),
url(r'^author/(?P<author_name>\w+)/(?P<page>\d+).html$', views.AuthorDetailView.as_view(),
name='author_detail_page'),
url(r'^tag/(?P<tag_name>[\w-]+).html$', views.TagDetailView.as_view(), name='tag_detail'),
url(r'^tag/(?P<tag_name>[\w-]+)/(?P<page>\d+).html$', views.TagDetailView.as_view(), name='tag_detail_page'),
url(r'^upload', views.fileupload, name='upload'),
url(r'^refresh', views.refresh_memcache, name='refresh')
]
+139 -126
View File
@@ -1,43 +1,20 @@
from django.shortcuts import render
# Create your views here.
import os
import datetime
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from django.views.generic import UpdateView
from django.views.generic.edit import CreateView, FormView
from django.views.generic.dates import YearArchiveView, MonthArchiveView
from blog.models import Article, Category, Tag
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.core.exceptions import ObjectDoesNotExist
from comments.forms import CommentForm
from django.conf import settings
from django import forms
from django import http
from django.http import HttpResponse
from abc import ABCMeta, abstractmethod
from haystack.generic_views import SearchView
from blog.forms import BlogSearchForm
import datetime
from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
import os
from django.contrib.auth.decorators import login_required
"""
class SeoProcessor():
__metaclass__ = ABCMeta
@abstractmethod
def get_title(self):
pass
@abstractmethod
def get_keywords(self):
pass
@abstractmethod
def get_description(self):
pass
"""
from DjangoBlog.utils import cache, logger
from django.shortcuts import get_object_or_404
from blog.models import Article, Category, Tag
from comments.forms import CommentForm
class ArticleListView(ListView):
@@ -52,17 +29,54 @@ class ArticleListView(ListView):
paginate_by = settings.PAGINATE_BY
page_kwarg = 'page'
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):
# raise NotImplementedError()
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):
key = self.get_queryset_cache_key()
value = self.get_queryset_from_cache(key)
return value
class IndexView(ArticleListView):
def get_queryset(self):
article_list = Article.objects.filter(type='a',status='p')
# for article in article_list:
# article.body = article.body[0:settings.ARTICLE_SUB_LENGTH]
# # article.body = markdown2.markdown(article.body)
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'
@@ -70,88 +84,61 @@ class ArticleDetailView(DetailView):
pk_url_kwarg = 'article_id'
context_object_name = "article"
def get_object(self):
def get_object(self, queryset=None):
obj = super(ArticleDetailView, self).get_object()
obj.viewed()
# obj.body = markdown2.markdown(obj.body)
self.object = obj
return obj
def get_context_data(self, **kwargs):
articleid = int(self.kwargs[self.pk_url_kwarg])
def get_article(id):
try:
return Article.objects.get(pk=id)
except ObjectDoesNotExist:
return None
comment_form = CommentForm()
u = self.request.user
user = self.request.user
if self.request.user.is_authenticated:
if user.is_authenticated and not user.is_anonymous and user.email and user.username:
comment_form.fields.update({
'email': forms.CharField(widget=forms.HiddenInput()),
'name': forms.CharField(widget=forms.HiddenInput()),
})
user = self.request.user
comment_form.fields["email"].initial = user.email
comment_form.fields["name"].initial = user.username
article_comments = self.object.comment_set.all()
article_comments = self.object.comment_list()
kwargs['form'] = comment_form
kwargs['article_comments'] = article_comments
kwargs['comment_count'] = len(article_comments) if article_comments else 0;
next_article = get_article(articleid + 1)
prev_article = get_article(articleid - 1)
kwargs['next_article'] = next_article
kwargs['prev_article'] = prev_article
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
return super(ArticleDetailView, self).get_context_data(**kwargs)
"""
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
data = form.cleaned_data
pass
"""
'''
class PageDetailView(ArticleDetailView):
model = BlogPage
pk_url_kwarg = 'page_id'
def get_object(self):
obj = super(PageDetailView, self).get_object()
print(obj.title)
obj.viewed()
# obj.body = markdown2.markdown(obj.body)
return obj
'''
class CategoryDetailView(ArticleListView):
# template_name = 'article_index.html'
# context_object_name = 'article_list'
# pk_url_kwarg = 'article_name'
page_type = "分类目录归档"
def get_queryset(self):
categoryname = self.kwargs['category_name']
def get_queryset_data(self):
slug = self.kwargs['category_name']
category = get_object_or_404(Category, slug=slug)
try:
categoryname = categoryname.split('/')[-1]
except:
pass
article_list = Article.objects.filter(category__name=categoryname, status='p')
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.kwargs['category_name']
categoryname = self.categoryname
try:
categoryname = categoryname.split('/')[-1]
except:
@@ -164,9 +151,13 @@ class CategoryDetailView(ArticleListView):
class AuthorDetailView(ArticleListView):
page_type = '作者文章归档'
def get_queryset(self):
def get_queryset_cache_key(self):
author_name = 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)
return article_list
@@ -191,14 +182,25 @@ class TagListView(ListView):
class TagDetailView(ArticleListView):
page_type = '分类标签归档'
def get_queryset(self):
tag_name = self.kwargs['tag_name']
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)
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.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)
@@ -207,19 +209,32 @@ class TagDetailView(ArticleListView):
@csrf_exempt
def fileupload(request):
if request.method == 'POST':
fname = ''
timestr = datetime.datetime.now().strftime('%Y/%m/%d')
basepath = os.path.join(r'/var/www/resource/image/', timestr)
if not os.path.exists(basepath):
os.makedirs(basepath)
fname = ''
response = []
for filename in request.FILES:
fname = filename
timestr = datetime.datetime.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
basepath = r'/var/www/resource/{type}/{timestr}'.format(
type='files' if not isimage else'image', timestr=timestr)
if settings.TESTING:
basepath = settings.BASE_DIR + '/uploads'
url = 'https://resource.lylinux.net/{type}/{timestr}/{filename}'.format(
type='files' if not isimage else'image', timestr=timestr, filename=filename)
if not os.path.exists(basepath):
os.makedirs(basepath)
savepath = os.path.join(basepath, filename)
with open(savepath, 'wb+') as wfile:
for chunk in request.FILES[filename].chunks():
wfile.write(chunk)
return HttpResponse('https://resource.lylinux.net/' + 'image/' + timestr + '/' + fname)
if isimage:
from PIL import Image
image = Image.open(savepath)
image.save(savepath, quality=20, optimize=True)
response.append(url)
return HttpResponse(response)
else:
return HttpResponse("only for post")
@@ -228,32 +243,30 @@ def fileupload(request):
@login_required
def refresh_memcache(request):
try:
result = os.popen(' service memcached restart ').readline()
return HttpResponse(result)
if request.user.is_superuser:
from DjangoBlog.utils import cache
if cache and cache is not None:
cache.clear()
return HttpResponse("ok")
else:
from django.http import HttpResponseForbidden
return HttpResponseForbidden()
except Exception as e:
return HttpResponse(e);
return HttpResponse(e)
"""
class BlogSearchView(SearchView):
form_class = BlogSearchForm
template_name = 'blog/article_detail.html'
model = Article
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
def page_not_found_view(request):
url = request.get_full_path()
return render(request, 'blog/error_page.html',
{'message': '哎呀,您访问的地址 ' + url + ' 是一个未知的地方。请点击首页看看别的?', 'statuscode': '404'})
# context_object_name属性用于给上下文变量取名(在模板中使用该名字)
context_object_name = 'article_list'
def get_queryset(self):
queryset = super(BlogSearchView, self).get_queryset()
# further filter queryset based on some set of criteria
# return queryset.filter(pub_date__gte=date(2015, 1, 1))
return queryset
def server_error_view(request):
return render(request, 'blog/error_page.html',
{'message': '哎呀,出错了,我已经收集到了错误信息,之后会抓紧抢修,请点击首页看看别的?', 'statuscode': '500'})
def get_context_data(self, **kwargs):
tag_name = 'search'
kwargs['page_type'] = 'search'
kwargs['tag_name'] = tag_name
return super(BlogSearchView, self).get_context_data(**kwargs)
"""
def permission_denied_view(request):
return render(request, 'blog/error_page.html',
{'message': '哎呀,您没有权限访问此页面,请点击首页看看别的?', 'statuscode': '403'})
+9 -1
View File
@@ -3,4 +3,12 @@ from django.contrib import admin
# Register your models here.
from .models import Comment
admin.site.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('id', 'body', 'author', 'article', 'last_mod_time')
list_display_links = ('id', 'body')
list_filter = ('author', 'article',)
exclude = ('created_time', 'last_mod_time')
admin.site.register(Comment, CommentAdmin)
+1 -38
View File
@@ -18,52 +18,15 @@ from django.forms import ModelForm
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
"""
class CommentForm(forms.Form):
url = forms.URLField(label='网址', required=False)
email = forms.EmailField(label='电子邮箱', required=False)
name = forms.CharField(label='姓名')
body = forms.CharField(widget=forms.Textarea, label='评论')
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
class LoginCommentForm(ModelForm):
url = forms.URLField(label='网址', required=False)
email = forms.EmailField(label='电子邮箱', required=False, widget=forms.HiddenInput)
name = forms.CharField(label='姓名', widget=forms.HiddenInput)
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
def __init__(self):
pass
class Meta:
model = Comment
fields = ['body']
"""
class CommentForm(ModelForm):
url = forms.URLField(label='网址', required=False)
email = forms.EmailField(label='电子邮箱', required=False)
email = forms.EmailField(label='电子邮箱', required=True)
name = forms.CharField(label='姓名', widget=forms.TextInput(attrs=
{'value': "", 'size': "30", 'maxlength': "245",
'aria-required': 'true'}
))
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
"""
if get_user_model().is_authenticated:
email = forms.EmailField(label='电子邮箱', required=False, widget=forms.HiddenInput)
name = forms.CharField(label='姓名', widget=forms.HiddenInput)
"""
"""
def __init__(self, user, *args, **kwargs):
self.user = user
super(CommentForm, self).__init__(*args, **kwargs)
if self.user.is_authenticated:
self.fields.update({
'email': forms.CharField(widget=forms.HiddenInput()),
'name': forms.CharField(widget=forms.HiddenInput()),
})
"""
class Meta:
model = Comment
+10 -8
View File
@@ -1,18 +1,16 @@
from django.db import models
from django.conf import settings
from blog.models import Article
from blog.models import Article
from DjangoBlog.utils import logger
from django.utils.timezone import now
# Create your models here.
class Comment(models.Model):
#url = models.URLField('地址', blank=True, null=True)
#email = models.EmailField('电子邮件', blank=True, null=True)
body = models.TextField('正文')
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
body = models.TextField('正文', max_length=300)
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者', on_delete=models.CASCADE)
article = models.ForeignKey(Article, verbose_name='文章', on_delete=models.CASCADE)
parent_comment = models.ForeignKey('self', verbose_name="上级评论", blank=True, null=True)
@@ -21,6 +19,10 @@ class Comment(models.Model):
ordering = ['created_time']
verbose_name = "评论"
verbose_name_plural = verbose_name
get_latest_by = 'created_time'
def __str__(self):
return self.body
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
+1 -25
View File
@@ -30,7 +30,6 @@ def parse_commenttree(commentlist, comment):
datas = []
def parse(c):
childs = commentlist.filter(parent_comment=c)
for child in childs:
datas.append(child)
@@ -43,31 +42,8 @@ def parse_commenttree(commentlist, comment):
@register.inclusion_tag('comments/tags/comment_item.html')
def show_comment_item(comment, ischild):
"""评论"""
depth = 1 if ischild else 2;
depth = 1 if ischild else 2
return {
'comment_item': comment,
'depth': depth
}
"""
@register.simple_tag(name='get_comment_count')
def GetCommentCount(parser, token):
commentcount = Comment.objects.filter(article__author_id=token).count()
return "0" if commentcount == 0 else str(commentcount) + " comments"
@register.inclusion_tag('comments/tags/post_comment.html')
def load_post_comment(article, lastform=None):
if not lastform:
form = CommentForm()
form.article_id = article.id
form.parent_comment_id = ''
else:
form = lastform
return {
'article': article,
'form': form
}
"""
+94 -1
View File
@@ -1,3 +1,96 @@
from django.test import TestCase
from django.test import Client, RequestFactory, TestCase
from blog.models import Article, Category, Tag
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
import datetime
from accounts.models import BlogUser
from comments.templatetags.comments_tags import *
from DjangoBlog.utils import get_max_articleid_commentid
# Create your tests here.
class CommentsTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_validate_comment(self):
site = Site.objects.get_current().domain
user = BlogUser.objects.create_superuser(email="liangliangyy1@gmail.com",
username="liangliangyy1", password="liangliangyy1")
self.client.login(username='liangliangyy1', password='liangliangyy1')
category = Category()
category.name = "categoryccc"
category.created_time = datetime.datetime.now()
category.last_mod_time = datetime.datetime.now()
category.save()
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.save()
commenturl = reverse('comments:postcomment', kwargs={'article_id': article.id})
response = self.client.post(commenturl,
{
'body': '123ffffffffff'
})
self.assertEqual(response.status_code, 200)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 0)
response = self.client.post(commenturl,
{
'body': '123ffffffffff',
'email': user.email,
'name': user.username
})
self.assertEqual(response.status_code, 302)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 1)
parent_comment_id = article.comment_list()[0].id
response = self.client.post(commenturl,
{
'body': '''
# Title1
```python
import os
```
[url](https://www.lylinux.org/)
[ddd](http://www.baidu.com)
''',
'email': user.email,
'name': user.username,
'parent_comment_id': parent_comment_id
})
self.assertEqual(response.status_code, 302)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 2)
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)
+27 -8
View File
@@ -6,8 +6,8 @@ from blog.models import Article
from .forms import CommentForm
from django.views.generic.edit import FormView
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth import get_user_model
from django import forms
class CommentPostView(FormView):
@@ -16,12 +16,24 @@ class CommentPostView(FormView):
def get(self, request, *args, **kwargs):
article_id = self.kwargs['article_id']
url = reverse('blog:detail', kwargs={'article_id': article_id})
return HttpResponseRedirect(url)
article = Article.objects.get(pk=article_id)
url = article.get_absolute_url()
return HttpResponseRedirect(url + "#comments")
def form_invalid(self, form):
article_id = self.kwargs['article_id']
article = Article.objects.get(pk=article_id)
u = self.request.user
if self.request.user.is_authenticated:
form.fields.update({
'email': forms.CharField(widget=forms.HiddenInput()),
'name': forms.CharField(widget=forms.HiddenInput()),
})
user = self.request.user
form.fields["email"].initial = user.email
form.fields["name"].initial = user.username
return self.render_to_response({
'form': form,
@@ -38,13 +50,12 @@ class CommentPostView(FormView):
email = form.cleaned_data['email']
username = form.cleaned_data['name']
user = get_user_model().objects.create_user(username=username, email=email, password=None)
author_id = user.pk
user = get_user_model().objects.get_or_create(username=username, email=email)[0]
# auth.login(self.request, user)
comment = form.save(False)
comment.article = article
comment.author = get_user_model().objects.get(pk=author_id)
comment.author = user
if form.cleaned_data['parent_comment_id']:
parent_comment = Comment.objects.get(pk=form.cleaned_data['parent_comment_id'])
@@ -52,5 +63,13 @@ class CommentPostView(FormView):
comment.save(True)
# return HttpResponseRedirect(article.get_absolute_url() + "#div-comment-" + comment.pk)
from DjangoBlog.blog_signals import comment_save_signal
port = 80
try:
# django1.8 没有这个方法...
port = self.request.get_port()
except:
pass
username = self.request.user.username if self.request.user else ''
comment_save_signal.send(sender=self.__class__, comment_id=comment.id, username=username, serverport=port)
return HttpResponseRedirect("%s#div-comment-%d" % (article.get_absolute_url(), comment.pk))
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because it is too large Load Diff
+10
View File
@@ -1,3 +1,13 @@
from django.contrib import admin
# Register your models here.
from .models import OAuthUser
class OAuthUserAdmin(admin.ModelAdmin):
list_display = ('id', 'author', 'nikename', 'type', 'picture', 'email',)
list_display_links = ('id', 'nikename')
list_filter = ('author', 'type',)
admin.site.register(OAuthUser, OAuthUserAdmin)
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: forms.py
@time: 2017/3/7 下午8:58
"""
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"})
View File
+11 -15
View File
@@ -2,28 +2,24 @@ from django.db import models
# Create your models here.
from django.conf import settings
from django.utils.timezone import now
class BaseModel(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='用户')
class OAuthUser(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='用户', blank=True, null=True)
openid = models.CharField(max_length=50)
nikename = models.CharField(max_length=50, verbose_name='昵称')
token = models.CharField(max_length=50)
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)
matedata = models.CharField(max_length=2000, null=True, blank=True)
created_time = models.DateTimeField('创建时间', default=now)
last_mod_time = models.DateTimeField('修改时间', default=now)
def __str__(self):
return self.nikename
class Meta:
abstract = True
class SinaWBUserInfo(BaseModel):
class Meta:
verbose_name = "新浪微博"
verbose_name_plural = verbose_name
class QQUserInfo(BaseModel):
class Meta:
verbose_name = "QQ"
verbose_name = 'oauth用户'
verbose_name_plural = verbose_name
+251 -24
View File
@@ -14,36 +14,38 @@
"""
from abc import ABCMeta, abstractmethod, abstractproperty
from oauth.models import OAuthUser
from django.conf import settings
import requests
import json
import urllib.parse
from DjangoBlog.utils import logger, parse_dict_to_url
class BaseManager(metaclass=ABCMeta):
class BaseOauthManager(metaclass=ABCMeta):
"""获取用户授权"""
AUTH_URL = None
"""获取token"""
TOKEN_URL = None
"""获取用户信息"""
API_URL = None
'''icon图标名'''
ICON_NAME = None
def __init__(self, client_id, client_secret, callback_url, access_token=None, openid=None):
self.client_id = client_id
self.client_secret = client_secret
self.callback_url = callback_url
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 != None
return self.access_token is not None
@property
def is_authorized(self):
return self.is_access_token_set and self.access_token != None and self.openid != None
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):
def get_authorization_url(self, nexturl='/'):
pass
@abstractmethod
@@ -63,26 +65,29 @@ class BaseManager(metaclass=ABCMeta):
return rsp.text
class WBOauthManager(BaseManager):
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, client_id, client_secret, callback_url, access_token=None, openid=None):
super(WBOauthManager, self).__init__(client_id=client_id, client_secret=client_secret,
callback_url=callback_url, access_token=access_token, openid=openid)
def __init__(self, access_token=None, openid=None):
self.client_id = settings.OAHUTH['sina']['appkey']
self.client_secret = settings.OAHUTH['sina']['appsecret']
self.callback_url = settings.OAHUTH['sina']['callbackurl']
super(WBOauthManager, self).__init__(access_token=access_token, openid=openid)
def get_authorization_url(self):
def get_authorization_url(self, nexturl='/'):
params = {
'client_id': self.client_id,
'response_type': 'code',
'redirect_uri': self.callback_url
'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):
print(code)
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
@@ -91,15 +96,8 @@ class WBOauthManager(BaseManager):
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
print(rsp)
# return rsp
obj = json.loads(rsp)
self.access_token = str(obj['access_token'])
self.openid = str(obj['uid'])
return self.get_oauth_userinfo()
try:
obj = json.loads(rsp)
self.access_token = str(obj['access_token'])
self.openid = str(obj['uid'])
return self.get_oauth_userinfo()
@@ -114,4 +112,233 @@ class WBOauthManager(BaseManager):
'access_token': self.access_token
}
rsp = self.do_get(self.API_URL, params)
print(rsp)
try:
datas = json.loads(rsp)
user = OAuthUser()
user.matedata = rsp
user.picture = datas['avatar_large']
user.nikename = 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:
logger.info('weibo oauth error.rsp:' + rsp)
return None
class GoogleOauthManager(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):
self.client_id = settings.OAHUTH['google']['appkey']
self.client_secret = settings.OAHUTH['google']['appsecret']
self.callback_url = settings.OAHUTH['google']['callbackurl']
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, quote_via=urllib.parse.quote)
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)
try:
self.access_token = str(obj['access_token'])
self.openid = str(obj['id_token'])
logger.info(self.ICON_NAME + ' oauth ' + rsp)
return self.access_token
except:
logger.info(self.ICON_NAME + ' oauth error ' + rsp)
return None
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.matedata = rsp
user.picture = datas['picture']
user.nikename = datas['name']
user.openid = datas['sub']
user.token = self.access_token
user.type = 'google'
if datas['email']:
user.email = datas['email']
return user
except:
logger.info('google oauth error.rsp:' + rsp)
return None
class GitHubOauthManager(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):
self.client_id = settings.OAHUTH['github']['appkey']
self.client_secret = settings.OAHUTH['github']['appsecret']
self.callback_url = settings.OAHUTH['github']['callbackurl']
super(GitHubOauthManager, 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,
'scope': 'user'
}
# url = self.AUTH_URL + "?" + urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
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)
try:
from urllib import parse
r = parse.parse_qs(rsp)
self.access_token = (r['access_token'][0])
return self.access_token
except:
return None
def get_oauth_userinfo(self):
params = {
'access_token': self.access_token
}
rsp = self.do_get(self.API_URL, params)
try:
datas = json.loads(rsp)
user = OAuthUser()
user.picture = datas['avatar_url']
user.nikename = datas['name']
user.openid = datas['id']
user.type = 'github'
user.token = self.access_token
user.matedata = rsp
if datas['email']:
user.email = datas['email']
return user
except:
logger.info('github oauth error.rsp:' + rsp)
return None
class FaceBookOauthManager(BaseOauthManager):
AUTH_URL = 'https://www.facebook.com/v2.10/dialog/oauth'
TOKEN_URL = 'https://graph.facebook.com/v2.10/oauth/access_token'
API_URL = 'https://graph.facebook.com/me'
ICON_NAME = 'facebook'
def __init__(self, access_token=None, openid=None):
self.client_id = settings.OAHUTH['facebook']['appkey']
self.client_secret = settings.OAHUTH['facebook']['appsecret']
self.callback_url = settings.OAHUTH['facebook']['callbackurl']
super(FaceBookOauthManager, 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,
'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)
try:
obj = json.loads(rsp)
token = str(obj['access_token'])
self.access_token = token
return self.access_token
except:
return None
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.nikename = datas['name']
user.openid = datas['id']
user.type = 'facebook'
user.token = self.access_token
user.matedata = rsp
if datas['email']:
user.email = datas['email']
if 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.warn(e)
return None
def get_oauth_apps():
applications = BaseOauthManager.__subclasses__()
return list(map(lambda x: x(), applications))
def get_manager_by_type(type):
applications = get_oauth_apps()
finds = list(filter(lambda x: x.ICON_NAME.lower() == type.lower(), applications))
if finds:
return finds[0]
return None
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: __init__.py
@time: 2017/3/4 下午3:22
"""
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: oauth_tags.py
@time: 2017/3/4 下午3:22
"""
from oauth.oauthmanager import get_oauth_apps
from django.core.urlresolvers import reverse
from django import template
from django.conf import settings
register = template.Library()
@register.inclusion_tag('oauth/oauth_applications.html')
def load_oauth_applications(request):
applications = get_oauth_apps()
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))
return {
'apps': apps
}
-10
View File
@@ -1,13 +1,3 @@
from django.test import TestCase
# Create your tests here.
class OAuthTet(TestCase):
def setUp(self):
pass
from oauth.oauthmanager import WBOauthManager
from django.conf import settings
settings.OAHUTH['sina']
manager=WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],client_secret=settings.OAHUTH['sina']['appsecret'],callback_url=settings.OAHUTH['sina']['callbackurl'])
+5 -3
View File
@@ -18,7 +18,9 @@ from django.views.decorators.cache import cache_page
from . import views
urlpatterns = [
url(r'^oauth/wbauthorize/(?P<sitename>\w+)$', views.wbauthorize),
url(r'^oauth/wboauthurl$', views.wboauthurl),
url(r'^oauth/authorize$', views.authorize),
url(r'^oauth/requireemail/(?P<oauthid>\d+).html', views.RequireEmailView.as_view(), name='require_email'),
url(r'^oauth/emailconfirm/(?P<id>\d+)/(?P<sign>\S+).html', views.emailconfirm, name='email_confirm'),
url(r'^oauth/bindsuccess/(?P<oauthid>\d+).html', views.bindsuccess, name='bindsuccess'),
url(r'^oauth/oauthlogin$', views.oauthlogin,name='oauthlogin')
]
+202 -14
View File
@@ -1,24 +1,212 @@
from django.shortcuts import render
# Create your views here.
from .oauthmanager import WBOauthManager
from django.conf import settings
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth import get_user_model
from .models import OAuthUser
from django.contrib.auth import login
from django.shortcuts import get_object_or_404
from django.views.generic import FormView, RedirectView
from oauth.forms import RequireEmailForm
from django.core.urlresolvers import reverse
from DjangoBlog.utils import send_email, get_md5
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseForbidden
from .oauthmanager import get_manager_by_type
def wbauthorize(request, sitename):
manager = WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],
client_secret=settings.OAHUTH['sina']['appsecret'],
callback_url=settings.OAHUTH['sina']['callbackurl'])
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 = request.GET.get('next_url', None)
if not nexturl or nexturl == '/login/':
nexturl = '/'
authorizeurl = manager.get_authorization_url(nexturl)
return HttpResponseRedirect(authorizeurl)
def authorize(request):
manager = None
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)
rsp = manager.get_access_token_by_code(code)
print(rsp)
return HttpResponse(rsp)
nexturl = request.GET.get('next_url', None)
if not nexturl:
nexturl = '/'
if not rsp:
return HttpResponseRedirect(manager.get_authorization_url(nexturl))
user = manager.get_oauth_userinfo()
if user:
if not user.nikename:
import datetime
user.nikename = "djangoblog" + datetime.datetime.now().strftime('%y%m%d%I%M%S')
try:
token = user.token
user = OAuthUser.objects.get(type=type, openid=user.openid)
if token:
user.token = token
except ObjectDoesNotExist:
pass
email = user.email
if email:
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]:
author.username = user.nikename
author.save()
user.author = author
user.save()
login(request, author)
return HttpResponseRedirect(nexturl)
if not email:
user.save()
url = reverse('oauth:require_email', kwargs={
'oauthid': user.id
})
return HttpResponseRedirect(url)
else:
return HttpResponseRedirect(nexturl)
def wboauthurl(request):
manager = WBOauthManager(client_id=settings.OAHUTH['sina']['appkey'],
client_secret=settings.OAHUTH['sina']['appsecret'],
callback_url=settings.OAHUTH['sina']['callbackurl'])
url = manager.get_authorization_url()
return HttpResponse(url)
def emailconfirm(request, id, sign):
if not sign:
return HttpResponseForbidden()
if not get_md5(settings.SECRET_KEY + str(id) + settings.SECRET_KEY).upper() == sign.upper():
return HttpResponseForbidden()
oauthuser = get_object_or_404(OAuthUser, pk=id)
author = None
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.username = oauthuser.nikename
author.save()
"""
if oauthuser.email and author.email:
login(request, author)
return HttpResponseRedirect('/')
"""
oauthuser.author = author
oauthuser.save()
login(request, author)
site = Site.objects.get_current().domain
content = '''
<p>恭喜您,您已经成功绑定您的邮箱,您可以使用{type}来直接免密码登录本网站.欢迎您继续关注本站,地址是</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
'''.format(type=oauthuser.type, url='http://' + site)
send_email(emailto=[oauthuser.email, ], title='恭喜您绑定成功!', 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_md5(settings.SECRET_KEY + str(oauthuser.id) + settings.SECRET_KEY)
site = Site.objects.get_current().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 = """
<p>请点击下面链接绑定您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您!
<br />
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
send_email(emailto=[email, ], title='绑定您的电子邮箱', 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)
title = ''
content = ''
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
if type == 'email':
title = '绑定成功'
content = "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。"
else:
title = '绑定成功'
content = "恭喜您绑定成功,您以后可以使用{type}来直接免密码登录本站啦,感谢您对本站对关注。".format(type=oauthuser.type)
return render(request, 'oauth/bindsuccess.html', {
'title': title,
'content': content
})
+34 -16
View File
@@ -1,16 +1,34 @@
Django
django-autoslug
django-haystack
django-pagedown
django-uuslug
jieba
markdown2
Pillow
PyMySQL
python-memcached
python-slugify
requests
Unidecode
Whoosh
mistune
pygments
appdirs==1.4.3
coverage==4.4.1
Django==1.11.7
django-appconf==1.0.2
django-autoslug==1.9.3
django-compressor==2.2
django-debug-toolbar==1.9.1
django-haystack==2.6.1
django-ipware==1.1.6
django-pagedown==0.1.3
django-uuslug==1.1.8
jieba==0.39
jsonpickle==0.9.5
markdown2==2.3.5
mistune==0.8.1
olefile==0.44
packaging==16.8
Pillow==4.3.0
Pygments==2.2.0
PyMySQL==0.7.11
pyparsing==2.2.0
python-memcached==1.58
python-slugify==1.2.4
pytz==2017.3
rcssmin==1.0.6
requests==2.18.4
rjsmin==1.0.12
six==1.11.0
sqlparse==0.2.4
Unidecode==0.4.21
urllib3==1.22
webencodings==0.5.1
WeRoBot==1.1.1
Whoosh==2.7.4
-2
View File
@@ -1,2 +0,0 @@
User-agent: *
Disallow: /admin/
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: __init__.py.py
@time: 2017/8/27 上午11:40
"""
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: blogapi.py
@time: 2017/8/27 上午11:40
"""
from blog.models import Article, Category, Tag
from haystack.query import EmptySearchQuerySet, SearchQuerySet
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__]
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: commonapi.py
@time: 2017/9/2 上午1:43
"""
import requests
import json
from DjangoBlog.utils import logger
class TuLing():
def __init__(self):
self.__key__ = '2f1446eb0321804291b0a1e217c25bb5'
self.__appid__ = 137762
def __build_req_url(self, content):
return 'http://www.tuling123.com/openapi/api?key=%s&info=%s&userid=%s' % (
self.__key__, content, self.__appid__)
def UserAgent(self, url):
rsp = requests.get(url)
return rsp.content
def getdata(self, content):
try:
requrl = self.__build_req_url(content)
res = self.UserAgent(requrl).decode('utf-8')
jsons = json.loads(res, encoding='utf-8')
if str(jsons["code"]) == '100000':
return jsons["text"]
except Exception as e:
logger.warn(e)
return "哎呀,出错啦。"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: MemcacheStorage.py
@time: 2017/8/27 上午2:42
"""
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)
View File
+11
View File
@@ -0,0 +1,11 @@
from django.contrib import admin
# Register your models here.
from .models import commands
class CommandsAdmin(admin.ModelAdmin):
list_display = ('title', 'command', 'describe')
admin.site.register(commands, CommandsAdmin)
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class ServermanagerConfig(AppConfig):
name = 'servermanager'
+17
View File
@@ -0,0 +1,17 @@
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)
created_time = models.DateTimeField('创建时间', auto_now_add=True)
last_mod_time = models.DateTimeField('修改时间', auto_now=True)
def __str__(self):
return self.title
class Meta:
verbose_name = '命令'
verbose_name_plural = verbose_name
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: robot.py
@time: 2017/8/27 上午1:55
"""
from werobot import WeRoBot
import re
from werobot.replies import ArticlesReply, MusicReply, ImageReply, Article
from .MemcacheStorage import MemcacheStorage
from servermanager.Api.blogapi import BlogApi
from servermanager.Api.commonapi import TuLing
import os
import json
from DjangoBlog.utils import get_md5
from django.conf import settings
import jsonpickle
from servermanager.models import commands
robot = WeRoBot(token='lylinux', enable_session=True)
memstorage = MemcacheStorage()
if memstorage.is_available:
robot.config['SESSION_STORAGE'] = memstorage
else:
from werobot.session.filestorage import FileStorage
import os
from django.conf import settings
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()
tuling = TuLing()
def convert_to_articlereply(articles, message):
reply = ArticlesReply(message=message)
from blog.templatetags.blog_tags import custom_markdown, truncatechars_content
from DjangoBlog.utils import CommonMarkdown
from django.utils.safestring import mark_safe
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_articlereply(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_articlereply(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('^weather\:.*$', re.I))
def weather(message, session):
return "建设中..."
@robot.filter(re.compile('^idcard\:.*$', re.I))
def idcard(message, session):
return "建设中..."
@robot.handler
def echo(message, session):
handler = MessageHandler(message, session)
return handler.handler()
class CommandHandler():
def __init__(self):
self.commands = commands.objects.all()
def run(self, title):
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:
str = os.popen(cmd).read()
return str
except:
return '命令执行出错!'
def get_help(self):
rsp = ''
for cmd in self.commands:
rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe)
return rsp
cmdhandler = CommandHandler()
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:
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 savesession(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.savesession()
return "退出成功"
if info.upper() == 'ADMIN':
self.userinfo.isAdmin = True
self.savesession()
return "输入管理员密码"
if self.userinfo.isAdmin and not self.userinfo.isPasswordSet:
passwd = settings.WXADMIN
if settings.TESTING:
passwd='123'
if passwd.upper() == get_md5(get_md5(info)).upper():
self.userinfo.isPasswordSet = True
self.savesession()
return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助"
else:
if self.userinfo.Count >= 3:
self.userinfo = WxUserInfo()
self.savesession()
return "超过验证次数"
self.userinfo.Count += 1
self.savesession()
return "验证失败,请重新输入管理员密码:"
if self.userinfo.isAdmin and self.userinfo.isPasswordSet:
if self.userinfo.Command != '' and info.upper() == 'Y':
return cmdhandler.run(self.userinfo.Command)
else:
if info.upper() == 'HELPME':
return cmdhandler.get_help()
self.userinfo.Command = info
self.savesession()
return "确认执行: " + info + " 命令?"
rsp = tuling.getdata(info)
return rsp
class WxUserInfo():
def __init__(self):
self.isAdmin = False
self.isPasswordSet = False
self.Count = 0
self.Command = ''
"""
@robot.handler
def hello(message, session):
blogapi = BlogApi()
result = blogapi.search_articles(message.content)
if result:
articles = list(map(lambda x: x.object, result))
reply = convert_to_articlereply(articles, message)
return reply
else:
return '没有找到相关文章。'
"""
+82
View File
@@ -0,0 +1,82 @@
from django.test import Client, RequestFactory, TestCase
from django.contrib.sites.models import Site
from .models import commands
import datetime
from accounts.models import BlogUser
from blog.models import Category, Article
from .robot import search, category, recents
from werobot.messages.messages import TextMessage
from .robot import MessageHandler, CommandHandler
from servermanager.Api.commonapi import TuLing
# Create your tests here.
class ServerManagerTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_tuling(self):
t = TuLing()
content = t.getdata('test')
self.assertIsNotNone(content)
def test_validate_comment(self):
site = Site.objects.get_current().domain
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.created_time = datetime.datetime.now()
c.last_mod_time = datetime.datetime.now()
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 = "nicetitleccc"
rsp = search(s, None)
self.assertTrue(rsp != '没有找到相关文章。')
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()
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python
# encoding: utf-8
"""
@version: ??
@author: liangliangyy
@license: MIT Licence
@contact: liangliangyy@gmail.com
@site: https://www.lylinux.org/
@software: PyCharm
@file: urls.py
@time: 2017/8/27 上午2:27
"""
from django.conf.urls import url
from werobot.contrib.django import make_view
from .robot import robot
urlpatterns = [
url(r'^robot/', make_view(robot)),
]
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
+7 -4
View File
@@ -19,21 +19,24 @@
{{ field.errors }}
{% endfor %}
<input type="hidden" name="next" value="{{ redirect_to }}">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<div class="checkbox">
<a class="pull-right">Need help?</a>
{% comment %}<a class="pull-right">Need help?</a>{% endcomment %}
<label>
<input type="checkbox" value="remember-me"> Stay signed in
</label>
</div>
{% load oauth_tags %}
{% load_oauth_applications request%}
</form>
</div>
<p class="text-center">
<a href="{% url "account:register" %}">Create an account</a>
<a href="{% url "account:register" %}">Create Account</a>
|
<a href="/">Home Page</a>
</p>
</div> <!-- /container -->
+3 -3
View File
@@ -3,7 +3,7 @@
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">Sign in with your Account</h2>
<h2 class="form-signin-heading text-center">Create Your Account</h2>
<div class="card card-signin">
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
@@ -20,7 +20,7 @@
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<button class="btn btn-lg btn-primary btn-block" type="submit">Create Your Account</button>
{% comment %}
<div class="checkbox">
@@ -34,7 +34,7 @@
</div>
<p class="text-center">
<a href="{% url "account:login" %}">LogIn</a>
<a href="{% url "account:login" %}">Sign In</a>
</p>
</div> <!-- /container -->
+18 -5
View File
@@ -1,22 +1,23 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% block header %}
<title>{{ article.title }} | {{ SITE_DESCRIPTION }}</title>
<meta property="og:type" content="article"/>
<meta property="og:title" content="{{ article.title }}"/>
<meta property="og:description" content="{{ article.body|custom_markdown|striptags|truncatewords:20 }}"/>
<meta property="og:url"
content="{{ article.get_absolute_url }}"/>
content="{{ article.get_full_url }}"/>
<meta property="article:published_time" content="{% datetimeformat article.pub_time %}"/>
<meta property="article:modified_time" content="{% datetimeformat article.pub_time %}"/>
<meta property="article:author" content="{{ article.author.get_absolute_url }}"/>
<meta property="article:author" content="{{ article.author.get_full_url }}"/>
<meta property="article:section" content="{{ article.category.name }}"/>
{% for t in article.tags.all %}
<meta property="article:tag" content="{{ t.name }}"/>
{% endfor %}
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
@@ -44,9 +45,21 @@
{% comment %}{% load comments_tags %}
{% load_post_comment article from %}{% endcomment %}
{% include 'comments/tags/comment_list.html' %}
{% include 'comments/tags/post_comment.html' %}
{% if user.is_authenticated %}
{% include 'comments/tags/post_comment.html' %}
{% else %}
<div class="comments-area">
<h3 class="comment-meta">您还没有登录请您<a
href="{% url "account:login" %}?next={{ request.get_full_path }}">登录</a>后发表评论</h3>
{% load oauth_tags %}
{% load_oauth_applications request%}
</div>
{% endif %}
{% endif %}
</div><!-- #primary -->
{% endblock %}
+9 -7
View File
@@ -1,14 +1,14 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
{% if tag_name %}
<title>{{ tag_name }} | {{ SITE_DESCRIPTION }}</title>
<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>
<title>{{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }}</title>
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
{% endif %}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
@@ -30,7 +30,8 @@
{% load_article_detail article True user %}
{% endfor %}
{% if is_paginated %}
<nav id="nav-below" class="navigation" role="navigation">
{% load_pagination_info page_obj page_type tag_name %}
{% comment %} <nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">文章导航</h3>
{% if page_obj.has_next %}
<div class="nav-previous"><a
@@ -43,16 +44,17 @@
class="meta-nav"></span></a>
</div>
{% endif %}
</nav><!-- .navigation -->
</nav><!-- .navigation -->{% endcomment %}
{% endif %}
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar request.user %}
{% load_sidebar user %}
{% endblock %}
+45
View File
@@ -0,0 +1,45 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
{% if tag_name %}
{% if statuscode == '404' %}
<title>404 NotFound</title>
{% elif statuscode == '500' %}
<title>Permission Denied</title>
{% elif statuscode == '403' %}
<title>500 Error</title>
{% else %}
<title></title>
{% endif %}
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{% endif %}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<h1 class="archive-title">{{ message }}</h1>
</header><!-- .archive-header -->
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user %}
{% endblock %}
+3 -4
View File
@@ -1,10 +1,9 @@
{% load blog_tags %}
<article id="post-{{ article.pk }} "
class="post-3815 post type-post status-publish format-standard hentry"
itemscope itemtype="http://schema.org/Article">
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
<header class="entry-header">
<h1 class="entry-title" itemprop="name">
<h1 class="entry-title">
{% if isindex %}
<a href="{{ article.get_absolute_url }}"
rel="bookmark">{{ article.title }}</a>
@@ -39,7 +38,7 @@
<div class="entry-content" itemprop="articleBody">
{% if isindex %}
{{ article.body|truncatechars_content|custom_markdown }}
{{ article.body|custom_markdown|truncatechars_content }}
<p class='read-more'><a
href=' {{ article.get_absolute_url }}'>Read more</a></p>
{% else %}
+13 -10
View File
@@ -4,7 +4,7 @@
itemprop="datePublished" content="{% datetimeformat article.created_time %}"
rel="bookmark">
<time class="entry-date"
<time class="entry-date updated"
datetime="{{ article.created_time }}">
{% datetimeformat article.created_time %}</time>
</a>
@@ -23,17 +23,20 @@
标签
{% endif %}
{% endif %}
<span class="by-author">作者是<span class="author vcard"><a class="url fn n"
href="{{ article.author.get_absolute_url }}"
title="查看所有由{{ article.author.username }}发布的文章"
rel="author">
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span class="by-author">作者是
<span class="author vcard">
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
title="查看所有由{{ article.author.username }}发布的文章"
rel="author">
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }}
</span></span>
</a></span>
{{ article.author.username }}
</span>
</span>
</a>
</span>
{% if user.is_authenticated %}
<a href="{{ article.get_admin_url }}">编辑</a>
{% endif %}
@@ -0,0 +1,14 @@
<nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">文章导航</h3>
{% if page_obj.has_next and next_url%}
<div class="nav-previous"><a
href="{{ next_url }}"><span
class="meta-nav">&larr;</span> 早期文章</a></div>
{% endif %}
{% if page_obj.has_previous and previous_url %}
<div class="nav-next"><a href="{{ previous_url }}">较新文章
<span
class="meta-nav"></span></a>
</div>
{% endif %}
</nav><!-- .navigation -->
+37 -5
View File
@@ -8,6 +8,17 @@
</div>
</form>
</aside>
{% if extra_sidebars %}
{% for sidebar in extra_sidebars %}
<aside class="widget_text widget widget_custom_html"><h3 class="widget-title">
{{ sidebar.name }}</h3>
<div class="textwidget custom-html-widget">
{{ sidebar.content }}
</div>
</aside>
{% endfor %}
{% endif %}
{% if most_read_articles %}
<aside id="views-4" class="widget widget_views"><h3 class="widget-title">Views</h3>
@@ -41,8 +52,7 @@
{% for c in sidebar_comments %}
<li class="recentcomments">
<span class="comment-author-link">
<a href="https://blog.misuzu.moe/" rel="external nofollow"
class="url">{{ c.author.username }}</a></span>
{{ c.author.username }}</span>
发表在
<a href="{{ c.article.get_absolute_url }}">{{ c.article.title }}</a>
</li>
@@ -77,6 +87,28 @@
{% if show_adsense %}
{% include 'share_layout/adsense.html' %}
{% endif %}
{% if sidebar_tags %}
<aside id="tag_cloud-2" class="widget widget_tag_cloud"><h3 class="widget-title">标签云</h3>
<div class="tagcloud">
{% for tag,count,size in sidebar_tags %}
<a href="{{ tag.get_absolute_url }}"
class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}"
style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}
</a>
{% endfor %}
</div>
</aside>
{% endif %}
<aside id="text-2" class="widget widget_text"><h3 class="widget-title">欢迎您star或者fork本站源代码</h3>
<div class="textwidget">
<p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&amp;label=Star"
alt="GitHub stars"></a> <a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork"
alt="GitHub forks"></a></p>
</div>
</aside>
<aside id="meta-2" class="widget widget_meta"><h3 class="widget-title">功能</h3>
<ul>
<li><a href="{{ SITE_BASE_URL }}/admin/">管理站点</a></li>
@@ -85,10 +117,10 @@
</li>
{% else %}
<li><a href="{% url "account:login" %}"></a>
<li><a href="{% url "account:login" %}"></a>
{% endif %}
<li><a href="http://gitbook.lylinux.net" target="_blank" rel="nofollow">GitBook</a></li>
</ul>
</aside>
<div id="rocket" class="show" title="点我返回顶部"></div>
</div><!-- #secondary -->
+8 -2
View File
@@ -7,7 +7,13 @@
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
class="avatar avatar-96 photo" height="96" width="96">
<cite class="fn">
<a rel="nofollow" href="{{ comment_item.author.get_absolute_url }}" rel="external nofollow"
<a rel="nofollow"
{% if comment_item.author.is_superuser %}
href="{{ comment_item.author.get_absolute_url }}"
{% else %}
href="#"
{% endif %}
rel="external nofollow"
class="url">{{ comment_item.author.username }}
</a>
</cite>
@@ -18,7 +24,7 @@
{{ comment_item.created_time }}
</div>
<p>{{ comment_item.body }}</p>
<p>{{ comment_item.body |custom_markdown }}</p>
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)"
+2 -2
View File
@@ -1,12 +1,12 @@
<section id="comments" class="themeform">
{% load blog_tags %}
{% load comments_tags %}
<h3 class="heading">{{ comment_count }} 条回复</h3>
{% comment %}<h3 class="heading">{{ comment_count }} 条回复</h3>{% endcomment %}
<ul class="comment-tabs group">
<li class="active"><a href="#commentlist-container"><i
class="fa fa-comments-o"></i>评论<span>{{ comment_count }}</span></a></li>
<li class=""><a href="#pinglist-container"><i class="fa fa-share"></i>引用<span>0</span></a></li>
{% comment %}<li class=""><a href="#pinglist-container"><i class="fa fa-share"></i>引用<span>0</span></a></li>{% endcomment %}
</ul>
{% if article_comments %}
<div id="commentlist-container" class="comment-tab" style="display: block;">
+3 -3
View File
@@ -47,13 +47,13 @@
</p>{% endcomment %}
{{ form.parent_comment_id }}
<p class="form-submit">
<div class="form-submit">
<span class="comment-markdown"> 支持markdown</span>
<input name="submit" type="submit" id="submit" class="submit" value="发表评论"/>
<small class="cancel-comment" id="cancel_comment" style="display: none">
<a href="javascript:void(0)" id="cancel-comment-reply-link" onclick="cancel_reply()">取消回复</a>
</small>
</p>
</div>
</form>
</div><!-- #respond -->
+22
View File
@@ -0,0 +1,22 @@
{% extends 'share_layout/base.html' %}
{% block header %}
<title> {{ title }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<h2 class="archive-title"> {{ content }}</h2>
</header><!-- .archive-header -->
<br/>
<header class="archive-header" style="text-align: center">
<a href="{% url "account:login" %}">Sign In</a>
|
<a href="/">Home Page</a>
</header><!-- .archive-header -->
</div>
</div>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
<div class="widget-login">
<small>
快捷登录:
</small>
{% for icon,url in apps %}
<a href="{{ url }}" rel="nofollow">
<span class="icon-sn-{{ icon }}"></span>
</a>
{% endfor %}
</div>
+46
View File
@@ -0,0 +1,46 @@
{% extends 'share_layout/base_account.html' %}
{% load static %}
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">Binding E-mail account</h2>
<div class="card card-signin">
{% if picture %}
<img class="img-circle profile-img" src="{{ picture }}" alt="">
{% else %}
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
{% endif %}
<form class="form-signin" action="" method="post">
{% csrf_token %}
{% comment %}<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>{% endcomment %}
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
{% comment %}
<div class="checkbox">
<a class="pull-right">Need help?</a>
<label>
<input type="checkbox" value="remember-me"> Stay signed in
</label>
</div>
{% endcomment %}
</form>
</div>
<p class="text-center">
<a href="{% url "account:login" %}">Sign In</a>
</p>
</div> <!-- /container -->
{% endblock %}
+11 -7
View File
@@ -1,12 +1,9 @@
<script type="text/javascript">
var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");
document.write(unescape("%3Cspan id='cnzz_stat_icon_1261145394'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/stat.php%3Fid%3D1261145394%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
@@ -16,5 +13,12 @@
ga('create', 'UA-27354903-4', 'auto');
ga('send', 'pageview');
</script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?30dd00aec5f76607ba37c6770ae48e45";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<a href="https://tongji.baidu.com/sc-web/3478620/home/ico?siteId=11261596" target="_blank" rel="nofollow">百度统计</a>
+32 -35
View File
@@ -1,4 +1,6 @@
{% load static %}
{% load cache %}
{% load compress %}
<!DOCTYPE html>
<!--[if IE 7]>
<html class="ie ie7" lang="zh-CN" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
@@ -15,33 +17,31 @@
{% block header %}
{% endblock %}
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<link rel="pingback" href="https://www.lylinux.org/xmlrpc.php"/>
<!--[if lt IE 9]>
<script src="{% static 'blog/js/html5.js' %}" type="text/javascript"></script>
<![endif]-->
<link rel='dns-prefetch' href='//fonts.googleapis.com'/>
<link rel='dns-prefetch' href='//s.w.org'/>
<link rel="alternate" type="application/rss+xml" title="{{ SITE_NAME }} &raquo; Feed"
href="{{ SITE_BASE_URL }}feed"/>
<link rel="alternate" type="application/rss+xml" title="{{ SITE_NAME }} &raquo; 评论Feed"
href="https://www.lylinux.org/comments/feed"/>
<link rel='stylesheet' id='twentytwelve-fonts-css'
href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700&#038;subset=latin,latin-ext'
type='text/css' media='all'/>
<link rel='stylesheet' id='twentytwelve-style-css' href='{% static 'blog/css/style.css' %}' type='text/css'
media='all'/>
{% comment %}<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>{% endcomment %}
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentytwelve-ie-css' href='{% static 'blog/css/ie.css' %}' type='text/css' media='all' />
<![endif]-->
{% comment %}<script type='text/javascript' src='{% static 'blog/js/jquery.js' %}' defer='defer'></script>
<script type='text/javascript' src='{% static 'blog/js/jquery-migrate.min.js' %}' defer='defer'></script>{% endcomment %}
<script type="text/javascript" src="{% static 'blog/js/jquery-3.1.1.js' %}"></script>
<link rel="stylesheet" href="{% static 'pygments/default.css' %}"/>
{% compress css %}
<link rel='stylesheet' id='twentytwelve-style-css' href='{% static 'blog/css/style.css' %}' type='text/css'
media='all'/>
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
{% comment %}<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>{% endcomment %}
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentytwelve-ie-css' href='{% static 'blog/css/ie.css' %}' type='text/css' media='all' />
<![endif]-->
<link rel="stylesheet" href="{% static 'pygments/default.css' %}"/>
<link rel="stylesheet" href="{% static 'blog/css/nprogress.css' %}">
{% block compress_css %}
{% endblock %}
{% endcompress %}
</head>
<body class="home blog custom-font-enabled">
@@ -53,38 +53,35 @@
<h2 class="site-description">{{ SITE_DESCRIPTION }}</h2>
</hgroup>
{% cache 36000 nav %}
{% include 'share_layout/nav.html' %}
{% endcache %}
{% include 'share_layout/nav.html' %}
</header><!-- #masthead -->
<div id="main" class="wrapper">
{% block content %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% cache 36000 sidebar request.user.username %}
{% block sidebar %}
{% endblock %}
{% endcache %}
</div><!-- #main .wrapper -->
{% include 'share_layout/footer.html' %}
</div><!-- #page -->
{% comment %}
<div id="wp-auto-top">
<div id="wp-auto-top-top"></div>
<div id="wp-auto-top-bottom"></div>
</div>
<script> var commentPositionId = '.ds-comments:last';
var wpAutoTopSpeed = 1;</script>
<div id="su-footer-links" style="text-align: center;"></div>
{% endcomment %}
{% comment %}<script type="application/javascript" src="{% static 'blog/js/navigation.js' %}" defer="defer"></script>{% endcomment %}
</body>
<footer>
{% comment %}<link href="{% static 'highlight/styles/default.css' %}" rel="stylesheet">
<script type="application/javascript" src="{% static 'highlight/highlight.pack.js' %}"></script>
<script>hljs.initHighlightingOnLoad();</script>{% endcomment %}
<script src="{% static 'blog/js/blog.js' %}" type="application/javascript"></script>
<script src="{% static 'blog/js/navigation.js' %}" type="application/javascript"></script>
{% compress js %}
<script type="text/javascript" src="{% static 'blog/js/jquery-3.1.1.js' %}"></script>
<script type="text/javascript" src="{% static 'blog/js/nprogress.js' %}"></script>
<script src="{% static 'blog/js/blog.js' %}" type="application/javascript"></script>
<script src="{% static 'blog/js/navigation.js' %}" type="application/javascript"></script>
{% block compress_js %}
{% endblock %}
{% endcompress %}
{% block footer %}
{% endblock %}
</footer>
+17 -14
View File
@@ -11,19 +11,22 @@
<link rel="icon" href="../../favicon.ico">
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
<!-- Bootstrap core CSS -->
<link href="{% static 'assets/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<link href="{% static 'assets/css/ie10-viewport-bug-workaround.css' %}" rel="stylesheet">
<!-- TODC Bootstrap core CSS -->
<link href="{% static 'assets/css/todc-bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{% static 'assets/css/signin.css' %}" rel="stylesheet">
<script src="{% static 'assets/js/ie-emulation-modes-warning.js' %}"></script>
{% load compress %}
{% compress css %}
<!-- Bootstrap core CSS -->
<link href="{% static 'assets/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<link href="{% static 'assets/css/ie10-viewport-bug-workaround.css' %}" rel="stylesheet">
<!-- TODC Bootstrap core CSS -->
<link href="{% static 'assets/css/todc-bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{% static 'assets/css/signin.css' %}" rel="stylesheet">
{% endcompress %}
{% compress js %}
<script src="{% static 'assets/js/ie10-viewport-bug-workaround.js' %}"></script>
<script src="{% static 'assets/js/ie-emulation-modes-warning.js' %}"></script>
{% endcompress %}
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
@@ -36,6 +39,6 @@
{% endblock %}
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="{% static 'assets/js/ie10-viewport-bug-workaround.js' %}"></script>
</body>
</html>
+15 -2
View File
@@ -1,8 +1,9 @@
<footer id="colophon" role="contentinfo">
<div class="site-info">
<div class="site-info" style="text-align: center">
Copyright&copy;&nbsp;2017&nbsp;
<a href="{{ SITE_BASE_URL }}" target="blank">{{ SITE_NAME }}</a>
&nbsp;|&nbsp;
<a href="{{ SITE_BASE_URL }}sitemap.xml" title="SiteMap" target="_blank">
SiteMap
</a>
@@ -16,4 +17,16 @@
{% include 'share_layout/analyticscode.html' %}
</div>
</footer><!-- #colophon -->
<div class="site-info" style="text-align: center">
Powered by
<a href="https://www.djangoproject.com/" rel="nofollow" target="blank">Django</a>
&nbsp;|&nbsp;
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow" target="blank">LiangLiangyy</a>
|
<a href="https://www.lylinux.net" target="blank">Lylinux</a>
|
本页面加载耗时:<!!LOAD_TIMES!!>s
</div>
</footer><!-- #colophon -->
+2
View File
@@ -0,0 +1,2 @@
import pymysql
pymysql.install_as_MySQLdb()
+33
View File
@@ -0,0 +1,33 @@
appdirs==1.4.3
coverage==4.4.1
Django==1.11.7
django-appconf==1.0.2
django-autoslug==1.9.3
django-compressor==2.2
django-debug-toolbar==1.9.1
django-haystack==2.6.1
django-ipware==1.1.6
django-pagedown==0.1.3
django-uuslug==1.1.8
jieba==0.39
jsonpickle==0.9.5
markdown2==2.3.5
mistune==0.8.1
olefile==0.44
packaging==16.8
Pillow==4.3.0
Pygments==2.2.0
PyMySQL==0.7.11
pyparsing==2.2.0
python-slugify==1.2.4
pytz==2017.3
rcssmin==1.0.6
requests==2.18.4
rjsmin==1.0.12
six==1.11.0
sqlparse==0.2.4
Unidecode==0.4.21
urllib3==1.22
webencodings==0.5.1
WeRoBot==1.1.1
Whoosh==2.7.4
+320
View File
@@ -0,0 +1,320 @@
"""
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 sys
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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 = '&3g0bdza#c%dm1lf%5gi&0-*53p3t0m*hmcvo29cn^$ji7je(c'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# DEBUG = False
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
# ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['www.lylinux.net', '127.0.0.1', 'example.com']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'django.contrib.sitemaps',
'pagedown',
'haystack',
'blog',
'accounts',
'comments',
'oauth',
'servermanager',
'compressor'
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.gzip.GZipMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.cache.FetchFromCacheMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.http.ConditionalGetMiddleware',
'blog.middleware.OnlineMiddleware'
]
ROOT_URLCONF = 'DjangoBlog.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'blog.context_processors.seo_processor'
],
},
},
]
WSGI_APPLICATION = 'DjangoBlog.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangoblog',
'USER': 'travis',
'PASSWORD': '',
'HOST': '127.0.0.1',
# 'HOST': '192.168.1.120',
# 'USER': 'root',
# 'PASSWORD': 'root',
'PORT': 3306,
}
}
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
SITE_ROOT = os.path.dirname(os.path.abspath(__file__))
SITE_ROOT = os.path.abspath(os.path.join(SITE_ROOT, '../'))
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'DjangoBlog.whoosh_cn_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
},
}
# 自动更新搜索索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 允许使用用户名或密码登录
AUTHENTICATION_BACKENDS = ['accounts.user_login_backend.EmailOrUsernameModelBackend']
STATIC_ROOT = os.path.join(SITE_ROOT, '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'
SITE_NAME = '且听风吟'
SITE_URL = 'http://www.lylinux.net'
SITE_DESCRIPTION = '大巧无工,重剑无锋.'
SITE_SEO_DESCRIPTION = '小站主要用来分享和记录学习经验,教程,记录个人生活的点滴以及一些随笔.欢迎大家访问小站'
SITE_SEO_KEYWORDS = 'linux,apache,mysql,服务器,ubuntu,shell,web,csharp,.net,asp,mac,swift'
ARTICLE_SUB_LENGTH = 300
SHOW_GOOGLE_ADSENSE = False
# bootstrap颜色样式
BOOTSTRAP_COLOR_TYPES = [
'default', 'primary', 'success', 'info', 'warning', 'danger'
]
# 侧边栏文章数目
SIDEBAR_ARTICLE_COUNT = 10
# 侧边栏评论数目
SIDEBAR_COMMENT_COUNT = 5
# 分页
PAGINATE_BY = 10
# http缓存时间
CACHE_CONTROL_MAX_AGE = 2592000
# cache setting
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'django_cache',
}
}
CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 10
CACHE_MIDDLEWARE_KEY_PREFIX = "djangoblog"
CACHE_MIDDLEWARE_ALIAS = 'default'
# SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# SESSION_CACHE_ALIAS = 'default'
OAHUTH = {
'sina': {
'appkey': '3161614143',
'appsecret': 'ee17c099317f872eeddb25204ea46721',
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=weibo'
},
'google': {
'appkey': 'googleappkey',
'appsecret': 'googleappsecret',
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=google'
},
'github': {
'appkey': 'githubappkey',
'appsecret': 'githubappsecret',
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=github'
},
'facebook': {
'appkey': 'facebookappkey',
'appsecret': 'facebookappsecret',
'callbackurl': 'http://www.lylinux.net/oauth/authorize?type=facebook'
}
}
SITE_ID = 1
BAIDU_NOTIFY_URL = "http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn&type=original"
# Emial:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
# EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.exmail.qq.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')
# 设置debug=false 未处理异常邮件通知
ADMINS = [('liangliang', 'liangliangyy@gmail.com')]
# 微信管理员密码(两次md5获得)
WXADMIN = '995F03AC401D6CABABAEF756FC4D43C7'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s',
},
'simple': {
'format': '%(levelname)s %(asctime)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.RotatingFileHandler',
'filename': 'djangoblog.log',
'maxBytes': 16777216, # 16 MB
'formatter': 'verbose'
},
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'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'
]