给blog和comments添加注释

skyEmpty 3 months ago
parent b4c5832998
commit 80cbcd5975

@ -0,0 +1,5 @@
"""
本包是DjangoBlog的博客应用(blog app)
它包含了博客的核心功能如文章分类标签评论等
"""

@ -0,0 +1,2 @@
# 管理命令包:包含博客相关的自定义 Django 管理命令。
# 仅增加中文注释,不修改或影响命令的加载与执行。

@ -1,3 +1,6 @@
# 命令说明重建搜索索引Elasticsearch初始化耗时与文章索引。
# 用法:`python manage.py build_index`(需启用 ELASTICSEARCH_ENABLED
# 注意:该命令会删除并重建文章索引,适合部署后或数据结构变更时执行。
from django.core.management.base import BaseCommand
from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \
@ -9,6 +12,7 @@ class Command(BaseCommand):
help = 'build search index'
def handle(self, *args, **options):
# 执行索引构建流程:初始化耗时索引、删除旧文章索引并重建
if ELASTICSEARCH_ENABLED:
ElaspedTimeDocumentManager.build_index()
manager = ElapsedTimeDocument()

@ -1,3 +1,5 @@
# 命令说明:生成搜索词(标签与分类名)列表并输出。
# 用法:`python manage.py build_search_words`,可用于搜索联想或词典构建。
from django.core.management.base import BaseCommand
from blog.models import Tag, Category
@ -8,6 +10,7 @@ class Command(BaseCommand):
help = 'build search words'
def handle(self, *args, **options):
# 收集 Tag 与 Category 的名称,去重后逐行输出到标准输出
datas = set([t.name for t in Tag.objects.all()] +
[t.name for t in Category.objects.all()])
print('\n'.join(datas))

@ -1,3 +1,5 @@
# 命令说明:清空全站缓存。
# 用法:`python manage.py clear_cache`,可在部署或数据更新后使用。
from django.core.management.base import BaseCommand
from djangoblog.utils import cache
@ -7,5 +9,6 @@ class Command(BaseCommand):
help = 'clear the whole cache'
def handle(self, *args, **options):
# 清空缓存并输出成功提示信息
cache.clear()
self.stdout.write(self.style.SUCCESS('Cleared cache\n'))

@ -1,3 +1,5 @@
# 命令说明:批量创建测试数据(用户、分类、标签、文章)。
# 用法:`python manage.py create_testdata`,便于本地开发与演示使用。
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password
from django.core.management.base import BaseCommand
@ -9,6 +11,7 @@ class Command(BaseCommand):
help = 'create test datas'
def handle(self, *args, **options):
# 1) 创建测试用户2) 创建父/子分类3) 批量创建文章并关联标签4) 清空缓存
user = get_user_model().objects.get_or_create(
email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0]

@ -1,3 +1,5 @@
# 命令说明:向百度推送站点 URL文章/标签/分类/全部)。
# 用法:`python manage.py ping_baidu <data_type>`,其中 data_type 可为 all/article/tag/category。
from django.core.management.base import BaseCommand
from djangoblog.spider_notify import SpiderNotify
@ -11,6 +13,7 @@ class Command(BaseCommand):
help = 'notify baidu url'
def add_arguments(self, parser):
# 定义命令行参数 data_type用于选择推送范围
parser.add_argument(
'data_type',
type=str,
@ -22,10 +25,12 @@ class Command(BaseCommand):
help='article : all article,tag : all tag,category: all category,all: All of these')
def get_full_url(self, path):
# 将相对路径转换为完整 URL带站点域名
url = "https://{site}{path}".format(site=site, path=path)
return url
def handle(self, *args, **options):
# 根据 data_type 收集对应 URL调用 SpiderNotify 推送到百度
type = options['data_type']
self.stdout.write('start get %s' % type)

@ -1,3 +1,5 @@
# 命令说明:同步 OAuth 用户头像到本地或设置默认头像。
# 用法:`python manage.py sync_user_avatar`,用于批量校验与更新头像 URL。
import requests
from django.core.management.base import BaseCommand
from django.templatetags.static import static
@ -11,6 +13,7 @@ class Command(BaseCommand):
help = 'sync user avatar'
def test_picture(self, url):
# 测试图片 URL 是否可访问(超时 2 秒),用于校验头像有效性
try:
if requests.get(url, timeout=2).status_code == 200:
return True
@ -18,6 +21,9 @@ class Command(BaseCommand):
pass
def handle(self, *args, **options):
# 逐个用户同步头像:
# - 若头像是静态路径但不可访问,则尝试从 OAuth metadata 拉取并保存;
# - 若没有头像或拉取失败,设置为默认占位头像。
static_url = static("../")
users = OAuthUser.objects.all()
self.stdout.write(f'开始同步{len(users)}个用户头像')

@ -1,4 +1,6 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 迁移说明:初始化 Blog 应用的核心数据表(文章、分类、标签、友情链接、侧边栏、站点设置)。
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑与内容。
from django.conf import settings
from django.db import migrations, models

@ -1,4 +1,6 @@
# Generated by Django 4.1.7 on 2023-03-29 06:08
# 迁移说明:为站点设置增加公共头部与公共尾部字段。
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑。
from django.db import migrations, models

@ -1,4 +1,6 @@
# Generated by Django 4.2.1 on 2023-05-09 07:45
# 迁移说明:为站点设置新增“评论是否需要审核”的布尔配置项。
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑。
from django.db import migrations, models

@ -1,4 +1,6 @@
# Generated by Django 4.2.1 on 2023-05-09 07:51
# 迁移说明:重命名 BlogSettings 中多个字段analyticscode→analytics_code、beiancode→beian_code、sitename→site_name
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑。
from django.db import migrations

@ -1,4 +1,7 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
# 迁移说明:统一与英文化模型 verbose 名称、调整字段命名与时间字段creation_time/last_modify_time
# 并修改多个模型字段的 verbose_name 与选项。
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑。
from django.conf import settings
from django.db import migrations, models

@ -1,4 +1,6 @@
# Generated by Django 4.2.7 on 2024-01-26 02:41
# 迁移说明:更新 BlogSettings 的 verbose 名称为英文描述。
# 此文件由 Django 自动生成。仅增加中文注释,不更改迁移逻辑。
from django.db import migrations

@ -0,0 +1,2 @@
# 迁移包:包含 Blog 应用的数据库迁移文件。
# 仅增加中文注释,不影响 Django 的迁移机制。

@ -1,13 +1,25 @@
# -*- coding: utf-8 -*-
"""
本模块定义了Haystack搜索索引用于文章模型的全文搜索
"""
from haystack import indexes
from blog.models import Article
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
"""
文章搜索索引
为Article模型创建一个搜索引擎索引允许用户通过关键字搜索文章
`text` 字段是主要的搜索字段它使用一个模板来组合所有要索引的字段
"""
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
"""返回要索引的模型"""
return Article
def index_queryset(self, using=None):
"""返回要索引的查询集,这里只索引已发布的文章"""
return self.get_model().objects.filter(status='p')

@ -1,9 +0,0 @@
.button {
border: none;
padding: 4px 80px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
}

@ -1,47 +0,0 @@
let wait = 60;
function time(o) {
if (wait == 0) {
o.removeAttribute("disabled");
o.value = "获取验证码";
wait = 60
return false
} else {
o.setAttribute("disabled", true);
o.value = "重新发送(" + wait + ")";
wait--;
setTimeout(function () {
time(o)
},
1000)
}
}
document.getElementById("btn").onclick = function () {
let id_email = $("#id_email")
let token = $("*[name='csrfmiddlewaretoken']").val()
let ts = this
let myErr = $("#myErr")
$.ajax(
{
url: "/forget_password_code/",
type: "POST",
data: {
"email": id_email.val(),
"csrfmiddlewaretoken": token
},
success: function (result) {
if (result != "ok") {
myErr.remove()
id_email.after("<ul className='errorlist' id='myErr'><li>" + result + "</li></ul>")
return
}
myErr.remove()
time(ts)
},
error: function (e) {
alert("发送失败,请重试")
}
}
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,13 +0,0 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*
* See the Getting Started docs for more information:
* http://getbootstrap.com/getting-started/#support-ie10-width
*/
@-ms-viewport { width: device-width; }
@-o-viewport { width: device-width; }
@viewport { width: device-width; }

@ -1,58 +0,0 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #fff;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin-heading {
margin: 0 0 15px;
font-size: 18px;
font-weight: 400;
color: #555;
}
.form-signin .checkbox {
margin-bottom: 10px;
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: 10px;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
}
.card {
width: 304px;
padding: 20px 25px 30px;
margin: 0 auto 25px;
background-color: #f7f7f7;
border-radius: 2px;
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
}
.card-signin {
width: 354px;
padding: 40px;
}
.card-signin .profile-img {
display: block;
width: 96px;
height: 96px;
margin: 0 auto 10px;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

@ -1,51 +0,0 @@
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
// IT'S JUST JUNK FOR OUR DOCS!
// ++++++++++++++++++++++++++++++++++++++++++
/*!
* Copyright 2014-2015 Twitter, Inc.
*
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see https://creativecommons.org/licenses/by/3.0/.
*/
// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes.
(function () {
'use strict';
function emulatedIEMajorVersion() {
var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
if (groups === null) {
return null
}
var ieVersionNum = parseInt(groups[1], 10)
var ieMajorVersion = Math.floor(ieVersionNum)
return ieMajorVersion
}
function actualNonEmulatedIEMajorVersion() {
// Detects the actual version of IE in use, even if it's in an older-IE emulation mode.
// IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx
// @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx
var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line
if (jscriptVersion === undefined) {
return 11 // IE11+ not in emulation mode
}
if (jscriptVersion < 9) {
return 8 // IE8 (or lower; haven't tested on IE<8)
}
return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode
}
var ua = window.navigator.userAgent
if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) {
return // Opera, which might pretend to be IE
}
var emulated = emulatedIEMajorVersion()
if (emulated === null) {
return // Not IE
}
var nonEmulated = actualNonEmulatedIEMajorVersion()
if (emulated !== nonEmulated) {
window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
}
})();

@ -1,23 +0,0 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
// See the Getting Started docs for more information:
// http://getbootstrap.com/getting-started/#support-ie10-width
(function () {
'use strict';
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
var msViewportStyle = document.createElement('style')
msViewportStyle.appendChild(
document.createTextNode(
'@-ms-viewport{width:auto!important}'
)
)
document.querySelector('head').appendChild(msViewportStyle)
}
})();

@ -1,377 +0,0 @@
/*
Styles for older IE versions (previous to IE9).
*/
/* 设置默认背景色 */
body {
background-color: blue;
}
/* 自定义空背景样式 */
body.custom-background-empty {
background-color: #fff;
}
/* 针对特定背景类移除阴影并重置边距与内边距 */
body.custom-background-empty .site,
body.custom-background-white .site {
box-shadow: none;
margin-bottom: 0;
margin-top: 0;
padding: 0;
}
/* 屏幕阅读器文本隐藏处理兼容旧版IE */
.assistive-text,
.site .screen-reader-text {
clip: rect(1px 1px 1px 1px); /* IE6, IE7 需要这种写法 */
}
/* 全宽布局下的内容区域设置 */
.full-width .site-content {
float: none;
width: 100%;
}
/* 图片尺寸控制以防止在 IE8 中被拉伸 */
img.size-full,
img.size-large,
img.header-image,
img.wp-post-image,
img[class*="align"],
img[class*="wp-image-"],
img[class*="attachment-"] {
width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */
}
/* 作者头像浮动定位 */
.author-avatar {
float: left;
margin-top: 8px;
margin-top: 0.571428571rem;
}
/* 作者描述信息右侧显示 */
.author-description {
float: right;
width: 80%;
}
/* 主站点容器样式:居中、阴影效果及最大宽度限制 */
.site {
box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3);
margin: 48px auto;
max-width: 960px;
overflow: hidden;
padding: 0 40px;
}
/* 内容区左侧浮动并设定宽度比例 */
.site-content {
float: left;
width: 65.104166667%;
}
/* 特定页面模板下内容区占满整行 */
body.template-front-page .site-content,
body.attachment .site-content,
body.full-width .site-content {
width: 100%;
}
/* 小工具区域右侧浮动并设定固定百分比宽度 */
.widget-area {
float: right;
width: 26.041666667%;
}
/* 站点头部标题左对齐 */
.site-header h1,
.site-header h2 {
text-align: left;
}
/* 站点头部主标题字体大小和行高调整 */
.site-header h1 {
font-size: 26px;
line-height: 1.846153846;
}
/* 导航菜单上下边框及整体展示方式 */
.main-navigation ul.nav-menu,
.main-navigation div.nav-menu > ul {
border-bottom: 1px solid #ededed;
border-top: 1px solid #ededed;
display: inline-block !important;
text-align: left;
width: 100%;
}
/* 清除导航列表默认缩进和外边距 */
.main-navigation ul {
margin: 0;
text-indent: 0;
}
/* 导航链接和列表项设为行内块元素 */
.main-navigation li a,
.main-navigation li {
display: inline-block;
text-decoration: none;
}
/* IE7 下特殊处理使其支持基本显示 */
.ie7 .main-navigation li a,
.ie7 .main-navigation li {
display: inline;
}
/* 导航链接颜色、行高和大写转换 */
.main-navigation li a {
border-bottom: 0;
color: #6a6a6a;
line-height: 3.692307692;
text-transform: uppercase;
}
/* 悬停时文字变黑 */
.main-navigation li a:hover {
color: #000;
}
/* 列表项右间距和相对定位用于子菜单弹出 */
.main-navigation li {
margin: 0 40px 0 0;
position: relative;
}
/* 子菜单初始状态不可见且绝对定位 */
.main-navigation li ul {
margin: 0;
padding: 0;
position: absolute;
top: 100%;
z-index: 1;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
/* IE7 特殊处理:继承剪裁属性并默认隐藏 */
.ie7 .main-navigation li ul {
clip: inherit;
display: none;
left: 0;
overflow: visible;
}
/* 多级子菜单位置偏移 */
.main-navigation li ul ul,
.ie7 .main-navigation li ul ul {
top: 0;
left: 100%;
}
/* 父级悬停或聚焦时显示子菜单 */
.main-navigation ul li:hover > ul,
.main-navigation ul li:focus > ul,
.main-navigation .focus > ul {
border-left: 0;
clip: inherit;
overflow: inherit;
height: inherit;
width: inherit;
}
/* IE7 支持通过 hover 显示子菜单 */
.ie7 .main-navigation ul li:hover > ul,
.ie7 .main-navigation ul li:focus > ul {
display: block;
}
/* 子菜单链接基础样式 */
.main-navigation li ul li a {
background: #efefef;
border-bottom: 1px solid #ededed;
display: block;
font-size: 11px;
line-height: 2.181818182;
padding: 8px 10px;
width: 180px;
}
/* 子菜单链接悬停样式 */
.main-navigation li ul li a:hover {
background: #e3e3e3;
color: #444;
}
/* 当前选中菜单项加粗并更改颜色 */
.main-navigation .current-menu-item > a,
.main-navigation .current-menu-ancestor > a,
.main-navigation .current_page_item > a,
.main-navigation .current_page_ancestor > a {
color: #636363;
font-weight: bold;
}
/* 移动端切换按钮在此版本中隐藏 */
.main-navigation .menu-toggle {
display: none;
}
/* 文章标题字体大小 */
.entry-header .entry-title {
font-size: 22px;
}
/* 回复表单输入框宽度适配 */
#respond form input[type="text"] {
width: 46.333333333%;
}
/* 回复文本域宽度适配 */
#respond form textarea.blog-textarea {
width: 79.666666667%;
}
/* 前台首页模板清除溢出 */
.template-front-page .site-content,
.template-front-page article {
overflow: hidden;
}
/* 若有特色图像则左右分栏排布 */
.template-front-page.has-post-thumbnail article {
float: left;
width: 47.916666667%;
}
/* 页面图像靠右放置 */
.entry-page-image {
float: right;
margin-bottom: 0;
width: 47.916666667%;
}
/* IE 前台小工具修复:清除浮动并强制全宽 */
.template-front-page .widget-area {
clear: both;
}
.template-front-page .widget {
width: 100% !important;
border: none;
}
/* 左右双列小工具布局 */
.template-front-page .widget-area .widget,
.template-front-page .first.front-widgets,
.template-front-page.two-sidebars .widget-area .front-widgets {
float: left;
margin-bottom: 24px;
width: 51.875%;
}
/* 第二组小工具右浮动并清空前一个元素影响 */
.template-front-page .second.front-widgets,
.template-front-page .widget-area .widget:nth-child(odd) {
clear: right;
}
/* 右侧小工具组设置 */
.template-front-page .first.front-widgets,
.template-front-page .second.front-widgets,
.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets {
float: right;
margin: 0 0 24px;
width: 39.0625%;
}
/* 双侧边栏模式下取消浮动并自适应宽度 */
.template-front-page.two-sidebars .widget,
.template-front-page.two-sidebars .widget:nth-child(even) {
float: none;
width: auto;
}
/* 解决 IE9 以下密码输入框圆点不显示问题 */
input[type="password"] {
font-family: Helvetica, Arial, sans-serif;
}
/* RTL overrides for IE7 and IE8
-------------------------------------------------------------- */
/* RTL 模式下站点头部标题右对齐 */
.rtl .site-header h1,
.rtl .site-header h2 {
text-align: right;
}
/* RTL 模式下小工具区和作者描述向左浮动 */
.rtl .widget-area,
.rtl .author-description {
float: left;
}
/* RTL 模式下作者头像和内容区向右浮动 */
.rtl .author-avatar,
.rtl .site-content {
float: right;
}
/* RTL 模式下主导航菜单右对齐 */
.rtl .main-navigation ul.nav-menu,
.rtl .main-navigation div.nav-menu > ul {
text-align: right;
}
/* RTL 模式下多层级导航菜单左间距调整 */
.rtl .main-navigation ul li ul li,
.rtl .main-navigation ul li ul li ul li {
margin-left: 40px;
margin-right: auto;
}
/* RTL 模式下三级及以上菜单定位修正 */
.rtl .main-navigation li ul ul {
position: absolute;
bottom: 0;
right: 100%;
z-index: 1;
}
/* IE7 RTL 模式下三级及以上菜单定位修正 */
.ie7 .rtl .main-navigation li ul ul {
position: absolute;
bottom: 0;
right: 100%;
z-index: 1;
}
/* IE7 RTL 模式下导航列表层级提升避免遮挡 */
.ie7 .rtl .main-navigation ul li {
z-index: 99;
}
/* IE7 RTL 模式下二级菜单底部对齐并右对齐 */
.ie7 .rtl .main-navigation li ul {
position: absolute;
bottom: 100%;
right: 0;
z-index: 1;
}
/* IE7 RTL 模式下导航项左间距设置 */
.ie7 .rtl .main-navigation li {
margin-right: auto;
margin-left: 40px;
}
/* IE7 RTL 模式下四级及以上菜单使用相对定位 */
.ie7 .rtl .main-navigation li ul ul ul {
position: relative;
z-index: 1;
}

@ -1,74 +0,0 @@
/* 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); }
}

@ -1,305 +0,0 @@
.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

@ -1,378 +0,0 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(mem6YaGs126MiZpBA-UFUK0Zdc0.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-display: fallback;
src: url(memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UN_r8OUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWJ0bbck.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFUZ0bbck.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWZ0bbck.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFVp0bbck.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFWp0bbck.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFW50bbck.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(mem8YaGs126MiZpBA-UFVZ0b.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-display: fallback;
src: url(mem5YaGs126MiZpBA-UNirkOUuhp.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

@ -1,91 +0,0 @@
/**
* Created by liangliang on 2016/11/20.
*/
function do_reply(parentid) {
console.log(parentid);
$("#id_parent_comment_id").val(parentid)
$("#commentform").appendTo($("#div-comment-" + parentid));
$("#reply-title").hide();
$("#cancel_comment").show();
}
function cancel_reply() {
$("#reply-title").show();
$("#cancel_comment").hide();
$("#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);
});
window.onload = function () {
var replyLinks = document.querySelectorAll(".comment-reply-link");
for (var i = 0; i < replyLinks.length; i++) {
replyLinks[i].onclick = function () {
var pk = this.getAttribute("data-pk");
do_reply(pk);
};
}
};
// $(document).ready(function () {
// var form = $('#i18n-form');
// var selector = $('.i18n-select');
// selector.on('change', function () {
// form.submit();
// });
// });

@ -1,8 +0,0 @@
/*
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

File diff suppressed because one or more lines are too long

@ -1,55 +0,0 @@
/**
* Handles toggling the navigation menu for small screens and
* accessibility for submenu items.
*/
( function() {
var nav = document.getElementById( 'site-navigation' ), button, menu;
if ( ! nav ) {
return;
}
button = nav.getElementsByTagName( 'button' )[0];
menu = nav.getElementsByTagName( 'ul' )[0];
if ( ! button ) {
return;
}
// Hide button if menu is missing or empty.
if ( ! menu || ! menu.childNodes.length ) {
button.style.display = 'none';
return;
}
button.onclick = function() {
if ( -1 === menu.className.indexOf( 'nav-menu' ) ) {
menu.className = 'nav-menu';
}
if ( -1 !== button.className.indexOf( 'toggled-on' ) ) {
button.className = button.className.replace( ' toggled-on', '' );
menu.className = menu.className.replace( ' toggled-on', '' );
} else {
button.className += ' toggled-on';
menu.className += ' toggled-on';
}
};
} )();
// Better focus for hidden submenu items for accessibility.
( function( $ ) {
$( '.main-navigation' ).find( 'a' ).on( 'focus.twentytwelve blur.twentytwelve', function() {
$( this ).parents( '.menu-item, .page_item' ).toggleClass( 'focus' );
} );
if ( 'ontouchstart' in window ) {
$('body').on( 'touchstart.twentytwelve', '.menu-item-has-children > a, .page_item_has_children > a', function( e ) {
var el = $( this ).parent( 'li' );
if ( ! el.hasClass( 'focus' ) ) {
e.preventDefault();
el.toggleClass( 'focus' );
el.siblings( '.focus').removeClass( 'focus' );
}
} );
}
} )( jQuery );

@ -1,480 +0,0 @@
/* 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) {
} 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;
});

@ -1,21 +0,0 @@
$(function () {
MathJax.Hub.Config({
showProcessingMessages: false, //关闭js加载过程信息
messageStyle: "none", //不显示信息
extensions: ["tex2jax.js"], jax: ["input/TeX", "output/HTML-CSS"], displayAlign: "left", tex2jax: {
inlineMath: [["$", "$"]], //行内公式选择$
displayMath: [["$$", "$$"]], //段内公式选择$$
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'], //避开某些标签
}, "HTML-CSS": {
availableFonts: ["STIX", "TeX"], //可选字体
showMathMenu: false //关闭右击菜单显示
}
});
// 识别范围 => 文章内容、评论内容标签
const contentId = document.getElementById("content");
const commentId = document.getElementById("comments");
MathJax.Hub.Queue(["Typeset", MathJax.Hub, contentId, commentId]);
})

@ -1,293 +0,0 @@
.codehilite .hll {
background-color: #ffffcc
}
.codehilite {
background: #ffffff;
}
.codehilite .c {
color: #177500
}
/* Comment */
.codehilite .err {
color: #000000
}
/* Error */
.codehilite .k {
color: #A90D91
}
/* Keyword */
.codehilite .l {
color: #1C01CE
}
/* Literal */
.codehilite .n {
color: #000000
}
/* Name */
.codehilite .o {
color: #000000
}
/* Operator */
.codehilite .ch {
color: #177500
}
/* Comment.Hashbang */
.codehilite .cm {
color: #177500
}
/* Comment.Multiline */
.codehilite .cp {
color: #633820
}
/* Comment.Preproc */
.codehilite .cpf {
color: #177500
}
/* Comment.PreprocFile */
.codehilite .c1 {
color: #177500
}
/* Comment.Single */
.codehilite .cs {
color: #177500
}
/* Comment.Special */
.codehilite .kc {
color: #A90D91
}
/* Keyword.Constant */
.codehilite .kd {
color: #A90D91
}
/* Keyword.Declaration */
.codehilite .kn {
color: #A90D91
}
/* Keyword.Namespace */
.codehilite .kp {
color: #A90D91
}
/* Keyword.Pseudo */
.codehilite .kr {
color: #A90D91
}
/* Keyword.Reserved */
.codehilite .kt {
color: #A90D91
}
/* Keyword.Type */
.codehilite .ld {
color: #1C01CE
}
/* Literal.Date */
.codehilite .m {
color: #1C01CE
}
/* Literal.Number */
.codehilite .s {
color: #C41A16
}
/* Literal.String */
.codehilite .na {
color: #836C28
}
/* Name.Attribute */
.codehilite .nb {
color: #A90D91
}
/* Name.Builtin */
.codehilite .nc {
color: #3F6E75
}
/* Name.Class */
.codehilite .no {
color: #000000
}
/* Name.Constant */
.codehilite .nd {
color: #000000
}
/* Name.Decorator */
.codehilite .ni {
color: #000000
}
/* Name.Entity */
.codehilite .ne {
color: #000000
}
/* Name.Exception */
.codehilite .nf {
color: #000000
}
/* Name.Function */
.codehilite .nl {
color: #000000
}
/* Name.Label */
.codehilite .nn {
color: #000000
}
/* Name.Namespace */
.codehilite .nx {
color: #000000
}
/* Name.Other */
.codehilite .py {
color: #000000
}
/* Name.Property */
.codehilite .nt {
color: #000000
}
/* Name.Tag */
.codehilite .nv {
color: #000000
}
/* Name.Variable */
.codehilite .ow {
color: #000000
}
/* Operator.Word */
.codehilite .mb {
color: #1C01CE
}
/* Literal.Number.Bin */
.codehilite .mf {
color: #1C01CE
}
/* Literal.Number.Float */
.codehilite .mh {
color: #1C01CE
}
/* Literal.Number.Hex */
.codehilite .mi {
color: #1C01CE
}
/* Literal.Number.Integer */
.codehilite .mo {
color: #1C01CE
}
/* Literal.Number.Oct */
.codehilite .sb {
color: #C41A16
}
/* Literal.String.Backtick */
.codehilite .sc {
color: #2300CE
}
/* Literal.String.Char */
.codehilite .sd {
color: #C41A16
}
/* Literal.String.Doc */
.codehilite .s2 {
color: #C41A16
}
/* Literal.String.Double */
.codehilite .se {
color: #C41A16
}
/* Literal.String.Escape */
.codehilite .sh {
color: #C41A16
}
/* Literal.String.Heredoc */
.codehilite .si {
color: #C41A16
}
/* Literal.String.Interpol */
.codehilite .sx {
color: #C41A16
}
/* Literal.String.Other */
.codehilite .sr {
color: #C41A16
}
/* Literal.String.Regex */
.codehilite .s1 {
color: #C41A16
}
/* Literal.String.Single */
.codehilite .ss {
color: #C41A16
}
/* Literal.String.Symbol */
.codehilite .bp {
color: #5B269A
}
/* Name.Builtin.Pseudo */
.codehilite .vc {
color: #000000
}
/* Name.Variable.Class */
.codehilite .vg {
color: #000000
}
/* Name.Variable.Global */
.codehilite .vi {
color: #000000
}
/* Name.Variable.Instance */
.codehilite .il {
color: #1C01CE
}
/* Literal.Number.Integer.Long */

@ -0,0 +1,2 @@
# 模板标签包:用于注册博客的自定义模板标签与过滤器。
# 仅增加中文注释,不影响 Django 模板标签的加载行为。

@ -1,3 +1,7 @@
# 模块说明:
# 本模块定义博客的模板标签与过滤器,包括时间/日期格式化、Markdown 渲染与目录、
# 文章摘要、面包屑、标签列表、侧边栏数据、分页链接、Gravatar 头像和通用查询过滤。
# 仅增加中文注释,不更改任何业务逻辑或删除/修改原有代码。
import hashlib
import logging
import random
@ -26,11 +30,13 @@ register = template.Library()
@register.simple_tag(takes_context=True)
# 返回插件系统生成的页面 <head> 元信息(字符串 HTML
def head_meta(context):
return mark_safe(hooks.apply_filters('head_meta', '', context))
@register.simple_tag
# 将时间对象使用站点配置的 TIME_FORMAT 进行格式化
def timeformat(data):
try:
return data.strftime(settings.TIME_FORMAT)
@ -40,6 +46,7 @@ def timeformat(data):
@register.simple_tag
# 将时间对象使用站点配置的 DATE_TIME_FORMAT 进行格式化
def datetimeformat(data):
try:
return data.strftime(settings.DATE_TIME_FORMAT)
@ -50,11 +57,13 @@ def datetimeformat(data):
@register.filter()
@stringfilter
# 将 Markdown 文本转换为 HTML并标记为安全
def custom_markdown(content):
return mark_safe(CommonMarkdown.get_markdown(content))
@register.simple_tag
# 生成并返回 Markdown 的目录TOCHTML
def get_markdown_toc(content):
from djangoblog.utils import CommonMarkdown
body, toc = CommonMarkdown.get_markdown_with_toc(content)
@ -63,6 +72,7 @@ def get_markdown_toc(content):
@register.filter()
@stringfilter
# 将评论中的 Markdown 转换为 HTML并进行安全清洗
def comment_markdown(content):
content = CommonMarkdown.get_markdown(content)
return mark_safe(sanitize_html(content))
@ -84,6 +94,7 @@ def truncatechars_content(content):
@register.filter(is_safe=True)
@stringfilter
# 去除所有 HTML 标签并截断为 150 个字符的纯文本
def truncate(content):
from django.utils.html import strip_tags
@ -91,6 +102,7 @@ def truncate(content):
@register.inclusion_tag('blog/tags/breadcrumb.html')
# 构建文章的面包屑导航数据,用于模板渲染
def load_breadcrumb(article):
"""
获得文章面包屑
@ -112,6 +124,7 @@ def load_breadcrumb(article):
@register.inclusion_tag('blog/tags/article_tag_list.html')
# 返回文章标签列表,包含标签链接、文章计数与随机样式类型
def load_articletags(article):
"""
文章标签
@ -132,6 +145,7 @@ def load_articletags(article):
@register.inclusion_tag('blog/tags/sidebar.html')
# 加载并缓存侧边栏数据:最近文章、分类、阅读排行、日期归档、最新评论、友链与标签云
def load_sidebar(user, linktype):
"""
加载侧边栏
@ -192,6 +206,7 @@ def load_sidebar(user, linktype):
@register.inclusion_tag('blog/tags/article_meta_info.html')
# 返回文章的基本元信息上下文,用于模板显示作者、分类、标签等
def load_article_metas(article, user):
"""
获得文章meta信息
@ -205,6 +220,7 @@ 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 = ''
@ -274,6 +290,7 @@ def load_pagination_info(page_obj, page_type, tag_name):
@register.inclusion_tag('blog/tags/article_info.html')
# 渲染文章详情或列表摘要,依据 isindex 控制摘要显示
def load_article_detail(article, isindex, user):
"""
加载文章详情
@ -295,6 +312,7 @@ def load_article_detail(article, isindex, user):
# return only the URL of the gravatar
# TEMPLATE USE: {{ email|gravatar_url:150 }}
@register.filter
# 根据邮箱生成 Gravatar 头像地址,优先使用 OAuth头像带默认占位与缓存
def gravatar_url(email, size=40):
"""获得gravatar头像"""
cachekey = 'gravatat/' + email
@ -319,6 +337,7 @@ def gravatar_url(email, size=40):
@register.filter
# 返回包含 Gravatar 头像的 <img> 标签 HTML
def gravatar(email, size=40):
"""获得gravatar头像"""
url = gravatar_url(email, size)
@ -328,6 +347,7 @@ def gravatar(email, size=40):
@register.simple_tag
# 模板标签:对 QuerySet 执行 filter(**kwargs) 并返回结果
def query(qs, **kwargs):
""" template tag which allows queryset filtering. Usage:
{% query books author=author as mybooks %}
@ -339,6 +359,7 @@ def query(qs, **kwargs):
@register.filter
# 拼接两个参数为字符串并返回
def addstr(arg1, arg2):
"""concatenate arg1 & arg2"""
return str(arg1) + str(arg2)

@ -1,3 +1,6 @@
# 模块说明:博客应用的集成测试与命令执行测试。
# 覆盖内容包括:文章/标签/分类页面访问、分页、搜索、文件上传、错误页、
# 以及自定义管理命令(索引、推送、造数、清缓存、同步头像、搜索词)。
import os
from django.conf import settings

@ -1,3 +1,9 @@
# -*- coding: utf-8 -*-
"""
本模块定义了博客应用的视图函数和类视图用于处理HTTP请求并返回响应
它包含了文章列表文章详情分类标签归档友链等页面的视图以及文件上传和错误处理等功能
"""
import logging
import os
import uuid
@ -25,6 +31,17 @@ logger = logging.getLogger(__name__)
class ArticleListView(ListView):
"""
文章列表视图的基类提供了分页缓存等通用功能
Attributes:
template_name (str): 渲染模板的路径
context_object_name (str): 上下文对象的名称
page_type (str): 页面类型如分类标签等
paginate_by (int): 每页显示的文章数量
page_kwarg (str): URL中表示页码的参数名
link_type (LinkShowType): 友情链接的显示类型
"""
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
@ -38,10 +55,12 @@ class ArticleListView(ListView):
link_type = LinkShowType.L
def get_view_cache_key(self):
"""获取视图缓存的键"""
return self.request.get['pages']
@property
def page_number(self):
"""获取当前页码"""
page_kwarg = self.page_kwarg
page = self.kwargs.get(
page_kwarg) or self.request.GET.get(page_kwarg) or 1
@ -49,22 +68,26 @@ class ArticleListView(ListView):
def get_queryset_cache_key(self):
"""
子类重写.获得queryset的缓存key
获取查询集的缓存键子类必须实现
"""
raise NotImplementedError()
def get_queryset_data(self):
"""
子类重写.获取queryset的数据
获取查询集的数据子类必须实现
"""
raise NotImplementedError()
def get_queryset_from_cache(self, cache_key):
'''
缓存页面数据
:param cache_key: 缓存key
:return:
'''
"""
从缓存中获取查询集数据如果缓存不存在则重新获取并设置缓存
Args:
cache_key (str): 缓存键
Returns:
QuerySet: 文章查询集
"""
value = cache.get(cache_key)
if value:
logger.info('get view cache.key:{key}'.format(key=cache_key))
@ -76,45 +99,54 @@ class ArticleListView(ListView):
return article_list
def get_queryset(self):
'''
重写默认从缓存获取数据
:return:
'''
"""
重写get_queryset方法从缓存中获取数据
"""
key = self.get_queryset_cache_key()
value = self.get_queryset_from_cache(key)
return value
def get_context_data(self, **kwargs):
"""添加额外的上下文数据"""
kwargs['linktype'] = self.link_type
return super(ArticleListView, self).get_context_data(**kwargs)
class IndexView(ArticleListView):
'''
首页
'''
"""
首页视图继承自ArticleListView
"""
# 友情链接类型
link_type = LinkShowType.I
def get_queryset_data(self):
"""获取首页文章列表数据"""
article_list = Article.objects.filter(type='a', status='p')
return article_list
def get_queryset_cache_key(self):
"""获取首页文章列表的缓存键"""
cache_key = 'index_{page}'.format(page=self.page_number)
return cache_key
class ArticleDetailView(DetailView):
'''
文章详情页面
'''
"""
文章详情页视图
Attributes:
model (Article): 关联的模型
template_name (str): 渲染模板的路径
pk_url_kwarg (str): URL中表示主键的参数名
context_object_name (str): 上下文对象的名称
"""
template_name = 'blog/article_detail.html'
model = Article
pk_url_kwarg = 'article_id'
context_object_name = "article"
def get_context_data(self, **kwargs):
"""添加评论表单、评论列表、前后篇文章等上下文数据"""
comment_form = CommentForm()
article_comments = self.object.comment_list()
@ -162,12 +194,13 @@ class ArticleDetailView(DetailView):
class CategoryDetailView(ArticleListView):
'''
分类目录列表
'''
"""
分类详情页视图展示某个分类下的文章列表
"""
page_type = "分类目录归档"
def get_queryset_data(self):
"""获取分类文章列表数据"""
slug = self.kwargs['category_name']
category = get_object_or_404(Category, slug=slug)
@ -180,6 +213,7 @@ class CategoryDetailView(ArticleListView):
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
@ -189,6 +223,7 @@ class CategoryDetailView(ArticleListView):
return cache_key
def get_context_data(self, **kwargs):
"""添加页面类型、标签名等上下文数据"""
categoryname = self.categoryname
try:
@ -201,12 +236,13 @@ class CategoryDetailView(ArticleListView):
class AuthorDetailView(ArticleListView):
'''
作者详情页
'''
"""
作者详情页视图展示某个作者的文章列表
"""
page_type = '作者文章归档'
def get_queryset_cache_key(self):
"""获取作者文章列表的缓存键"""
from uuslug import slugify
author_name = slugify(self.kwargs['author_name'])
cache_key = 'author_{author_name}_{page}'.format(
@ -214,12 +250,14 @@ class AuthorDetailView(ArticleListView):
return cache_key
def get_queryset_data(self):
"""获取作者文章列表数据"""
author_name = self.kwargs['author_name']
article_list = Article.objects.filter(
author__username=author_name, type='a', status='p')
return article_list
def get_context_data(self, **kwargs):
"""添加页面类型、作者名等上下文数据"""
author_name = self.kwargs['author_name']
kwargs['page_type'] = AuthorDetailView.page_type
kwargs['tag_name'] = author_name
@ -227,12 +265,13 @@ class AuthorDetailView(ArticleListView):
class TagDetailView(ArticleListView):
'''
标签列表页面
'''
"""
标签详情页视图展示某个标签下的文章列表
"""
page_type = '分类标签归档'
def get_queryset_data(self):
"""获取标签文章列表数据"""
slug = self.kwargs['tag_name']
tag = get_object_or_404(Tag, slug=slug)
tag_name = tag.name
@ -242,6 +281,7 @@ class TagDetailView(ArticleListView):
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
@ -251,6 +291,7 @@ class TagDetailView(ArticleListView):
return cache_key
def get_context_data(self, **kwargs):
"""添加页面类型、标签名等上下文数据"""
# tag_name = self.kwargs['tag_name']
tag_name = self.name
kwargs['page_type'] = TagDetailView.page_type
@ -259,32 +300,38 @@ class TagDetailView(ArticleListView):
class ArchivesView(ArticleListView):
'''
文章归档页
'''
"""
文章归档页视图
"""
page_type = '文章归档'
paginate_by = None
page_kwarg = None
template_name = 'blog/article_archives.html'
def get_queryset_data(self):
"""获取所有已发布的文章"""
return Article.objects.filter(status='p').all()
def get_queryset_cache_key(self):
"""获取归档页的缓存键"""
cache_key = 'archives'
return cache_key
class LinkListView(ListView):
"""友情链接列表页视图"""
model = Links
template_name = 'blog/links_list.html'
def get_queryset(self):
"""获取所有启用的友情链接"""
return Links.objects.filter(is_enable=True)
class EsSearchView(SearchView):
"""Elasticsearch搜索视图"""
def get_context(self):
"""获取搜索结果页的上下文数据"""
paginator, page = self.build_page()
context = {
"query": self.query,
@ -303,9 +350,16 @@ class EsSearchView(SearchView):
@csrf_exempt
def fileupload(request):
"""
该方法需自己写调用端来上传图片该方法仅提供图床功能
:param request:
:return:
文件上传视图用于图床功能
接收POST请求验证签名后保存上传的文件并返回文件的URL
需要调用端自行实现上传逻辑
Args:
request (HttpRequest): HTTP请求对象
Returns:
HttpResponse: 包含文件URL的HTTP响应或在出错时返回HttpResponseForbidden
"""
if request.method == 'POST':
sign = request.GET.get('sign', None)

@ -1,3 +1,5 @@
# 模块说明:评论模型的后台管理配置,提供启用/禁用评论的批量操作,
# 并在列表中展示到用户与文章的跳转链接。
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
@ -5,10 +7,12 @@ from django.utils.translation import gettext_lazy as _
def disable_commentstatus(modeladmin, request, queryset):
# 批量关闭选中评论的显示状态
queryset.update(is_enable=False)
def enable_commentstatus(modeladmin, request, queryset):
# 批量开启选中评论的显示状态
queryset.update(is_enable=True)
@ -31,6 +35,7 @@ class CommentAdmin(admin.ModelAdmin):
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
# 在后台列表中展示评论作者的链接,跳转到对应用户修改页
info = (obj.author._meta.app_label, obj.author._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
return format_html(
@ -38,6 +43,7 @@ class CommentAdmin(admin.ModelAdmin):
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
def link_to_article(self, obj):
# 在后台列表中展示评论所属文章的链接,跳转到对应文章修改页
info = (obj.article._meta.app_label, obj.article._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
return format_html(

@ -1,5 +1,7 @@
# 模块说明Django 应用配置,用于注册 comments 应用。
from django.apps import AppConfig
class CommentsConfig(AppConfig):
"""comments 应用的注册配置类"""
name = 'comments'

@ -1,3 +1,4 @@
# 模块说明:评论表单定义,支持提交正文与可选的父评论 ID。
from django import forms
from django.forms import ModelForm
@ -5,9 +6,11 @@ from .models import Comment
class CommentForm(ModelForm):
"""评论提交表单:包含正文与隐藏的父评论 ID"""
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
class Meta:
"""绑定模型与字段,仅开放 body 字段"""
model = Comment
fields = ['body']

@ -1,3 +1,4 @@
# 模块说明:评论模型定义,包含评论正文、时间、作者、所属文章、父评论与是否启用等字段。
from django.conf import settings
from django.db import models
from django.utils.timezone import now
@ -9,6 +10,7 @@ from blog.models import Article
# Create your models here.
class Comment(models.Model):
"""评论模型:存储站点中的用户评论数据"""
body = models.TextField('正文', max_length=300)
creation_time = models.DateTimeField(_('creation time'), default=now)
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
@ -30,10 +32,12 @@ class Comment(models.Model):
default=False, blank=False, null=False)
class Meta:
"""模型元数据:按 ID 倒序设置可读名称latest_by 用于最新记录获取"""
ordering = ['-id']
verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id'
def __str__(self):
"""返回评论的正文,便于后台显示"""
return self.body

@ -1,3 +1,4 @@
# 模板标签:评论树解析与评论项渲染,供模板调用。
from django import template
register = template.Library()
@ -8,6 +9,7 @@ def parse_commenttree(commentlist, comment):
"""获得当前评论子评论的列表
用法: {% parse_commenttree article_comments comment as childcomments %}
"""
# 深度优先遍历,收集所有子评论
datas = []
def parse(c):
@ -23,6 +25,7 @@ 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
return {
'comment_item': comment,

@ -1,3 +1,7 @@
"""模块说明:评论功能测试用例集合
- 覆盖评论验证发表评论流程邮件通知与模板标签
- 仅添加中文说明不影响测试逻辑
"""
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
@ -11,7 +15,9 @@ from djangoblog.utils import get_max_articleid_commentid
# Create your tests here.
class CommentsTest(TransactionTestCase):
"""评论相关集成测试:登录、创建文章、发表评论与回复。"""
def setUp(self):
"""准备站点配置(评论需审核)、测试客户端与用户。"""
self.client = Client()
self.factory = RequestFactory()
from blog.models import BlogSettings
@ -25,12 +31,14 @@ class CommentsTest(TransactionTestCase):
password="liangliangyy1")
def update_article_comment_status(self, article):
"""批量将文章的所有评论标记为启用状态。"""
comments = article.comment_set.all()
for comment in comments:
comment.is_enable = True
comment.save()
def test_validate_comment(self):
"""验证发表评论与回复、树解析、渲染标签与邮件发送。"""
self.client.login(username='liangliangyy1', password='liangliangyy1')
category = Category()

@ -1,3 +1,4 @@
# URL 路由:评论相关的接口,包含文章评论提交入口。
from django.urls import path
from . import views

@ -1,3 +1,4 @@
# 模块说明:评论通知邮件工具,向评论作者与父评论作者发送提醒邮件。
import logging
from django.utils.translation import gettext_lazy as _
@ -9,6 +10,10 @@ logger = logging.getLogger(__name__)
def send_comment_email(comment):
"""发送评论通知邮件:
- 向评论作者发送感谢与评论查看链接
- 若存在父评论则向父评论作者发送被回复的提醒
"""
site = get_current_site().domain
subject = _('Thanks for your comment')
article_url = f"https://{site}{comment.article.get_absolute_url()}"

@ -1,4 +1,4 @@
# Create your views here.
# 视图模块处理评论提交流程GET 重定向到文章评论区POST 验证与保存评论)。
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
@ -13,20 +13,24 @@ from .models import Comment
class CommentPostView(FormView):
"""评论提交视图:负责表单展示、验证与保存评论"""
form_class = CommentForm
template_name = 'blog/article_detail.html'
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# CSRF 保护装饰器,保障 POST 安全
return super(CommentPostView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
# 直接跳转至文章评论区(锚点)
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
url = article.get_absolute_url()
return HttpResponseRedirect(url + "#comments")
def form_invalid(self, form):
# 表单校验失败时,返回文章详情页并携带错误信息
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
@ -37,6 +41,7 @@ class CommentPostView(FormView):
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
# 保存评论主体与作者、父评论等信息;若站点不需要审核则直接启用
user = self.request.user
author = BlogUser.objects.get(pk=user.pk)
article_id = self.kwargs['article_id']

Loading…
Cancel
Save