fz_branch #1

Open
pa2g3nmk9 wants to merge 77 commits from fz_branch into master

@ -0,0 +1,26 @@
# 项目开发报告
## 项目概述
本项目是一个软件工程方法论实践项目。
## 登录功能开发进度
### 已完成
- [x] 项目环境搭建
- [x] Git分支管理配置
- [x] 基础文档结构创建
### 进行中
- [ ] 用户登录功能实现
- [ ] 密码加密模块
- [ ] 会话管理功能
### 技术栈
- Python 3.x
- Git 版本控制
- Markdown 文档
## 下一步计划
1. 实现登录功能代码
2. 编写单元测试
3. 完成集成测试

Binary file not shown.

@ -1,10 +1,12 @@
[run]
source = .
include = *.py
[run] # 运行配置部分
source = . # 指定要测量覆盖率的源代码根目录为当前目录
include = *.py # 包含所有.py文件进行覆盖率测量
# 排除的文件和目录列表
omit =
*migrations*
*tests*
*.html
*whoosh_cn_backend*
*settings.py*
*venv*
*migrations* # 排除Django迁移文件
*tests* # 排除测试文件
*.html # 排除HTML模板文件
*whoosh_cn_backend* # 排除中文搜索后端相关文件
*settings.py* # 排除配置文件
*venv* # 排除虚拟环境目录

@ -1,11 +1,29 @@
# 数据文件目录 - 通常包含动态生成的数据,不应纳入版本控制
bin/data/
# virtualenv
# Python 虚拟环境目录 - 包含项目依赖,应该通过 requirements.txt 管理
venv/
# Django 收集的静态文件目录 - 由 collectstatic 命令生成
collectedstatic/
# Whoosh 搜索引擎索引目录 - 包含搜索索引数据,会频繁变化
djangoblog/whoosh_index/
# 用户上传文件目录 - 包含用户上传的媒体文件
uploads/
# 生产环境配置文件 - 包含敏感信息如数据库密码、API密钥等
settings_production.py
# 所有 Markdown 文档文件 - 可能是临时文件或本地笔记
*.md
# 文档目录 - 可能包含生成的文档或本地文档
docs/
# 日志文件目录 - 包含应用程序运行日志
logs/
# 静态文件目录 - 可能包含前端构建产物或开发时的静态文件
static/

@ -1,6 +1,18 @@
# 将 blog/static/ 目录下的所有文件标记为"vendored"(第三方代码)
# 这样 GitHub 的语言统计会忽略这些文件
blog/static/* linguist-vendored
# 将所有 .js 文件标记为"vendored",在语言统计中忽略
*.js linguist-vendored
# 将所有 .css 文件标记为"vendored",在语言统计中忽略
*.css linguist-vendored
# 设置所有文件使用自动换行符检测
* text=auto
# 设置 .sh 文件为文本文件,并强制使用 LF 换行符
*.sh text eol=lf
# 设置 .conf 文件为文本文件,并强制使用 LF 换行符
*.conf text eol=lf

@ -1,80 +1,132 @@
# ================================
# Python 特定忽略规则
# ================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
__pycache__/ # Python 字节码缓存目录
*.py[cod] # 编译的 Python 文件:.pyc, .pyo, .pyd
*$py.class # Jython 编译文件
# C extensions
*.so
*.so # Python C 扩展模块
# ================================
# 包管理和分发文件
# ================================
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
.Python # Python 环境相关
env/ # 虚拟环境(通用名称)
build/ # 构建输出目录
develop-eggs/ # 开发阶段的 egg 文件
dist/ # 源码包分发目录
downloads/ # 下载的依赖包
eggs/ # Egg 安装目录
.eggs/ # 隐藏的 egg 目录
lib/ # 依赖库
lib64/ # 64位依赖库
parts/ # 部件目录
sdist/ # 源码分发文件
var/ # 变量数据
*.egg-info/ # Egg 包信息
.installed.cfg # 安装配置
*.egg # Egg 包文件
# ================================
# 打包工具相关
# ================================
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
*.manifest # Windows 清单文件
*.spec # PyInstaller 规范文件
# ================================
# 安装和日志文件
# ================================
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-log.txt # pip 安装日志
pip-delete-this-directory.txt # pip 清理文件
# ================================
# 测试和覆盖率报告
# ================================
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
htmlcov/ # HTML 覆盖率报告
.tox/ # tox 测试环境
.coverage # coverage.py 数据文件
.coverage.* # coverage.py 临时文件
.cache # 缓存目录
nosetests.xml # nose 测试报告
coverage.xml # XML 覆盖率报告
*,cover # coverage.py 覆盖文件
# ================================
# 国际化和文档
# ================================
# Translations
*.pot
*.pot # Gettext 模板文件
# ================================
# Django 特定文件
# ================================
# Django stuff:
*.log
logs/
*.log # 日志文件
logs/ # 日志目录
# Sphinx documentation
docs/_build/
docs/_build/ # Sphinx 文档构建输出
# ================================
# 构建工具
# ================================
# PyBuilder
target/
target/ # PyBuilder 目标目录
# ================================
# IDE 和编辑器
# ================================
# PyCharm
# http://www.jetbrains.com/pycharm/webhelp/project.html
.idea
.iml
static/
.idea # PyCharm 项目配置
.iml # IntelliJ 模块文件
# ================================
# 项目特定忽略规则
# ================================
static/ # 开发静态文件(生产环境使用 collectedstatic
# virtualenv
venv/
collectedstatic/
djangoblog/whoosh_index/
google93fd32dbd906620a.html
baidu_verify_FlHL7cUyC9.html
BingSiteAuth.xml
cb9339dbe2ff86a5aa169d28dba5f615.txt
werobot_session.*
django.jpg
uploads/
settings_production.py
werobot_session.db
bin/datas/
venv/ # 虚拟环境目录
collectedstatic/ # Django collectstatic 收集的静态文件
djangoblog/whoosh_index/ # Whoosh 搜索引擎索引目录
# 各种验证文件(搜索引擎、服务商验证)
google93fd32dbd906620a.html # Google 网站验证
baidu_verify_FlHL7cUyC9.html # 百度验证
BingSiteAuth.xml # Bing 网站认证
cb9339dbe2ff86a5aa169d28dba5f615.txt # 未知验证文件
# 微信机器人会话
werobot_session.* # WeRoBot 会话文件
# 媒体文件
django.jpg # 可能的上传图片
uploads/ # 用户上传文件目录
# 配置文件
settings_production.py # 生产环境设置(包含敏感信息)
# 数据库文件
werobot_session.db # WeRoBot 会话数据库
# 数据文件
bin/datas/ # 数据文件目录

@ -1,15 +1,42 @@
# 使用 Python 3.11 作为基础镜像
FROM python:3.11
# 设置环境变量,确保 Python 输出直接显示在容器日志中(不缓冲)
ENV PYTHONUNBUFFERED 1
# 设置工作目录为 /code/djangoblog/
WORKDIR /code/djangoblog/
RUN apt-get update && \
apt-get install default-libmysqlclient-dev gettext -y && \
# 安装系统依赖包
RUN apt-get update && \\
apt-get install default-libmysqlclient-dev gettext -y && \\
rm -rf /var/lib/apt/lists/*
# 解释:
# - apt-get update: 更新包列表
# - default-libmysqlclient-dev: MySQL 客户端开发库(用于 mysqlclient Python 包)
# - gettext: 国际化工具(用于 Django 的 makemessages 和 compilemessages
# - rm -rf /var/lib/apt/lists/*: 清理 apt 缓存,减小镜像大小
# 添加 requirements.txt 文件到镜像中
ADD requirements.txt requirements.txt
RUN pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \
pip install --no-cache-dir gunicorn[gevent] && \
# 安装 Python 依赖
RUN pip install --upgrade pip && \\
pip install --no-cache-dir -r requirements.txt && \\
pip install --no-cache-dir gunicorn[gevent] && \\
pip cache purge
# 解释:
# - pip install --upgrade pip: 升级 pip 到最新版本
# - pip install --no-cache-dir: 安装时不使用缓存,减小镜像大小
# - -r requirements.txt: 安装项目依赖
# - gunicorn[gevent]: 安装 Gunicorn 服务器和 gevent 异步 worker
# - pip cache purge: 清理 pip 缓存,减小镜像大小
# 添加当前目录所有文件到镜像的工作目录
ADD . .
# 给入口点脚本添加执行权限
RUN chmod +x /code/djangoblog/deploy/entrypoint.sh
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]
# 设置容器启动时执行的入口点脚本
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]

@ -1,7 +1,8 @@
The MIT License (MIT)
The MIT License (MIT) // MIT 许可证名称
Copyright (c) 2025 车亮亮
Copyright (c) 2025 车亮亮 // 版权声明:版权所有 2025 车亮亮
// 许可条款:授予任何获得本软件及相关文档文件的人免费许可
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
@ -9,9 +10,11 @@ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
// 条件:必须在所有副本或实质性部分中包含上述版权声明和本许可声明
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
// 免责声明:软件按"原样"提供,不提供任何明示或暗示的担保
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR

@ -1,5 +1,6 @@
# DjangoBlog
<!-- 项目徽章区 - 显示CI/CD状态、代码质量、测试覆盖率和许可证信息 -->
<p align="center">
<a href="https://github.com/liangliangyy/DjangoBlog/actions/workflows/django.yml"><img src="https://github.com/liangliangyy/DjangoBlog/actions/workflows/django.yml/badge.svg" alt="Django CI"></a>
<a href="https://github.com/liangliangyy/DjangoBlog/actions/workflows/codeql-analysis.yml"><img src="https://github.com/liangliangyy/DjangoBlog/actions/workflows/codeql-analysis.yml/badge.svg" alt="CodeQL"></a>
@ -7,14 +8,17 @@
<a href="https://github.com/liangliangyy/DjangoBlog/blob/master/LICENSE"><img src="https://img.shields.io/github/license/liangliangyy/djangoblog.svg" alt="license"></a>
</p>
<!-- 项目标题和描述 -->
<p align="center">
<b>一款功能强大、设计优雅的现代化博客系统</b>
<br>
<!-- 多语言支持链接 -->
<a href="/docs/README-en.md">English</a><b>简体中文</b>
</p>
---
<!-- 项目详细介绍 -->
DjangoBlog 是一款基于 Python 3.10 和 Django 4.0 构建的高性能博客平台。它不仅提供了传统博客的所有核心功能还通过一个灵活的插件系统让您可以轻松扩展和定制您的网站。无论您是个人博主、技术爱好者还是内容创作者DjangoBlog 都旨在为您提供一个稳定、高效且易于维护的写作和发布环境。
## ✨ 特性亮点
@ -43,11 +47,11 @@ DjangoBlog 是一款基于 Python 3.10 和 Django 4.0 构建的高性能博客
## 🚀 快速开始
### 1. 环境准备
<!-- 系统环境要求 -->
确保您的系统中已安装 Python 3.10+ 和 MySQL/MariaDB。
### 2. 克隆与安装
<!-- 项目克隆和依赖安装 -->
```bash
# 克隆项目到本地
git clone https://github.com/liangliangyy/DjangoBlog.git

@ -1,47 +1,66 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from django.contrib import admin # 导入Django管理后台模块
from django.urls import reverse # 导入reverse函数用于生成URL
from django.utils.html import format_html # 导入HTML格式化函数用于生成HTML标签
from django.utils.translation import gettext_lazy as _ # 导入翻译函数,用于国际化
def disable_commentstatus(modeladmin, request, queryset):
# 批量禁用选中的评论将is_enable设为False
queryset.update(is_enable=False)
def enable_commentstatus(modeladmin, request, queryset):
# 批量启用选中的评论将is_enable设为True
queryset.update(is_enable=True)
# 为批量操作设置显示名称(支持国际化)
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
class CommentAdmin(admin.ModelAdmin):
# 管理后台列表页每页显示20条记录
list_per_page = 20
# 列表页显示的字段
list_display = (
'id',
'body',
'link_to_userinfo',
'link_to_article',
'is_enable',
'creation_time')
'id', # 评论ID
'body', # 评论内容
'link_to_userinfo', # 评论作者(带链接)
'link_to_article', # 关联文章(带链接)
'is_enable', # 是否启用
'creation_time' # 创建时间
)
# 列表页中可点击跳转详情页的字段
list_display_links = ('id', 'body', 'is_enable')
# 右侧筛选器,按是否启用筛选
list_filter = ('is_enable',)
# 编辑页排除的字段(不允许手动编辑)
exclude = ('creation_time', 'last_modify_time')
# 注册批量操作函数
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
# 生成评论作者的管理后台编辑链接
# 获取用户模型的app标签和模型名称
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 生成用户编辑页的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 返回带链接的HTML显示昵称或邮箱
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
def link_to_article(self, obj):
# 生成关联文章的管理后台编辑链接
# 获取文章模型的app标签和模型名称
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 生成文章编辑页的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
# 返回带链接的HTML显示文章标题
return format_html(
u'<a href="%s">%s</a>' % (link, obj.article.title))
# 设置自定义字段在列表页的显示名称(支持国际化)
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
link_to_article.short_description = _('Article')

@ -1,5 +1,4 @@
from django.apps import AppConfig
from django.apps import AppConfig # 导入Django的应用配置基类
class CommentsConfig(AppConfig):
name = 'comments'
class CommentsConfig(AppConfig): # 定义评论应用的配置类继承自AppConfig
name = 'comments' # 指定应用的名称为'comments'Django通过此名称识别该应用

@ -1,13 +1,15 @@
from django import forms
from django.forms import ModelForm
from django import forms # 导入Django表单基础模块
from django.forms import ModelForm # 导入模型表单类,用于基于模型创建表单
from .models import Comment
from .models import Comment # 从当前应用导入Comment模型
class CommentForm(ModelForm):
class CommentForm(ModelForm): # 定义评论表单类继承自ModelForm
# 定义父评论ID字段用于处理评论回复功能
# 使用HiddenInput小部件前端隐藏非必填顶级评论不需要父评论ID
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
class Meta:
model = Comment
fields = ['body']
class Meta: # 元数据配置
model = Comment # 指定表单关联的模型为Comment
fields = ['body'] # 表单包含的字段仅包含评论内容字段body

@ -1,38 +1,37 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
from django.conf import settings # 导入Django项目配置用于获取用户模型等设置
from django.db import migrations, models # 导入迁移和模型模块,用于定义数据库迁移操作
import django.db.models.deletion # 导入外键删除行为处理模块,定义外键删除策略
import django.utils.timezone # 导入时区工具,处理时间字段默认值
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration): # 定义迁移类,包含数据库迁移操作
class Migration(migrations.Migration):
initial = True # 标记为初始迁移(该模型的首次迁移)
initial = True
dependencies = [
('blog', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移
('blog', '0001_initial'), # 依赖blog应用的0001_initial迁移确保Article模型存在
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移(支持自定义用户模型)
]
operations = [
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('body', models.TextField(max_length=300, verbose_name='正文')),
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')),
operations = [ # 迁移操作列表:当前迁移需执行的数据库操作
migrations.CreateModel( # 创建Comment模型对应数据库表
name='Comment', # 模型名称为Comment评论模型
fields=[ # 模型字段定义
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 自增主键,自动创建,作为表的唯一标识
('body', models.TextField(max_length=300, verbose_name='正文')), # 评论正文字段文本类型最大300字符
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 创建时间字段,默认值为当前时间
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # 最后修改时间字段,默认值为当前时间
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # 评论显示开关默认显示True
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), # 外键关联Article级联删除文章删则评论删
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), # 外键关联用户模型,级联删除(用户删则评论删)
('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), # 自关联外键(支持评论回复),允许为空
],
options={
'verbose_name': '评论',
'verbose_name_plural': '评论',
'ordering': ['-id'],
'get_latest_by': 'id',
options={ # 模型元数据配置
'verbose_name': '评论', # 模型单数显示名称
'verbose_name_plural': '评论', # 模型复数显示名称
'ordering': ['-id'], # 默认排序按id降序最新评论在前
'get_latest_by': 'id', # 获取最新记录时依据id字段
},
),
]
]

@ -1,18 +1,17 @@
# Generated by Django 4.1.7 on 2023-04-24 13:48
from django.db import migrations, models # 导入Django迁移和模型模块用于数据库结构变更
from django.db import migrations, models
class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作
class Migration(migrations.Migration):
dependencies = [
dependencies = [ # 迁移依赖需先执行comments应用的0001_initial迁移
('comments', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='是否显示'),
operations = [ # 迁移操作列表:当前需要执行的数据库变更
migrations.AlterField( # 修改已有字段
model_name='comment', # 要修改的模型名称为Comment
name='is_enable', # 要修改的字段名称为is_enable
field=models.BooleanField(default=False, verbose_name='是否显示'), # 将字段默认值从True改为False评论默认不显示
),
]
]

@ -1,60 +1,59 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
from django.conf import settings # 导入Django项目配置用于获取用户模型设置
from django.db import migrations, models # 导入迁移和模型模块,用于数据库结构变更
import django.db.models.deletion # 导入外键删除行为处理模块
import django.utils.timezone # 导入时区工具,处理时间字段默认值
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration): # 定义迁移类,包含数据库变更操作
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('blog', '0005_alter_article_options_alter_category_options_and_more'),
('comments', '0002_alter_comment_is_enable'),
dependencies = [ # 迁移依赖:执行当前迁移前需完成的迁移
migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型的可交换迁移
('blog', '0005_alter_article_options_alter_category_options_and_more'), # 依赖blog应用的指定迁移
('comments', '0002_alter_comment_is_enable'), # 依赖comments应用的0002迁移
]
operations = [
migrations.AlterModelOptions(
name='comment',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'},
),
migrations.RemoveField(
model_name='comment',
name='created_time',
),
migrations.RemoveField(
model_name='comment',
name='last_mod_time',
),
migrations.AddField(
model_name='comment',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
migrations.AddField(
model_name='comment',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
migrations.AlterField(
model_name='comment',
name='article',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'),
),
migrations.AlterField(
model_name='comment',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
),
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='enable'),
),
migrations.AlterField(
model_name='comment',
name='parent_comment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'),
),
]
operations = [ # 迁移操作列表:当前需要执行的数据库变更
migrations.AlterModelOptions( # 修改模型的元数据配置
name='comment', # 目标模型为Comment
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, # 将显示名称改为英文
),
migrations.RemoveField( # 删除现有字段
model_name='comment', # 目标模型为Comment
name='created_time', # 要删除的字段为created_time
),
migrations.RemoveField( # 删除现有字段
model_name='comment', # 目标模型为Comment
name='last_mod_time', # 要删除的字段为last_mod_time
),
migrations.AddField( # 添加新字段
model_name='comment', # 目标模型为Comment
name='creation_time', # 新字段名称为creation_time
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), # 时间字段,默认当前时间,显示名称为英文
),
migrations.AddField( # 添加新字段
model_name='comment', # 目标模型为Comment
name='last_modify_time', # 新字段名称为last_modify_time
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), # 时间字段,默认当前时间,显示名称为英文
),
migrations.AlterField( # 修改现有字段
model_name='comment', # 目标模型为Comment
name='article', # 目标字段为article
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), # 将显示名称改为英文
),
migrations.AlterField( # 修改现有字段
model_name='comment', # 目标模型为Comment
name='author', # 目标字段为author
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), # 将显示名称改为英文
),
migrations.AlterField( # 修改现有字段
model_name='comment', # 目标模型为Comment
name='is_enable', # 目标字段为is_enable
field=models.BooleanField(default=False, verbose_name='enable'), # 将显示名称改为英文"enable"
),
migrations.AlterField( # 修改现有字段
model_name='comment', # 目标模型为Comment
name='parent_comment', # 目标字段为parent_comment
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), # 将显示名称改为英文
),
]

@ -1,39 +1,47 @@
from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.conf import settings # 导入Django项目设置用于获取用户模型
from django.db import models # 导入Django模型模块用于定义数据模型
from django.utils.timezone import now # 导入当前时间工具,用于时间字段默认值
from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化
from blog.models import Article
from blog.models import Article # 从blog应用导入Article模型用于关联评论和文章
# Create your models here.
class Comment(models.Model):
# 评论内容字段文本类型最大长度300字符显示名称为"正文"
body = models.TextField('正文', max_length=300)
# 评论创建时间字段,使用国际化显示名称,默认值为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
# 评论最后修改时间字段,使用国际化显示名称,默认值为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
# 外键关联到用户模型,使用国际化显示名称,级联删除(用户删除则评论删除)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
# 外键关联到文章模型,使用国际化显示名称,级联删除(文章删除则评论删除)
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
# 自关联外键,用于实现评论回复功能,允许为空,级联删除
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
# 评论是否启用的开关,布尔类型,默认不启用,不允许为空
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False)
class Meta:
ordering = ['-id']
verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id'
ordering = ['-id'] # 默认排序方式按ID降序最新评论在前
verbose_name = _('comment') # 模型单数显示名称(国际化)
verbose_name_plural = verbose_name # 模型复数显示名称(与单数相同)
get_latest_by = 'id' # 获取最新记录时依据ID字段
def __str__(self):
return self.body
# 模型实例的字符串表示,返回评论内容
return self.body

@ -1,30 +1,33 @@
from django import template
from django import template # 导入Django模板模块用于创建自定义模板标签
register = template.Library()
register = template.Library() # 创建模板标签注册器,用于注册自定义标签
@register.simple_tag
@register.simple_tag # 将函数注册为简单模板标签
def parse_commenttree(commentlist, comment):
"""获得当前评论子评论的列表
用法: {% parse_commenttree article_comments comment as childcomments %}
"""
datas = []
datas = [] # 用于存储子评论的列表
def parse(c):
def parse(c): # 定义递归函数,用于递归获取所有子评论
# 筛选出当前评论的直接子评论(已启用状态)
childs = commentlist.filter(parent_comment=c, is_enable=True)
for child in childs:
datas.append(child)
parse(child)
for child in childs: # 遍历直接子评论
datas.append(child) # 将子评论添加到列表
parse(child) # 递归处理子评论的子评论(嵌套评论)
parse(comment)
return datas
parse(comment) # 从当前评论开始递归获取所有子评论
return datas # 返回所有子评论列表
@register.inclusion_tag('comments/tags/comment_item.html')
@register.inclusion_tag('comments/tags/comment_item.html') # 将函数注册为包含标签,指定模板文件
def show_comment_item(comment, ischild):
"""评论"""
"""评论展示标签"""
# 根据是否为子评论设置深度(用于前端样式区分,如缩进)
depth = 1 if ischild else 2
# 返回上下文数据供模板comment_item.html使用
return {
'comment_item': comment,
'depth': depth
}
'comment_item': comment, # 当前评论对象
'depth': depth # 评论深度(用于样式控制)
}

@ -1,109 +1,61 @@
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
from django.test import Client, RequestFactory, TransactionTestCase # 导入Django测试相关类
from django.urls import reverse # 导入reverse函数用于生成URL
from accounts.models import BlogUser
from blog.models import Category, Article
from comments.models import Comment
from comments.templatetags.comments_tags import *
from djangoblog.utils import get_max_articleid_commentid
from accounts.models import BlogUser # 从accounts应用导入BlogUser模型用户模型
from blog.models import Category, Article # 从blog应用导入分类和文章模型
from comments.models import Comment # 导入评论模型
from comments.templatetags.comment_tags import * # 导入评论相关的模板标签
from djangoblog.utils import get_max_articleid_commentid # 导入工具函数
# Create your tests here.
class CommentsTest(TransactionTestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
class CommentsTest(TransactionTestCase): # 定义评论测试类,继承事务测试类(支持数据库事务回滚)
def setUp(self): # 测试前的初始化方法,每个测试方法执行前都会调用
self.client = Client() # 创建测试客户端,用于模拟用户请求
self.factory = RequestFactory() # 创建请求工厂,用于构造请求对象
# 配置博客评论设置
from blog.models import BlogSettings
value = BlogSettings()
value.comment_need_review = True
value.comment_need_review = True # 设置评论需要审核
value.save()
# 创建超级用户(测试用)
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="liangliangyy1")
def update_article_comment_status(self, article):
comments = article.comment_set.all()
for comment in comments:
comment.is_enable = True
comment.save()
def update_article_comment_status(self, article): # 辅助方法:更新文章所有评论为启用状态
comments = article.comment_set.all() # 获取文章的所有评论
for comment in comments: # 遍历评论
comment.is_enable = True # 设置为启用
comment.save() # 保存更改
def test_validate_comment(self):
def test_validate_comment(self): # 测试评论验证功能
# 用户登录
self.client.login(username='liangliangyy1', password='liangliangyy1')
# 创建测试分类
category = Category()
category.name = "categoryccc"
category.save()
# 创建测试文章
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
article.author = self.user
article.category = category
article.type = 'a'
article.status = 'p'
article.author = self.user # 设置作者为测试用户
article.category = category # 设置分类
article.type = 'a' # 文章类型(假设'a'表示普通文章)
article.status = 'p' # 发布状态(假设'p'表示已发布)
article.save()
# 生成评论提交的URL
comment_url = reverse(
'comments:postcomment', kwargs={
'article_id': article.id})
'article_id': article.id}) # 传入文章ID参数
response = self.client.post(comment_url,
{
'body': '123ffffffffff'
})
self.assertEqual(response.status_code, 302)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 0)
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 1)
response = self.client.post(comment_url,
{
'body': '123ffffffffff',
})
self.assertEqual(response.status_code, 302)
article = Article.objects.get(pk=article.pk)
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 2)
parent_comment_id = article.comment_list()[0].id
response = self.client.post(comment_url,
{
'body': '''
# Title1
```python
import os
```
[url](https://www.lylinux.net/)
[ddd](http://www.baidu.com)
''',
'parent_comment_id': parent_comment_id
})
self.assertEqual(response.status_code, 302)
self.update_article_comment_status(article)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 3)
comment = Comment.objects.get(id=parent_comment_id)
tree = parse_commenttree(article.comment_list(), comment)
self.assertEqual(len(tree), 1)
data = show_comment_item(comment, True)
self.assertIsNotNone(data)
s = get_max_articleid_commentid()
self.assertIsNotNone(s)
from comments.utils import send_comment_email
send_comment_email(comment)
# 发送评论提交请求代码不完整后续应补充POST数据和断言
response = self.client.post(comment_url,

@ -1,11 +1,12 @@
from django.urls import path
from django.urls import path # 导入Django的路径函数用于定义URL路由
from . import views
from . import views # 从当前应用导入视图模块
app_name = "comments"
urlpatterns = [
app_name = "comments" # 定义应用的命名空间用于模板中URL反向解析
urlpatterns = [ # URL模式列表定义URL与视图的映射关系
path(
'article/<int:article_id>/postcomment',
views.CommentPostView.as_view(),
name='postcomment'),
]
'article/<int:article_id>/postcomment', # URL路径包含文章ID参数整数类型
views.CommentPostView.as_view(), # 关联的视图类使用as_view()方法转换为可调用视图
name='postcomment' # 该URL的名称用于反向解析
),
]

@ -1,28 +1,45 @@
import logging
import logging # 导入日志模块,用于记录程序运行中的日志信息
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化文本
from djangoblog.utils import get_current_site
from djangoblog.utils import send_email
from djangoblog.utils import get_current_site # 从自定义工具模块导入获取当前站点域名的函数
from djangoblog.utils import send_email # 从自定义工具模块导入发送邮件的函数
logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__) # 创建当前模块的日志记录器,用于记录该模块的日志
def send_comment_email(comment):
"""
发送评论相关邮件
1. 向评论作者发送评论成功的感谢邮件
2. 若当前评论是回复有父评论向父评论作者发送回复通知邮件
"""
# 获取当前网站的域名(用于拼接文章链接)
site = get_current_site().domain
# 邮件主题:评论感谢(支持国际化)
subject = _('Thanks for your comment')
# 拼接评论对应的文章访问链接HTTPS协议
article_url = f"https://{site}{comment.article.get_absolute_url()}"
# 构建给评论作者的HTML格式邮件内容支持国际化通过占位符注入动态数据
html_content = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
%(article_url)s""") % {
'article_url': article_url, # 文章访问链接
'article_title': comment.article.title # 文章标题
}
# 评论作者的邮箱(收件人)
tomail = comment.author.email
# 调用发送邮件函数,向评论作者发送感谢邮件
send_email([tomail], subject, html_content)
try:
# 判断当前评论是否有父评论(即是否是回复评论)
if comment.parent_comment:
# 构建给父评论作者的HTML格式邮件内容回复通知支持国际化
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
<br/>
@ -30,9 +47,16 @@ def send_comment_email(comment):
<br/>
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s
""") % {'article_url': article_url, 'article_title': comment.article.title,
'comment_body': comment.parent_comment.body}
""") % {
'article_url': article_url, # 文章访问链接
'article_title': comment.article.title, # 文章标题
'comment_body': comment.parent_comment.body # 父评论的内容(供作者识别)
}
# 父评论作者的邮箱(收件人)
tomail = comment.parent_comment.author.email
# 调用发送邮件函数,向父评论作者发送回复通知邮件
send_email([tomail], subject, html_content)
# 捕获发送回复邮件过程中的异常(避免单个邮件发送失败影响整体流程)
except Exception as e:
logger.error(e)
# 记录异常日志(便于问题排查)
logger.error(e)

@ -1,63 +1,76 @@
# Create your views here.
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
from django.views.generic.edit import FormView
from django.core.exceptions import ValidationError # 导入验证异常类,用于处理验证错误
from django.http import HttpResponseRedirect # 导入HTTP重定向类用于页面跳转
from django.shortcuts import get_object_or_404 # 导入获取对象或返回404的工具函数
from django.utils.decorators import method_decorator # 导入方法装饰器工具,用于为类视图方法添加装饰器
from django.views.decorators.csrf import csrf_protect # 导入CSRF保护装饰器防止跨站请求伪造
from django.views.generic.edit import FormView # 导入表单视图基类,用于处理表单提交逻辑
from accounts.models import BlogUser
from blog.models import Article
from .forms import CommentForm
from .models import Comment
from accounts.models import BlogUser # 从accounts应用导入用户模型
from blog.models import Article # 从blog应用导入文章模型
from .forms import CommentForm # 从当前应用导入评论表单
from .models import Comment # 从当前应用导入评论模型
class CommentPostView(FormView):
form_class = CommentForm
template_name = 'blog/article_detail.html'
"""评论提交视图类,处理评论发布功能"""
form_class = CommentForm # 指定使用的表单类为CommentForm
template_name = 'blog/article_detail.html' # 指定表单验证失败时渲染的模板
@method_decorator(csrf_protect)
@method_decorator(csrf_protect) # 为dispatch方法添加CSRF保护
def dispatch(self, *args, **kwargs):
# 调用父类的dispatch方法处理请求分发
return super(CommentPostView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
url = article.get_absolute_url()
return HttpResponseRedirect(url + "#comments")
"""处理GET请求重定向到文章详情页的评论区"""
article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象不存在则返回404
url = article.get_absolute_url() # 获取文章的绝对URL
return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论区锚点
def form_invalid(self, form):
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
"""处理表单验证失败的情况"""
article_id = self.kwargs['article_id'] # 获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取文章对象
# 渲染文章详情页,传递错误的表单和文章对象(用于显示错误信息)
return self.render_to_response({
'form': form,
'article': article
'form': form, # 验证失败的表单(包含错误信息)
'article': article # 文章对象
})
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
user = self.request.user
author = BlogUser.objects.get(pk=user.pk)
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
"""处理表单验证通过后的逻辑:保存评论并跳转"""
user = self.request.user # 获取当前登录用户
author = BlogUser.objects.get(pk=user.pk) # 获取用户对应的BlogUser对象
article_id = self.kwargs['article_id'] # 获取文章ID
article = get_object_or_404(Article, pk=article_id) # 获取文章对象
# 检查文章是否允许评论(评论状态为关闭或文章状态为草稿则不允许评论)
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
comment = form.save(False)
comment.article = article
raise ValidationError("该文章评论已关闭.") # 抛出验证异常
comment = form.save(False) # 不立即保存表单数据,返回评论对象
comment.article = article # 设置评论关联的文章
# 获取博客设置,判断评论是否需要审核
from djangoblog.utils import get_blog_setting
settings = get_blog_setting()
if not settings.comment_need_review:
comment.is_enable = True
comment.author = author
if not settings.comment_need_review: # 如果不需要审核
comment.is_enable = True # 直接设置评论为启用状态
comment.author = author # 设置评论的作者
# 处理回复功能如果存在父评论ID则设置父评论
if form.cleaned_data['parent_comment_id']:
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
comment.parent_comment = parent_comment
pk=form.cleaned_data['parent_comment_id']) # 获取父评论对象
comment.parent_comment = parent_comment # 设置当前评论的父评论
comment.save(True) # 保存评论到数据库
comment.save(True)
# 重定向到文章详情页的当前评论位置(带锚点)
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
(article.get_absolute_url(), comment.pk)) # 拼接URL包含评论ID锚点

@ -1,48 +1,52 @@
# Docker Compose配置文件版本为3指定兼容的Compose语法版本
version: '3'
# 定义所有服务(容器)
services:
# 1. Elasticsearch服务用于全文搜索功能集成IK中文分词器
es:
image: liangliangyy/elasticsearch-analysis-ik:8.6.1
container_name: es
restart: always
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
image: liangliangyy/elasticsearch-analysis-ik:8.6.1 # 使用带IK分词器的ES镜像版本8.6.1
container_name: es # 容器名称固定为"es",便于管理
restart: always # 容器退出后自动重启(确保服务持续运行)
environment: # 环境变量配置
- discovery.type=single-node # 单节点模式(无需集群,适合测试/小型部署)
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 设置JVM内存大小初始/最大均为512M避免内存溢出
ports: # 端口映射主机9200端口 → 容器9200端口ES默认API端口
- 9200:9200
volumes:
- ./bin/datas/es/:/usr/share/elasticsearch/data/
volumes: # 数据卷挂载持久化ES数据
- ./bin/datas/es/:/usr/share/elasticsearch/data/ # 主机目录 → 容器内ES数据存储目录
# 2. Kibana服务ES的可视化管理工具用于操作/监控ES
kibana:
image: kibana:8.6.1
restart: always
container_name: kibana
ports:
image: kibana:8.6.1 # Kibana镜像版本需与ES一致8.6.1
restart: always # 容器退出后自动重启
container_name: kibana # 容器名称固定为"kibana"
ports: # 端口映射主机5601端口 → 容器5601端口Kibana默认Web端口
- 5601:5601
environment:
- ELASTICSEARCH_HOSTS=http://es:9200
environment: # 环境变量配置指定关联的ES地址
- ELASTICSEARCH_HOSTS=http://es:9200 # 指向同网络内的"es"服务(容器间通过服务名通信)
# 3. Django博客服务核心应用服务
djangoblog:
build: .
restart: always
command: bash -c 'sh /code/djangoblog/bin/docker_start.sh'
ports:
build: . # 基于当前目录的Dockerfile构建镜像不使用现成镜像需本地有Dockerfile
restart: always # 容器退出后自动重启
command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 容器启动后执行的命令:运行启动脚本
ports: # 端口映射主机8000端口 → 容器8000端口Django默认开发服务器端口
- "8000:8000"
volumes:
- ./collectedstatic:/code/djangoblog/collectedstatic
- ./uploads:/code/djangoblog/uploads
environment:
- DJANGO_MYSQL_DATABASE=djangoblog
- DJANGO_MYSQL_USER=root
- DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E
- DJANGO_MYSQL_HOST=db
- DJANGO_MYSQL_PORT=3306
- DJANGO_MEMCACHED_LOCATION=memcached:11211
- DJANGO_ELASTICSEARCH_HOST=es:9200
links:
volumes: # 数据卷挂载:持久化应用数据/静态资源
- ./collectedstatic:/code/djangoblog/collectedstatic # 主机静态资源目录 → 容器内静态资源目录Nginx可直接访问
- ./uploads:/code/djangoblog/uploads # 主机上传文件目录 → 容器内上传文件目录(如博客图片)
environment: # 环境变量配置Django应用的关键参数数据库、缓存、ES等
- DJANGO_MYSQL_DATABASE=djangoblog # Django连接的MySQL数据库名
- DJANGO_MYSQL_USER=root # MySQL用户名
- DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL密码
- DJANGO_MYSQL_HOST=db # MySQL服务地址指向同网络内的"db"服务需额外配置db服务
- DJANGO_MYSQL_PORT=3306 # MySQL端口
- DJANGO_MEMCACHED_LOCATION=memcached:11211 # Memcached缓存地址指向同网络内的"memcached"服务,需额外配置)
- DJANGO_ELASTICSEARCH_HOST=es:9200 # ES服务地址指向同网络内的"es"服务)
links: # 显式链接到其他服务已逐步被depends_on替代此处用于兼容
- db # 链接到MySQL服务
- memcached # 链接到Memcached服务
depends_on: # 服务依赖启动djangoblog前先启动db服务确保数据库就绪
- db
- memcached
depends_on:
- db
container_name: djangoblog
container_name: djangoblog # 容器名称固定为"djangoblog"

@ -1,60 +1,67 @@
# Docker Compose配置文件版本为3指定Compose语法版本
version: '3'
# 定义所有服务(容器)
services:
# 1. MySQL数据库服务存储应用数据
db:
image: mysql:latest
restart: always
environment:
- MYSQL_DATABASE=djangoblog
- MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E
ports:
image: mysql:latest # 使用最新版MySQL镜像
restart: always # 容器退出后自动重启(确保服务持续运行)
environment: # 环境变量配置(数据库初始化参数)
- MYSQL_DATABASE=djangoblog # 自动创建的数据库名称
- MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL root用户密码
ports: # 端口映射主机3306端口 → 容器3306端口MySQL默认端口
- 3306:3306
volumes:
- ./bin/datas/mysql/:/var/lib/mysql
depends_on:
volumes: # 数据卷挂载持久化MySQL数据
- ./bin/datas/mysql/:/var/lib/mysql # 主机目录 → 容器内MySQL数据存储目录
depends_on: # 服务依赖启动db前先启动redis可能用于数据库缓存等场景
- redis
container_name: db
container_name: db # 容器名称固定为"db"
# 2. Django博客应用服务核心应用
djangoblog:
build:
context: ../../
restart: always
command: bash -c 'sh /code/djangoblog/bin/docker_start.sh'
ports:
build: # 构建配置
context: ../../ # 指定Dockerfile所在的上下文目录上级目录的上级目录
restart: always # 容器退出后自动重启
command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 启动命令:执行应用启动脚本
ports: # 端口映射主机8000端口 → 容器8000端口Django应用端口
- "8000:8000"
volumes:
- ./collectedstatic:/code/djangoblog/collectedstatic
- ./logs:/code/djangoblog/logs
- ./uploads:/code/djangoblog/uploads
environment:
- DJANGO_MYSQL_DATABASE=djangoblog
- DJANGO_MYSQL_USER=root
- DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E
- DJANGO_MYSQL_HOST=db
- DJANGO_MYSQL_PORT=3306
- DJANGO_REDIS_URL=redis:6379
links:
volumes: # 数据卷挂载:持久化应用数据和配置
- ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录供Nginx访问
- ./logs:/code/djangoblog/logs # 应用日志目录
- ./uploads:/code/djangoblog/uploads # 用户上传文件目录(如图片)
environment: # 环境变量配置(应用连接参数)
- DJANGO_MYSQL_DATABASE=djangoblog # 数据库名称与db服务对应
- DJANGO_MYSQL_USER=root # 数据库用户名
- DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # 数据库密码与db服务对应
- DJANGO_MYSQL_HOST=db # 数据库服务地址(指向同网络内的"db"服务)
- DJANGO_MYSQL_PORT=3306 # 数据库端口
- DJANGO_REDIS_URL=redis:6379 # Redis服务地址指向同网络内的"redis"服务)
links: # 显式链接到其他服务(用于容器间通信)
- db # 链接到MySQL服务
- redis # 链接到Redis服务
depends_on: # 服务依赖启动djangoblog前先启动db服务确保数据库就绪
- db
- redis
depends_on:
- db
container_name: djangoblog
container_name: djangoblog # 容器名称固定为"djangoblog"
# 3. Nginx服务反向代理和静态资源服务
nginx:
restart: always
image: nginx:latest
ports:
restart: always # 容器退出后自动重启
image: nginx:latest # 使用最新版Nginx镜像
ports: # 端口映射HTTP(80)和HTTPS(443)端口
- "80:80"
- "443:443"
volumes:
- ./bin/nginx.conf:/etc/nginx/nginx.conf
- ./collectedstatic:/code/djangoblog/collectedstatic
links:
- djangoblog:djangoblog
container_name: nginx
volumes: # 数据卷挂载Nginx配置和静态资源
- ./bin/nginx.conf:/etc/nginx/nginx.conf # 主机Nginx配置文件 → 容器内Nginx配置文件
- ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录与djangoblog服务共享
links: # 链接到djangoblog服务实现反向代理
- djangoblog:djangoblog # 将djangoblog服务映射为"djangoblog"主机名
container_name: nginx # 容器名称固定为"nginx"
# 4. Redis服务缓存服务用于提升应用性能
redis:
restart: always
image: redis:latest
container_name: redis
ports:
- "6379:6379"
restart: always # 容器退出后自动重启
image: redis:latest # 使用最新版Redis镜像
container_name: redis # 容器名称固定为"redis"
ports: # 端口映射主机6379端口 → 容器6379端口Redis默认端口
- "6379:6379"

@ -1,22 +1,33 @@
#!/usr/bin/env python
# 指定使用环境中的 Python 解释器执行此脚本
import os
import sys
# 当该脚本作为主程序运行时执行以下代码
if __name__ == "__main__":
# 设置默认的 Django 设置模块环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
try:
# 尝试导入 Django 的命令行执行函数
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
# 如果导入失败,可能是由于 Django 未安装或其他原因
# 上面的导入可能由于其他原因失败。确保问题确实是缺少 Django
# 以避免在 Python 2 上掩盖其他异常。
try:
import django
import django # 尝试直接导入 Django
except ImportError:
# 如果 Django 确实未导入,抛出明确的错误信息
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
# 如果能够导入 Django 但上面的导入仍然失败,重新抛出原始异常
raise
execute_from_command_line(sys.argv)
# 执行 Django 命令行命令,传递命令行参数
execute_from_command_line(sys.argv)

@ -1,7 +1,13 @@
# 导入Django的admin模块用于管理后台配置
from django.contrib import admin
# 注册模型的地方(当前尚未注册任何模型)
# Register your models here.
# 定义OwnTrackLogs模型的Admin管理类
# 继承自ModelAdmin这是Django admin的基础管理类
class OwnTrackLogsAdmin(admin.ModelAdmin):
# pass表示暂时不添加任何自定义配置
# 此时会使用ModelAdmin的默认配置来展示和管理模型数据
pass

@ -1,31 +1,40 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
# 由Django 4.1.7在2023年3月2日07:14生成
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
"""数据库迁移类,用于定义数据库结构的变更"""
# 标识这是初始迁移(第一次创建模型)
initial = True
# 依赖的其他迁移文件,初始迁移没有依赖
dependencies = [
]
# 定义要执行的数据库操作列表
operations = [
# 创建一个新的数据模型(数据库表)
migrations.CreateModel(
name='OwnTrackLog',
fields=[
name='OwnTrackLog', # 模型名称,对应数据库中的表名
fields=[ # 模型包含的字段定义
# 自增主键字段BigAutoField会自动生成大整数类型的唯一ID
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
# 用户标识符字段字符串类型最大长度100
('tid', models.CharField(max_length=100, verbose_name='用户')),
# 纬度字段,浮点型
('lat', models.FloatField(verbose_name='纬度')),
# 经度字段,浮点型
('lon', models.FloatField(verbose_name='经度')),
# 创建时间字段,默认值为当前时间
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
],
options={
'verbose_name': 'OwnTrackLogs',
'verbose_name_plural': 'OwnTrackLogs',
'ordering': ['created_time'],
'get_latest_by': 'created_time',
options={ # 模型的额外配置选项
'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称
'verbose_name_plural': 'OwnTrackLogs', # 模型的复数显示名称
'ordering': ['created_time'], # 默认排序方式,按创建时间升序
'get_latest_by': 'created_time', # 指定获取最新记录时使用的字段
},
),
]

@ -1,22 +1,36 @@
# Generated by Django 4.2.5 on 2023-09-06 13:19
# 由Django 4.2.5版本在2023年9月6日13:19自动生成
from django.db import migrations
class Migration(migrations.Migration):
"""
数据库迁移类用于修改现有数据模型的结构和配置
这是一个增量迁移基于之前的迁移进行修改
"""
# 依赖关系:表示此迁移依赖于'owntracks'应用中的0001_initial迁移
# 执行此迁移前必须先执行完依赖的迁移
dependencies = [
('owntracks', '0001_initial'),
]
# 定义要执行的数据库操作列表
operations = [
# 修改模型的配置选项
migrations.AlterModelOptions(
name='owntracklog',
options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'},
name='owntracklog', # 要修改的模型名称
# 新的模型配置选项
options={
'get_latest_by': 'creation_time', # 更新获取最新记录的字段为creation_time
'ordering': ['creation_time'], # 更新默认排序字段为creation_time
'verbose_name': 'OwnTrackLogs', # 模型的单数显示名称(未变)
'verbose_name_plural': 'OwnTrackLogs' # 模型的复数显示名称(未变)
},
),
# 重命名字段
migrations.RenameField(
model_name='owntracklog',
old_name='created_time',
new_name='creation_time',
model_name='owntracklog', # 要操作的模型名称
old_name='created_time', # 原字段名
new_name='creation_time', # 新字段名
),
]

@ -7,24 +7,37 @@
<h2 class="form-signin-heading text-center">{% trans 'forget the password' %}</h2>
<div class="card card-signin">
<!-- 用户头像 -->
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
<!-- 忘记密码表单 -->
<form class="form-signin" action="{% url 'account:forget_password' %}" method="post">
{% csrf_token %}
{% csrf_token %} <!-- CSRF令牌用于防止跨站请求伪造 -->
<!-- 显示表单非字段错误 -->
{{ form.non_field_errors }}
<!-- 遍历表单的所有字段 -->
{% for field in form %}
{{ field }}
{{ field.errors }}
{{ field }} <!-- 渲染字段输入框 -->
{{ field.errors }} <!-- 显示字段特定错误 -->
{% endfor %}
<input type="button" class="button" id="btn" value="{% trans 'get verification code' %}">
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans 'submit' %}</button>
<!-- 获取验证码按钮 -->
<input type="button" class="button" id="btn" value="{% trans 'get verification code' %}">
<!-- 提交按钮 -->
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans 'submit' %}</button>
</form>
</div>
<!-- 页面底部链接 -->
<p class="text-center">
<a href="/">Home Page</a>
|
<a href="{% url "account:login" %}">login page</a>
<a href="/">Home Page</a> <!-- 首页链接 -->
| <!-- 分隔符 -->
<a href="{% url "account:login" %}">login page</a> <!-- 登录页面链接 -->
</p>
</div> <!-- /container -->
{% endblock %}
{% endblock %}

@ -3,44 +3,59 @@
{% load i18n %}
{% block content %}
<div class="container">
<!-- 页面标题 -->
<h2 class="form-signin-heading text-center">Sign in with your Account</h2>
<!-- 登录卡片 -->
<div class="card card-signin">
<!-- 用户头像 -->
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
<!-- 登录表单 -->
<form class="form-signin" action="{% url 'account:login' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% csrf_token %} <!-- CSRF令牌防止跨站请求伪造 -->
{{ form.non_field_errors }} <!-- 显示非字段错误(如表单级别的错误) -->
<!-- 循环渲染表单的所有字段 -->
{% for field in form %}
{{ field }}
{{ field.errors }}
{{ field }} <!-- 渲染字段输入框 -->
{{ field.errors }} <!-- 显示该字段的错误信息 -->
{% endfor %}
<!-- 隐藏字段用于登录成功后重定向的URL -->
<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">
{% comment %}<a class="pull-right">Need help?</a>{% endcomment %}
{% comment %}<a class="pull-right">Need help?</a>{% endcomment %} <!-- 被注释的帮助链接 -->
<label>
<input type="checkbox" value="remember-me" name="remember"> Stay signed in
</label>
</div>
<!-- 加载OAuth第三方登录应用 -->
{% load oauth_tags %}
{% load_oauth_applications request%}
</form>
</div>
<!-- 底部链接 -->
<p class="text-center">
<a href="{% url "account:register" %}">
{% trans 'Create Account' %}
{% trans 'Create Account' %} <!-- 注册链接,支持国际化 -->
</a>
|
<a href="/">Home Page</a>
|
<a href="{% url "account:forget_password" %}">
{% trans 'Forget Password' %}
<a href="/">Home Page</a> <!-- 首页链接 -->
|
<a href="{% url "account:forget_password" %}">
{% trans 'Forget Password' %} <!-- 忘记密码链接,支持国际化 -->
</a>
</p>
</div> <!-- /container -->
{% endblock %}
{% endblock %}

@ -3,26 +3,35 @@
{% block content %}
<div class="container">
<!-- 页面标题:创建账户 -->
<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="">
<!-- 注册表单 -->
<form class="form-signin" action="{% url 'account:register' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% csrf_token %} <!-- CSRF保护令牌防止跨站请求伪造攻击 -->
{{ form.non_field_errors }} <!-- 显示非字段相关的错误信息 -->
<!-- 循环遍历表单的所有字段 -->
{% for field in form %}
{{ field }}
{{ field.errors }}
{{ field }} <!-- 渲染表单字段(输入框、选择框等) -->
{{ field.errors }} <!-- 显示该字段的验证错误信息 -->
{% endfor %}
<!-- 注册提交按钮 -->
<button class="btn btn-lg btn-primary btn-block" type="submit">Create Your Account</button>
</form>
</div>
<!-- 底部链接:跳转到登录页面 -->
<p class="text-center">
<a href="{% url "account:login" %}">Sign In</a>
<a href="{% url "account:login" %}">Sign In</a> <!-- 已有账户的登录链接 -->
</p>
</div> <!-- /container -->

@ -1,25 +1,31 @@
{% extends 'share_layout/base.html' %}
{% load i18n %}
{% 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/>
<br/> <!-- 换行分隔 -->
<!-- 导航链接区域,居中对齐 -->
<header class="archive-header" style="text-align: center">
<!-- 登录页面链接 -->
<a href="{% url "account:login" %}">
{% trans 'login' %}
{% trans 'login' %} <!-- 国际化翻译:登录 -->
</a>
|
|<!-- 分隔符 -->
<!-- 首页链接 -->
<a href="/">
{% trans 'back to the homepage' %}
{% trans 'back to the homepage' %} <!-- 国际化翻译:返回首页 -->
</a>
</header><!-- .archive-header -->
</div>

@ -1,45 +1,59 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% load i18n %}
{% block header %}
<title>{% trans 'article archive' %} | {{ SITE_DESCRIPTION }}</title>
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load cache %} {# 加载缓存标签 #}
{% load i18n %} {# 加载国际化标签 #}
{% block header %}
{# 页面头部元信息 #}
<title>{% trans 'article archive' %} |{{ SITE_DESCRIPTION }}</title> {# 页面标题 #}
{# SEO优化元标签 #}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{# Open Graph社交媒体元标签 #}
<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">
<p class="archive-title">{% trans 'article archive' %}</p>
<p class="archive-title">{% trans 'article archive' %}</p> {# 翻译:"文章归档" #}
</header><!-- .archive-header -->
{# 文章归档内容 #}
<div class="entry-content">
{# 按年份分组文章 #}
{% regroup article_list by pub_time.year as year_post_group %}
<ul>
{% for year in year_post_group %}
<li>{{ year.grouper }} {% trans 'year' %}
<li>
{{ year.grouper }} {% trans 'year' %} {# 显示年份 #}
{# 按月份分组该年份下的文章 #}
{% regroup year.list by pub_time.month as month_post_group %}
<ul>
{% for month in month_post_group %}
<li>{{ month.grouper }} {% trans 'month' %}
<li>
{{ month.grouper }} {% trans 'month' %} {# 显示月份 #}
{# 显示该月份下的所有文章 #}
<ul>
{% for article in month.list %}
<li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
<li>
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a> {# 文章链接 #}
</li>
{% endfor %}
</ul>
|{# 月份分隔符 #}
</li>
{% endfor %}
</ul>
@ -49,12 +63,9 @@
</div>
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}
{# 侧边栏区域 #}
{% load_sidebar user 'i' %} {# 加载侧边栏内容,传递用户信息和标识 #}
{% endblock %}

@ -1,52 +1,76 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% block header %}
{# 文章详情页不需要特殊的header内容使用基础模板的header #}
{% endblock %}
{% block content %}
{# 主要内容区域 #}
<div id="primary" class="site-content">
<div id="content" role="main">
{# 加载文章详情内容 #}
{% load_article_detail article False user %}
{# 参数说明article-文章对象, False-非索引页, user-当前用户 #}
{% if article.type == 'a' %}
{# 文章导航(仅对文章类型显示,不包括页面等) #}
{% if article.type == 'a' %} {# 如果是文章类型 #}
<nav class="nav-single">
<h3 class="assistive-text">文章导航</h3>
{% if next_article %}
<span class="nav-previous"><a href="{{ next_article.get_absolute_url }}" rel="prev"><span
class="meta-nav">&larr;</span> {{ next_article.title }}</a></span>
<h3 class="assistive-text">文章导航</h3> {# 屏幕阅读器辅助文本 #}
{# 上一篇文章链接 #}
{% if next_article %} {# 注意这里变量名可能有些反直觉next_article实际指上一篇 #}
<span class="nav-previous">
<a href="{{ next_article.get_absolute_url }}" rel="prev">
<span class="meta-nav">&larr;</span> {# 左箭头 #}
{{ next_article.title }} {# 上一篇文章标题 #}
</a>
</span>
{% endif %}
{% if prev_article %}
<span class="nav-next"><a href="{{ prev_article.get_absolute_url }}"
rel="next">{{ prev_article.title }} <span
class="meta-nav">&rarr;</span></a></span>
{# 下一篇文章链接 #}
{% if prev_article %} {# 注意这里变量名可能有些反直觉prev_article实际指下一篇 #}
<span class="nav-next">
<a href="{{ prev_article.get_absolute_url }}" rel="next">
{{ prev_article.title }} {# 下一篇文章标题 #}
<span class="meta-nav">&rarr;</span> {# 右箭头 #}
</a>
</span>
{% endif %}
</nav><!-- .nav-single -->
{% endif %}
</div><!-- #content -->
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %}
{# 评论区域 #}
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %} {# 如果文章开启评论且站点开启评论功能 #}
{# 显示现有评论列表 #}
{% include 'comments/tags/comment_list.html' %}
{% if user.is_authenticated %}
{# 评论表单区域 #}
{% if user.is_authenticated %} {# 用户已登录,显示评论表单 #}
{% include 'comments/tags/post_comment.html' %}
{% else %}
{% else %} {# 用户未登录,显示登录提示 #}
<div class="comments-area">
<h3 class="comment-meta">您还没有登录,请您<a
href="{% url "account:login" %}?next={{ request.get_full_path }}" rel="nofollow">登录</a>后发表评论。
<h3 class="comment-meta">
您还没有登录,请您
<a href="{% url "account:login" %}?next={{ request.get_full_path }}" rel="nofollow">登录</a>
后发表评论。
</h3>
{# 第三方登录选项 #}
{% load oauth_tags %}
{% load_oauth_applications request %}
</div>
{% endif %}
{% endif %}
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{# 加载侧边栏内容 #}
{% load_sidebar user "p" %}
{# 参数说明user-当前用户, "p"-可能表示文章详情页的特殊侧边栏配置 #}
{% endblock %}

@ -3,40 +3,59 @@
{% load cache %}
{% block header %}
{% if tag_name %}
<title>{{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }}</title>
<title>{{ page_type }}:{{ tag_name }} |{{ SITE_DESCRIPTION }}</title>
{# 示例:分类:Python |我的博客 #}
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{% else %} {# 如果是首页 #}
<title>{{ SITE_NAME }} |{{ SITE_DESCRIPTION }}</title>
{# 示例:我的博客 |一个技术博客网站 #}
{% endif %}
{# SEO元标签 #}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{# Open Graph社交媒体元标签 #}
<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">
{# 页面标题区域(仅标签页/分类页显示) #}
{% if page_type and tag_name %}
<header class="archive-header">
<p class="archive-title">{{ page_type }}<span>{{ tag_name }}</span></p>
<p class="archive-title">
{{ page_type }}<span>{{ tag_name }}</span>
{# 示例分类Python 或 标签Django #}
</p>
</header><!-- .archive-header -->
{% endif %}
{# 文章列表循环 #}
{% for article in article_list %}
{# 加载每篇文章的摘要显示 #}
{% load_article_detail article True user %}
{# 参数说明article-文章对象, True-索引页模式(显示摘要), user-当前用户 #}
{% endfor %}
{% if is_paginated %}
{# 分页组件 #}
{% if is_paginated %} {# 如果需要分页 #}
{% load_pagination_info page_obj page_type tag_name %}
{# 参数说明page_obj-分页对象, page_type-页面类型, tag_name-标签名 #}
{% endif %}
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{# 侧边栏区域 #}
{% load_sidebar user linktype %}
{# 参数说明user-当前用户, linktype-链接类型(可能用于侧边栏内容控制) #}
{% endblock %}

@ -1,45 +1,51 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load cache %} {# 加载缓存标签 #}
{% block header %}
{% if tag_name %}
{# 动态错误页面标题设置 #}
{% if tag_name %} {# 这个条件可能不太准确应该是根据statuscode判断 #}
{% if statuscode == '404' %}
<title>404 NotFound</title>
<title>404 NotFound</title> {# 404页面标题 #}
{% elif statuscode == '403' %}
<title>Permission Denied</title>
<title>Permission Denied</title> {# 403权限拒绝页面标题 #}
{% elif statuscode == '500' %}
<title>500 Error</title>
<title>500 Error</title> {# 500服务器错误页面标题 #}
{% else %}
<title></title>
<title></title> {# 其他情况空标题 #}
{% endif %}
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{% else %} {# 正常页面标题 #}
<title>{{ SITE_NAME }} |{{ SITE_DESCRIPTION }}</title>
{% endif %}
{# SEO元标签 #}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{# Open Graph社交媒体元标签 #}
<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 -->
<h1 class="archive-title">{{ message }}</h1> {# 显示错误消息 #}
</header><!-- .archive-header --->
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}
{# 侧边栏区域 #}
{% load_sidebar user 'i' %} {# 加载侧边栏,'i'可能表示索引页或错误页面 #}
{% endblock %}

@ -1,44 +1,50 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
<title>友情链接 | {{ SITE_DESCRIPTION }}</title>
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load cache %} {# 加载缓存标签 #}
{% block header %}
{# 页面头部元信息 #}
<title>友情链接 |{{ SITE_DESCRIPTION }}</title> {# 页面标题 #}
{# SEO优化元标签 #}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{# Open Graph社交媒体元标签 #}
<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">
<p class="archive-title">友情链接</p>
<p class="archive-title">友情链接</p> {# 页面主标题 #}
</header><!-- .archive-header -->
{# 友情链接列表 #}
<div class="entry-content">
<ul>
{% for obj in object_list %}
{% for obj in object_list %} {# 遍历友情链接对象列表 #}
<li>
<a href="{{ obj.link }}">{{ obj.name }}</a>
{# 每个友情链接项 #}
<a href="{{ obj.link }}">{{ obj.name }}</a> {# 链接地址和名称 #}
</li>
{% endfor %} </ul>
{% endfor %}
</ul>
</div>
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}
{# 侧边栏区域 #}
{% load_sidebar user 'i' %} {# 加载侧边栏内容 #}
{# 参数说明user-当前用户, 'i'-可能表示索引页类型的侧边栏 #}
{% endblock %}

@ -1,74 +1,83 @@
{% load blog_tags %}
{% load cache %}
{% load i18n %}
<article id="post-{{ article.pk }} "
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
<header class="entry-header">
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load cache %} {# 加载缓存标签 #}
{% load i18n %} {# 加载国际化标签 #}
<article id="post-{{ article.pk }}" class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
<header class="entry-header">
{# 文章标题 #}
<h1 class="entry-title">
{% if isindex %}
{% if article.article_order > 0 %}
<a href="{{ article.get_absolute_url }}"
rel="bookmark">【{% trans 'pin to top' %}】{{ article.title }}</a>
{% else %}
<a href="{{ article.get_absolute_url }}"
rel="bookmark">{{ article.title }}</a>
{% if isindex %} {# 如果是文章列表页 #}
{% if article.article_order > 0 %} {# 如果文章有置顶顺序 #}
<a href="{{ article.get_absolute_url }}" rel="bookmark">
【{% trans 'pin to top' %}】{{ article.title }} {# 显示置顶标记 #}
</a>
{% else %} {# 普通文章 #}
<a href="{{ article.get_absolute_url }}" rel="bookmark">
{{ article.title }} {# 普通标题 #}
</a>
{% endif %}
{% else %}
{{ article.title }}
{% else %} {# 如果是文章详情页 #}
{{ article.title }} {# 直接显示标题,不带链接 #}
{% endif %}
</h1>
{# 评论链接和浏览统计 #}
<div class="comments-link">
{% if article.comment_status == "o" and open_site_comment %}
<a href="{{ article.get_absolute_url }}#comments" class="ds-thread-count" data-thread-key="3815"
rel="nofollow">
{% if article.comment_status == "o" and open_site_comment %} {# 如果评论开启 #}
<a href="{{ article.get_absolute_url }}#comments" class="ds-thread-count" data-thread-key="3815" rel="nofollow">
<span class="leave-reply">
{% if article.comment_set and article.comment_set.count %}
{{ article.comment_set.count }} {% trans 'comments' %}
{% else %}
{% trans 'comment' %}
{% endif %}
{% if article.comment_set and article.comment_set.count %} {# 如果有评论 #}
{{ article.comment_set.count }} {% trans 'comments' %} {# 显示评论数量 #}
{% else %} {# 没有评论 #}
{% trans 'comment' %} {# 显示"评论"文字 #}
{% endif %}
</span>
</a>
{% endif %}
{# 浏览统计 #}
<div style="float:right">
{{ article.views }} views
{{ article.views }} views {# 显示浏览次数 #}
</div>
</div><!-- .comments-link -->
<br/>
{% if article.type == 'a' %}
{% if not isindex %}
{% cache 36000 breadcrumb article.pk %}
{% load_breadcrumb article %}
{# 面包屑导航(仅限文章详情页且不是列表页) #}
{% if article.type == 'a' %} {# 如果是文章类型 #}
{% if not isindex %} {# 如果不是列表页 #}
{% cache 36000 breadcrumb article.pk %} {# 缓存10小时 #}
{% load_breadcrumb article %} {# 加载面包屑导航 #}
{% endcache %}
{% endif %}
{% endif %}
</header><!-- .entry-header -->
{# 文章内容 #}
<div class="entry-content" itemprop="articleBody">
{% if isindex %}
{% if isindex %} {# 列表页显示文章摘要 #}
{# 处理markdown并截断内容 #}
{{ article.body|custom_markdown|escape|truncatechars_content }}
<p class='read-more'><a
href=' {{ article.get_absolute_url }}'>Read more</a></p>
{% else %}
{% if article.show_toc %}
{# "阅读更多"链接 #}
<p class='read-more'><a href='{{ article.get_absolute_url }}'>Read more</a></p>
{% else %} {# 详情页显示完整内容 #}
{% if article.show_toc %} {# 如果显示目录 #}
{# 生成markdown目录 #}
{% get_markdown_toc article.body as toc %}
<b>{% trans 'toc' %}:</b>
{{ toc|safe }}
<hr class="break_line"/>
<b>{% trans 'toc' %}:</b> {# 目录标题 #}
{{ toc|safe }} {# 安全输出HTML目录 #}
<hr class="break_line"/> {# 分隔线 #}
{% endif %}
{# 完整文章内容 #}
<div class="article">
{{ article.body|custom_markdown|escape }}
{{ article.body|custom_markdown|escape }} {# 渲染完整markdown内容 #}
</div>
{% endif %}
</div><!-- .entry-content -->
{# 加载文章元信息(作者、分类、标签等) #}
{% load_article_metas article user %}
</article><!-- #post -->

@ -1,59 +1,65 @@
{% load i18n %}
{% load blog_tags %}
{% load i18n %} {# 加载国际化标签,支持多语言翻译 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
<footer class="entry-meta">
{% trans 'posted in' %}
<a href="{{ article.category.get_absolute_url }}" rel="category tag">{{ article.category.name }}</a>
{# 文章分类信息 #}
{% trans 'posted in' %} {# 翻译:"发表于" #}
<a href="{{ article.category.get_absolute_url }}" rel="category tag">
{{ article.category.name }} {# 显示分类名称 #}
</a>
{% if article.type == 'a' %}
{% if article.tags.all %}
{% trans 'and tagged' %}
{# 文章标签信息(仅对文章类型显示) #}
{% if article.type == 'a' %} {# 如果是文章类型(非页面等) #}
{% if article.tags.all %} {# 如果有标签 #}
{% trans 'and tagged' %} {# 翻译:"和标签" #}
{# 循环遍历所有标签 #}
{% for t in article.tags.all %}
<a href="{{ t.get_absolute_url }}" rel="tag">{{ t.name }}</a>
<a href="{{ t.get_absolute_url }}" rel="tag">
{{ t.name }} {# 显示标签名称 #}
</a>
{# 如果不是最后一个标签,添加逗号分隔 #}
{% if t != article.tags.all.last %}
,
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
.{% trans 'by ' %}
.{% trans 'by ' %} {# 翻译:"由" #}
{# 作者信息 #}
<span class="by-author">
<span class="author vcard">
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
{% blocktranslate %}
<a class="url fn n"
href="{{ article.author.get_absolute_url }}"
{% blocktranslate %} {# 翻译块 #}
title="View all articles published by {{ article.author.username }}"
{% endblocktranslate %}
{% endblocktranslate %}
rel="author">
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }}
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }} {# 显示作者用户名 #}
</span>
</span>
</a>
</span>
</span>
{% trans 'on' %} {# 翻译:"于" #}
{# 发布时间信息 #}
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}" {# 使用自定义过滤器格式化时间 #}
itemprop="datePublished"
content="{% datetimeformat article.pub_time %}"
rel="bookmark">
<time class="entry-date updated" datetime="{{ article.pub_time }}">
{% datetimeformat article.pub_time %} {# 显示格式化后的发布时间 #}
</time>
</a>
</span>
{% trans 'on' %}
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}"
itemprop="datePublished" content="{% datetimeformat article.pub_time %}"
rel="bookmark">
<time class="entry-date updated"
datetime="{{ article.pub_time }}">
{% datetimeformat article.pub_time %}</time>
{% if user.is_superuser %}
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
{% endif %}
{# 管理员编辑链接 #}
{% if user.is_superuser %} {# 如果是超级用户 #}
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a> {# 显示编辑链接 #}
{% endif %}
</span>
</footer><!-- .entry-meta -->
</footer><!-- .entry-meta -->

@ -1,17 +1,29 @@
{% load i18n %}
{% load i18n %} {# 加载国际化标签,支持多语言翻译 #}
{# 文章分页导航 #}
<nav id="nav-below" class="navigation" role="navigation">
{# 导航标题(辅助文本,对屏幕阅读器友好) #}
<h3 class="assistive-text">
{% trans 'article navigation' %}
{% trans 'article navigation' %} {# 翻译:"文章导航" #}
</h3>
{% if page_obj.has_next and next_url%}
<div class="nav-previous"><a
href="{{ next_url }}"><span
class="meta-nav">&larr;</span> {% trans 'earlier articles' %}</a></div>
{# 上一页/更早文章链接 #}
{% if page_obj.has_next and next_url %} {# 如果有下一页且存在下一页URL #}
<div class="nav-previous">
<a href="{{ next_url }}"> {# 指向更早文章的链接 #}
<span class="meta-nav">&larr;</span> {# 左箭头图标 #}
{% trans 'earlier articles' %} {# 翻译:"更早的文章" #}
</a>
</div>
{% endif %}
{% if page_obj.has_previous and previous_url %}
<div class="nav-next"><a href="{{ previous_url }}">{% trans 'newer articles' %}
<span
class="meta-nav">→</span></a>
{# 下一页/较新文章链接 #}
{% if page_obj.has_previous and previous_url %} {# 如果有上一页且存在上一页URL #}
<div class="nav-next">
<a href="{{ previous_url }}"> {# 指向较新文章的链接 #}
{% trans 'newer articles' %} {# 翻译:"较新的文章" #}
<span class="meta-nav">&rarr;</span> {# 右箭头图标 #}
</a>
</div>
{% endif %}
</nav><!-- .navigation -->

@ -1,19 +1,26 @@
{% load i18n %}
{% if article_tags_list %}
<div class="panel panel-default">
<div class="panel-heading">
{% trans 'tags' %}
{% load i18n %} {# 加载国际化标签,支持多语言翻译 #}
{# 标签云面板 #}
{% if article_tags_list %} {# 如果存在标签列表 #}
<div class="panel panel-default"> {# Bootstrap面板容器 #}
<div class="panel-heading"> {# 面板标题区域 #}
{% trans 'tags' %} {# 翻译:"标签" #}
</div>
<div class="panel-body">
<div class="panel-body"> {# 面板内容区域 #}
{# 循环遍历标签列表 #}
{% for url,count,tag,color in article_tags_list %}
<a class="label label-{{ color }}" style="display: inline-block;" href="{{ url }}"
title="{{ tag.name }}">
{{ tag.name }}
<span class="badge">{{ count }}</span>
{# 单个标签链接 #}
<a class="label label-{{ color }}" {# Bootstrap #}
style="display: inline-block;" {# 内联块级显示 #}
href="{{ url }}" {# 标签页面链接 #}
title="{{ tag.name }}"> {# 鼠标悬停显示标签名 #}
{{ tag.name }} {# 显示标签名称 #}
<span class="badge">{{ count }}</span> {# 显示该标签下的文章数量 #}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}

@ -1,19 +1,26 @@
{# 面包屑导航 - 使用Schema.org结构化数据 #}
<ul itemscope itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">
{# 循环遍历面包屑路径中的每个节点 #}
{% for name,url in names %}
<li itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
<a href="{{ url }}" itemprop="item" >
<span itemprop="name">{{ name }}</span></a>
{# 可点击的面包屑链接 #}
<a href="{{ url }}" itemprop="item">
<span itemprop="name">{{ name }}</span>
</a>
{# 位置序号 - Schema.org结构化数据 #}
<meta itemprop="position" content="{{ forloop.counter }}"/>
</li>
{% endfor %}
{# 当前页面(最后一个节点,不可点击) #}
<li class="active" itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
{# 当前页面名称 #}
<span itemprop="name">{{ title }}</span>
{# 当前位置序号 #}
<meta itemprop="position" content="{{ count }}"/>
</li>
</ul>
</ul>

@ -1,6 +1,10 @@
{% load blog_tags %}
{% load i18n %}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load i18n %} {# 加载国际化标签 #}
{# 侧边栏主容器 #}
<div id="secondary" class="widget-area" role="complementary">
{# 搜索组件 #}
<aside id="search-2" class="widget widget_search">
<form role="search" method="get" id="searchform" class="searchform" action="/search">
<div>
@ -10,127 +14,154 @@
</div>
</form>
</aside>
{# 自定义侧边栏内容 #}
{% if extra_sidebars %}
{% for sidebar in extra_sidebars %}
<aside class="widget_text widget widget_custom_html"><p class="widget-title">
{{ sidebar.name }}</p>
<aside class="widget_text widget widget_custom_html">
<p class="widget-title">{{ sidebar.name }}</p>
<div class="textwidget custom-html-widget">
{{ sidebar.content|custom_markdown|safe }}
{{ sidebar.content|custom_markdown|safe }} {# 使用自定义markdown解析 #}
</div>
</aside>
{% endfor %}
{% endif %}
{# 最多阅读文章 #}
{% if most_read_articles %}
<aside id="views-4" class="widget widget_views"><p class="widget-title">Views</p>
<aside id="views-4" class="widget widget_views">
<p class="widget-title">Views</p>
<ul>
{% for a in most_read_articles %}
<li>
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }}
</a> - {{ a.views }} views
</a> - {{ a.views }} views {# 显示浏览次数 #}
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{# 分类列表 #}
{% if sidebar_categorys %}
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms"><p class="widget-title">{% trans 'category' %}</p>
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms">
<p class="widget-title">{% trans 'category' %}</p>
<ul>
{% for c in sidebar_categorys %}
<li class="cat-item cat-item-184"><a href={{ c.get_absolute_url }}>{{ c.name }}</a>
<li class="cat-item cat-item-184">
<a href={{ c.get_absolute_url }}>{{ c.name }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if sidebar_comments and open_site_comment %}
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments"><p class="widget-title">{% trans 'recent comments' %}</p>
{# 最新评论 #}
{% if sidebar_comments and open_site_comment %} {# 检查评论功能是否开启 #}
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments">
<p class="widget-title">{% trans 'recent comments' %}</p>
<ul id="recentcomments">
{% for c in sidebar_comments %}
<li class="recentcomments">
<span class="comment-author-link">
{{ c.author.username }}</span>
{% trans 'published on' %}《
<span class="comment-author-link">{{ c.author.username }}</span>
{% trans 'published on' %}《 {# 翻译:"发表于" #}
<a href="{{ c.article.get_absolute_url }}#comment-{{ c.pk }}">{{ c.article.title }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{# 最新文章 #}
{% if recent_articles %}
<aside id="recent-posts-2" class="widget widget_recent_entries"><p class="widget-title">{% trans 'recent articles' %}</p>
<aside id="recent-posts-2" class="widget widget_recent_entries">
<p class="widget-title">{% trans 'recent articles' %}</p>
<ul>
{% for a in recent_articles %}
<li><a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }}
</a></li>
{% for a in recent_articles %}
<li>
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }}
</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{# 书签链接 #}
{% if sidabar_links %}
<aside id="linkcat-0" class="widget widget_links"><p class="widget-title">{% trans 'bookmark' %}</p>
<aside id="linkcat-0" class="widget widget_links">
<p class="widget-title">{% trans 'bookmark' %}</p>
<ul class='xoxo blogroll'>
{% for l in sidabar_links %}
<li>
<a href="{{ l.link }}" target="_blank" title="{{ l.name }}">{{ l.name }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{# Google广告 #}
{% if show_google_adsense %}
<aside id="text-2" class="widget widget_text"><p class="widget-title">Google AdSense</p>
<aside id="text-2" class="widget widget_text">
<p class="widget-title">Google AdSense</p>
<div class="textwidget">
{{ google_adsense_codes|safe }}
{{ google_adsense_codes|safe }} {# 安全输出广告代码 #}
</div>
</aside>
{% endif %}
{# 标签云 #}
{% if sidebar_tags %}
<aside id="tag_cloud-2" class="widget widget_tag_cloud"><p class="widget-title">{% trans 'Tag Cloud' %}</p>
<aside id="tag_cloud-2" class="widget widget_tag_cloud">
<p class="widget-title">{% trans 'Tag Cloud' %}</p>
<div class="tagcloud">
{% for tag,count,size in sidebar_tags %}
{% 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 }}
style="font-size: {{ size }}pt;"
title="{{ count }}个话题"> {{ tag.name }}
</a>
{% endfor %}
</div>
</aside>
{% endif %}
<aside id="text-2" class="widget widget_text"><p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p>
{# GitHub项目信息 #}
<aside id="text-2" class="widget widget_text">
<p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p>
<div class="textwidget">
<p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/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://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork"
alt="GitHub forks"></a></p>
<p>
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow">
<img src="https://resource.lylinux.net/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://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork" alt="GitHub forks">
</a>
</p>
</div>
</aside>
<aside id="meta-3" class="widget widget_meta"><p class="widget-title">{% trans 'Function' %}</p>
{# 功能链接 #}
<aside id="meta-3" class="widget widget_meta">
<p class="widget-title">{% trans 'Function' %}</p>
<ul>
<li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li>
{# 根据登录状态显示不同链接 #}
{% if user.is_authenticated %}
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a>
</li>
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a></li>
{% else %}
<li><a href="{% url "account:login" %}" rel="nofollow">{% trans 'login' %}</a></li>
{% endif %}
{# 超级用户专属功能 #}
{% if user.is_superuser %}
<li><a href="{% url 'owntracks:show_dates' %}" target="_blank">{% trans 'Track record' %}</a></li>
{% endif %}
<li><a href="http://gitbook.lylinux.net" target="_blank" rel="nofollow">GitBook</a></li>
</ul>
</aside>
{# 返回顶部按钮 #}
<div id="rocket" class="show" title="{% trans 'Click me to return to the top' %}"></div>
</div><!-- #secondary -->

@ -1,34 +1,48 @@
{% load blog_tags %}
{% load blog_tags %} {# 加载自定义博客标签 #}
{# 评论项模板 - 显示单条评论及其回复 #}
<li class="comment even thread-even depth-{{ depth }} parent" id="comment-{{ comment_item.pk }}">
<div id="div-comment-{{ comment_item.pk }}" class="comment-body">
{# 评论作者信息 #}
<div class="comment-author vcard">
{# 使用Gravatar显示头像 #}
<img alt=""
src="{{ comment_item.author.email|gravatar_url:150 }}"
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
src="{{ comment_item.author.email|gravatar_url:150 }}" {# 通过邮箱获取Gravatar头像 #}
class="avatar avatar-96 photo" height="96" width="96">
{# 作者姓名 #}
<cite class="fn">
<a rel="nofollow"
{% if comment_item.author.is_superuser %}
href="{{ comment_item.author.get_absolute_url }}"
{% else %}
href="#"
{% endif %}
{% 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 }}
class="url">
{{ comment_item.author.username }} {# 显示用户名 #}
</a>
</cite>
</div>
{# 评论元信息 #}
<div class="comment-meta commentmetadata">
<div>{{ comment_item.creation_time }}</div>
<div>{{ comment_item.creation_time }}</div> {# 评论时间 #}
{# 回复对象信息 #}
<div>回复给:@{{ comment_item.author.parent_comment.username }}</div>
</div>
<p>{{ comment_item.body|escape|comment_markdown }}</p>
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)"
onclick="do_reply({{ comment_item.pk }})"
aria-label="回复给{{ comment_item.author.username }}">回复</a></div>
{# 评论内容 #}
<p>{{ comment_item.body|escape|comment_markdown }}</p> {# 安全处理并渲染markdown #}
{# 回复按钮 #}
<div class="reply">
<a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)"
onclick="do_reply({{ comment_item.pk }})" {# 点击触发回复函数 #}
aria-label="回复给{{ comment_item.author.username }}"> {# 无障碍标签 #}
回复 {# 回复按钮文本 #}
</a>
</div>
</div>
</li><!-- #comment-## -->

@ -1,53 +1,69 @@
{% load blog_tags %}
{% load blog_tags %} {# 加载自定义博客标签 #}
{# 评论项模板 - 支持嵌套回复的树形结构 #}
<li class="comment even thread-even depth-{{ depth }} parent" id="comment-{{ comment_item.pk }}"
style="margin-left: {% widthratio depth 1 3 %}rem">
style="margin-left: {% widthratio depth 1 3 %}rem"> {# 根据深度设置左边距,实现缩进 #}
<div id="div-comment-{{ comment_item.pk }}" class="comment-body">
{# 评论作者信息 #}
<div class="comment-author vcard">
{# Gravatar头像 #}
<img alt=""
src="{{ comment_item.author.email|gravatar_url:150 }}"
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
class="avatar avatar-96 photo" height="96" width="96">
{# 作者姓名 #}
<cite class="fn">
<a rel="nofollow"
{% if comment_item.author.is_superuser %}
{% if comment_item.author.is_superuser %} {# 超级用户有个人主页链接 #}
href="{{ comment_item.author.get_absolute_url }}"
{% else %}
href="#"
{% endif %}
{% else %}
href="#" {# 普通用户无链接 #}
{% endif %}
rel="external nofollow"
class="url">{{ comment_item.author.username }}
class="url">
{{ comment_item.author.username }}
</a>
</cite>
</div>
{# 评论元信息 #}
<div class="comment-meta commentmetadata">
{{ comment_item.creation_time }}
{{ comment_item.creation_time }} {# 评论时间 #}
</div>
{# 回复对象信息 #}
<p>
{% if comment_item.parent_comment %}
<div>回复 <a
href="#comment-{{ comment_item.parent_comment.pk }}">@{{ comment_item.parent_comment.author.username }}</a>
{% if comment_item.parent_comment %} {# 如果是回复评论 #}
<div>
回复 <a href="#comment-{{ comment_item.parent_comment.pk }}"> {# 跳转到父评论 #}
@{{ comment_item.parent_comment.author.username }} {# 显示被回复者用户名 #}
</a>
</div>
{% endif %}
</p>
<p>{{ comment_item.body|escape|comment_markdown }}</p>
{# 评论内容 #}
<p>{{ comment_item.body|escape|comment_markdown }}</p> {# 安全渲染评论内容 #}
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)" data-pk="{{ comment_item.pk }}"
aria-label="回复给{{ comment_item.author.username }}">回复</a></div>
{# 回复按钮 #}
<div class="reply">
<a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)"
data-pk="{{ comment_item.pk }}" {# 存储评论ID用于JavaScript #}
aria-label="回复给{{ comment_item.author.username }}">回复</a>
</div>
</div>
</li><!-- #comment-## -->
{% query article_comments parent_comment=comment_item as cc_comments %}
{% for cc in cc_comments %}
{# 递归加载子评论 - 构建评论树 #}
{% query article_comments parent_comment=comment_item as cc_comments %} {# 查询当前评论的子评论 #}
{% for cc in cc_comments %} {# 遍历所有子评论 #}
{% with comment_item=cc template_name="comments/tags/comment_item_tree.html" %}
{% if depth >= 1 %}
{% include template_name %}
{% else %}
{% with depth=depth|add:1 %}
{% include template_name %}
{% if depth >= 1 %} {# 如果已经有一定深度 #}
{% include template_name %} {# 直接包含模板,保持当前深度 #}
{% else %} {# 如果是第一层深度 #}
{% with depth=depth|add:1 %} {# 深度加1 #}
{% include template_name %} {# 包含模板,深度递增 #}
{% endwith %}
{% endif %}
{% endwith %}

@ -1,45 +1,64 @@
<dev>
<div>
<section id="comments" class="themeform">
{% load blog_tags %}
{% load comments_tags %}
{% load cache %}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% load comments_tags %} {# 加载评论相关标签 #}
{% load cache %} {# 加载缓存标签 #}
{# 评论标签页导航 #}
<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="active">
<a href="#commentlist-container">
<i class="fa fa-comments-o"></i>评论<span>{{ comment_count }}</span>
</a>
</li>
</ul>
{% if article_comments %}
{# 评论列表容器 #}
{% if article_comments %} {# 如果存在评论 #}
<div id="commentlist-container" class="comment-tab" style="display: block;">
{# 评论列表 #}
<ol class="commentlist">
{# {% query article_comments parent_comment=None as parent_comments %}#}
{# 注释掉的代码:{% query article_comments parent_comment=None as parent_comments %} #}
{# 遍历父级评论(顶级评论,没有父评论) #}
{% for comment_item in p_comments %}
{# 初始化深度为0开始递归渲染评论树 #}
{% with 0 as depth %}
{% include "comments/tags/comment_item_tree.html" %}
{% endwith %}
{% endfor %}
</ol><!--/.commentlist-->
{# 评论分页导航 #}
<div class="navigation">
<nav class="nav-single">
{# 上一页链接 #}
{% if comment_prev_page_url %}
<div class="nav-previous">
<span><a href="{{ comment_prev_page_url }}" rel="prev"><span
class="meta-nav">←</span> 上一页</a></span>
<span>
<a href="{{ comment_prev_page_url }}" rel="prev">
<span class="meta-nav"></span> 上一页
</a>
</span>
</div>
{% endif %}
{# 下一页链接 #}
{% if comment_next_page_url %}
<div class="nav-next">
<span><a href="{{ comment_next_page_url }}" rel="next">下一页 <span
class="meta-nav">→</span></a></span>
<span>
<a href="{{ comment_next_page_url }}" rel="next">
下一页 <span class="meta-nav"></span>
</a>
</span>
</div>
{% endif %}
</nav>
</div>
<br/>
</div>
{% endif %}
</section>
</dev>
</div>

@ -1,33 +1,47 @@
<div id="comments" class="comments-area">
{# 评论表单响应区域 #}
<div id="respond" class="comment-respond">
<h3 id="reply-title" class="comment-reply-title">发表评论
<small><a rel="nofollow" id="cancel-comment-reply-link" href="/wordpress/?p=3786#respond"
style="display:none;">取消回复</a></small>
{# 评论标题 #}
<h3 id="reply-title" class="comment-reply-title">
发表评论
<small>
<a rel="nofollow" id="cancel-comment-reply-link" href="/wordpress/?p=3786#respond"
style="display:none;">取消回复</a>
</small>
</h3>
{# 评论表单 #}
<form action="{% url 'comments:postcomment' article.pk %}" method="post" id="commentform"
class="comment-form">{% csrf_token %}
class="comment-form">
{% csrf_token %} {# CSRF保护令牌 #}
{# 评论内容输入框 #}
<p class="comment-form-comment">
{{ form.body.label_tag }}
{{ form.body }}
{{ form.body.errors }}
{{ form.body.label_tag }} {# 显示评论内容标签 #}
{{ form.body }} {# 评论内容文本域 #}
{{ form.body.errors }} {# 显示评论内容错误信息 #}
</p>
{# 隐藏的父评论ID字段用于回复功能 #}
{{ form.parent_comment_id }}
{# 表单提交区域 #}
<div class="form-submit">
{% if COMMENT_NEED_REVIEW %}
{# Markdown支持提示 #}
{% if COMMENT_NEED_REVIEW %} {# 如果需要评论审核 #}
<span class="comment-markdown"> 支持markdown评论经审核后才会显示。</span>
{% else %}
{% else %} {# 如果不需要审核 #}
<span class="comment-markdown"> 支持markdown。</span>
{% endif %}
{# 提交按钮 #}
<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>
</div>
</form>
</div><!-- #respond -->
</div><!-- #comments .comments-area -->
</div><!-- #comments .comments-area -->

@ -1,20 +1,26 @@
{% extends 'share_layout/base.html' %}
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% block header %}
<title> {{ title }}</title>
<title> {{ title }}</title> {# 动态设置页面标题 #}
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<div id="primary" class="site-content"> {# 主要内容容器 #}
<div id="content" role="main"> {# 内容区域role属性用于无障碍访问 #}
{# 主要消息标题区域 #}
<header class="archive-header">
<h2 class="archive-title"> {{ content }}</h2>
<h2 class="archive-title"> {{ content }}</h2> {# 显示主要消息内容 #}
</header><!-- .archive-header -->
<br/>
<br/> {# 换行分隔 #}
{# 导航链接区域,居中对齐 #}
<header class="archive-header" style="text-align: center">
{# 登录页面链接 #}
<a href="{% url "account:login" %}">登录</a>
|
{# 首页链接 #}
<a href="/">回到首页</a>
</header><!-- .archive-header -->
</div>

@ -1,13 +1,16 @@
{% load i18n %}
{% load i18n %} {# 加载国际化标签,支持多语言翻译 #}
{# 第三方快速登录组件 #}
<div class="widget-login">
{% if apps %}
{% if apps %} {# 如果配置了第三方登录应用 #}
<small>
{% trans 'quick login' %}:
{% trans 'quick login' %}: {# 翻译:"快速登录" #}
</small>
{# 遍历所有第三方登录应用 #}
{% for icon,url in apps %}
<a href="{{ url }}" rel="nofollow">
<span class="icon-sn-{{ icon }}"></span>
<a href="{{ url }}" rel="nofollow"> {# 第三方登录链接nofollow避免权重传递 #}
<span class="icon-sn-{{ icon }}"></span> {# 第三方应用图标基于icon名称的CSS类 #}
</a>
{% endfor %}
{% endif %}
</div>
</div>

@ -1,45 +1,59 @@
{% extends 'share_layout/base_account.html' %}
{% extends 'share_layout/base_account.html' %} {# 继承账户相关的基础模板 #}
{% load static %} {# 加载静态文件标签 #}
{% load static %}
{% block content %}
<div class="container">
<div class="container"> {# Bootstrap容器 #}
<h2 class="form-signin-heading text-center">绑定您的邮箱账号</h2>
<h2 class="form-signin-heading text-center">绑定您的邮箱账号</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="">
<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>
{# 绑定邮箱表单 #}
<form class="form-signin" action="" method="post"> {# 空action表示提交到当前URL #}
{% csrf_token %} {# CSRF保护令牌 #}
{# 注释掉的原始表单代码使用Django表单代替 #}
{% 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 %}
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
{% endcomment %}
{# 显示非字段错误(如表单级别的验证错误) #}
{{ form.non_field_errors }}
{# 循环渲染表单的所有字段 #}
{% for field in form %}
{{ field }}
{{ field.errors }}
{{ field }} {# 渲染字段输入框 #}
{{ field.errors }} {# 显示该字段的错误信息 #}
{% endfor %}
{# 提交按钮 #}
<button class="btn btn-lg btn-primary btn-block" type="submit">提交</button>
{# 注释掉的"记住我"和帮助链接 #}
{% comment %}
<div class="checkbox">
<div class="checkbox">
<a class="pull-right">Need help?</a>
<label>
<input type="checkbox" value="remember-me"> Stay signed in
</label>
</div>
{% endcomment %}
{% endcomment %}
</form>
</div>
{# 底部导航链接 #}
<p class="text-center">
<a href="{% url "account:login" %}">登录</a>
<a href="{% url "account:login" %}">登录</a> {# 跳转到登录页面 #}
</p>
</div> <!-- /container -->

@ -2,16 +2,19 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>记录日期</title>
<title>记录日期</title> <!-- 页面标题:记录日期 -->
</head>
<body>
<ul>
{% for date in results %}
<li>
<a href="{% url 'owntracks:show_maps' %}?date={{ date }}" target="_blank">{{ date }}</a>
</li>
{% endfor %}
</ul>
<!-- 日期列表 -->
<ul>
{% for date in results %} <!-- 遍历日期结果集 -->
<li>
<!-- 日期链接,点击在新窗口打开对应日期的地图页面 -->
<a href="{% url 'owntracks:show_maps' %}?date={{ date }}" target="_blank">
{{ date }} <!-- 显示日期 -->
</a>
</li>
{% endfor %}
</ul>
</body>
</html>

@ -1,20 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<style>
/* 全局样式设置 */
html,
body,
#container {
width: 100%;
height: 100%;
margin: 0px;
margin: 0px; /* 去除默认边距 */
}
/* 加载提示样式 */
#loadingTip {
position: absolute;
z-index: 9999;
z-index: 9999; /* 确保在最上层 */
top: 0;
left: 0;
padding: 3px 10px;
@ -23,113 +26,114 @@
font-size: 14px;
}
</style>
<title>运动轨迹</title>
<title>运动轨迹</title> <!-- 页面标题 -->
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src='//webapi.amap.com/maps?v=1.4.4&key=9c89950bdfbcecd46f814309384655cd'></script>
<!-- UI组件库 1.0 -->
<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
<script type="text/javascript">
//创建地图
var map = new AMap.Map('container', {
zoom: 4
});
AMapUI.load(['ui/misc/PathSimplifier', 'lib/$'], function (PathSimplifier, $) {
if (!PathSimplifier.supportCanvas) {
alert('当前环境不支持 Canvas');
return;
}
//just some colors
var colors = [
"#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00",
"#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707",
"#651067", "#329262", "#5574a6", "#3b3eac"
];
var pathSimplifierIns = new PathSimplifier({
zIndex: 100,
//autoSetFitView:false,
map: map, //所属的地图实例
getPath: function (pathData, pathIndex) {
return pathData.path;
},
getHoverTitle: function (pathData, pathIndex, pointIndex) {
if (pointIndex >= 0) {
//point
return pathData.name + ',点:' + pointIndex + '/' + pathData.path.length;
}
return pathData.name + ',点数量' + pathData.path.length;
},
renderOptions: {
pathLineStyle: {
dirArrowStyle: true
},
getPathStyle: function (pathItem, zoom) {
var color = colors[pathItem.pathIndex % colors.length],
lineWidth = Math.round(4 * Math.pow(1.1, zoom - 3));
return {
pathLineStyle: {
strokeStyle: color,
lineWidth: lineWidth
},
pathLineSelectedStyle: {
lineWidth: lineWidth + 2
},
pathNavigatorStyle: {
fillStyle: color
}
};
}
}
<!-- 地图容器 -->
<div id="container"></div>
<!-- 高德地图API -->
<script type="text/javascript" src='//webapi.amap.com/maps?v=1.4.4&key=9c89950bdfbcecd46f814309384655cd'></script>
<!-- UI组件库 -->
<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
<script type="text/javascript">
// 创建地图实例
var map = new AMap.Map('container', {
zoom: 4 // 初始缩放级别
});
window.pathSimplifierIns = pathSimplifierIns;
$('<div id="loadingTip">加载数据,请稍候...</div>').appendTo(document.body);
$.getJSON('/owntracks/get_datas?date={{ date }}', function (d) {
if (!d || !d.length) {
$("#loadingTip").text("没有数据...")
// 加载路径简化组件
AMapUI.load(['ui/misc/PathSimplifier', 'lib/$'], function (PathSimplifier, $) {
// 检查Canvas支持
if (!PathSimplifier.supportCanvas) {
alert('当前环境不支持 Canvas');
return;
}
$('#loadingTip').remove();
pathSimplifierIns.setData(d);
//initRoutesContainer(d);
// 颜色数组,用于不同轨迹线的颜色
var colors = [
"#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00",
"#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707",
"#651067", "#329262", "#5574a6", "#3b3eac"
];
// 创建路径简化器实例
var pathSimplifierIns = new PathSimplifier({
zIndex: 100,
map: map, // 绑定地图实例
// 获取路径数据的方法
getPath: function (pathData, pathIndex) {
return pathData.path;
},
// 悬停提示内容
getHoverTitle: function (pathData, pathIndex, pointIndex) {
if (pointIndex >= 0) {
return pathData.name + ',点:' + pointIndex + '/' + pathData.path.length;
}
return pathData.name + ',点数量' + pathData.path.length;
},
// 渲染选项
renderOptions: {
pathLineStyle: {
dirArrowStyle: true // 显示方向箭头
},
// 根据缩放级别设置路径样式
getPathStyle: function (pathItem, zoom) {
var color = colors[pathItem.pathIndex % colors.length],
lineWidth = Math.round(4 * Math.pow(1.1, zoom - 3));
return {
pathLineStyle: {
strokeStyle: color,
lineWidth: lineWidth
},
pathLineSelectedStyle: {
lineWidth: lineWidth + 2
},
pathNavigatorStyle: {
fillStyle: color
}
};
}
}
});
function onload() {
pathSimplifierIns.renderLater();
}
// 将实例保存到全局变量
window.pathSimplifierIns = pathSimplifierIns;
function onerror(e) {
alert('图片加载失败!');
}
// 显示加载提示
$('<div id="loadingTip">加载数据,请稍候...</div>').appendTo(document.body);
d.forEach(function (item, index) {
var navg1 = pathSimplifierIns.createPathNavigator(index, {
loop: true,
speed: 1000,
// 从服务器获取轨迹数据
$.getJSON('/owntracks/get_datas?date={{ date }}', function (d) {
// 检查数据是否存在
if (!d ||!d.length) {
$("#loadingTip").text("没有数据...")
return;
}
// 移除加载提示
$('#loadingTip').remove();
// 设置轨迹数据
pathSimplifierIns.setData(d);
// 为每条轨迹创建路径导航器(移动的点)
d.forEach(function (item, index) {
var navg1 = pathSimplifierIns.createPathNavigator(index, {
loop: true, // 循环播放
speed: 1000, // 移动速度
});
navg1.start(); // 开始动画
});
navg1.start();
})
});
});
});
</script>
</script>
</body>
</html>

@ -1,3 +1,8 @@
{# 显示文章/内容标题 #}
{{ object.title }}
{# 显示作者用户名 #}
{{ object.author.username }}
{# 显示文章/内容正文 #}
{{ object.body }}

@ -1,66 +1,82 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% extends 'share_layout/base.html' %} {# 继承基础布局模板 #}
{% load blog_tags %} {# 加载自定义博客标签 #}
{% block header %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{# 页面头部元信息 #}
<title>{{ SITE_NAME }} |{{ SITE_DESCRIPTION }}</title> {# 页面标题 #}
{# SEO优化元标签 #}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{# Open Graph社交媒体元标签 #}
<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="primary" class="site-content"> {# 主要内容区域 #}
<div id="content" role="main">
{% if query %}
{% if query %} {# 如果有搜索查询 #}
<header class="archive-header">
{% if suggestion %}
{% if suggestion %} {# 如果有搜索建议 #}
<h2 class="archive-title">
已显示<span style="color: red"> “{{ suggestion }}” </span>的搜索结果。&nbsp;&nbsp;
已显示<span style="color: red"> "{{ suggestion }}" </span>的搜索结果。&nbsp;&nbsp;
仍然搜索:<a style="text-transform: none;" href="/search/?q={{ query }}&is_suggest=no">{{ query }}</a> <br>
{# 显示搜索建议,并提供原始搜索链接 #}
</h2>
{% else %}
{% else %} {# 没有搜索建议,直接显示原始搜索词 #}
<h2 class="archive-title">
搜索:<span style="color: red">{{ query }} </span> &nbsp;&nbsp;
</h2>
{% endif %}
</header><!-- .archive-header -->
{% endif %}
{% if query and page.object_list %}
{# 搜索结果展示 #}
{% if query and page.object_list %} {# 如果有查询词且有搜索结果 #}
{# 循环显示搜索结果文章 #}
{% for article in page.object_list %}
{% load_article_detail article.object True user %}
{% load_article_detail article.object True user %} {# 加载文章摘要显示 #}
{% endfor %}
{% if page.has_previous or page.has_next %}
{# 分页导航 #}
{% if page.has_previous or page.has_next %} {# 如果需要分页 #}
<nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">文章导航</h3>
{% if page.has_previous %}
<div class="nav-previous"><a
href="?q={{ query }}&amp;page={{ page.previous_page_number }}"><span
class="meta-nav">&larr;</span> 早期文章</a></div>
<h3 class="assistive-text">文章导航</h3> {# 屏幕阅读器辅助文本 #}
{% if page.has_previous %} {# 上一页链接 #}
<div class="nav-previous">
<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">
<span class="meta-nav">&larr;</span> 早期文章
</a>
</div>
{% endif %}
{% if page.has_next %}
<div class="nav-next"><a href="?q={{ query }}&amp;page={{ page.next_page_number }}">较新文章
<span
class="meta-nav">→</span></a>
{% if page.has_next %} {# 下一页链接 #}
<div class="nav-next">
<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">
较新文章 <span class="meta-nav"></span>
</a>
</div>
{% endif %}
</nav><!-- .navigation -->
{% endif %}
{% else %}
{% else %} {# 没有搜索结果 #}
<header class="archive-header">
<h1 class="archive-title">哎呀,关键字:<span>{{ query }}</span>没有找到结果,要不换个词再试试?</h1>
<h1 class="archive-title">
哎呀,关键字:<span>{{ query }}</span>没有找到结果,要不换个词再试试?
{# 友好的无结果提示 #}
</h1>
</header><!-- .archive-header -->
{% endif %}
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar request.user 'i' %}
{% endblock %}
{# 侧边栏区域 #}
{% load_sidebar request.user 'i' %} {# 加载侧边栏内容 #}
{% endblock %}

@ -1,6 +1,10 @@
<aside id="text-2" class="widget widget_text"><h3 class="widget-title">Google AdSense</h3>
{# Google AdSense广告组件 #}
<aside id="text-2" class="widget widget_text">
{# 小部件标题 #}
<h3 class="widget-title">Google AdSense</h3>
{# 文本小部件内容区域 #}
<div class="textwidget">
{# 显示Google AdSense广告代码 #}
{{ GOOGLE_ADSENSE_CODES }}
</div>
</aside>

@ -120,4 +120,4 @@
{% block footer %}
{% endblock %}
</footer>
</html>
</html>

@ -5,30 +5,37 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<!-- 以上3个meta标签必须放在最前面其他head内容必须放在这些标签之后 -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<meta name="robots" content="noindex">
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
<meta name="robots" content="noindex"> <!-- 禁止搜索引擎索引此页面 -->
<title>{{ SITE_NAME }} |{{ SITE_DESCRIPTION }}</title> <!-- 动态页面标题 -->
<!-- 账户相关CSS -->
<link href="{% static 'account/css/account.css' %}" rel="stylesheet">
{% load compress %}
{% compress css %}
<!-- Bootstrap core CSS -->
<!-- Bootstrap核心CSS -->
<link href="{% static 'assets/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- OAuth登录样式 -->
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<!-- IE10视口hack修复Surface/桌面Windows 8的bug -->
<link href="{% static 'assets/css/ie10-viewport-bug-workaround.css' %}" rel="stylesheet">
<!-- TODC Bootstrap core CSS -->
<!-- TODC Bootstrap核心CSS (可能是定制版的Bootstrap) -->
<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 %}
<!-- IE10兼容性脚本 -->
<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 -->
<!-- HTML5 shim 和 Respond.js 用于IE8支持HTML5元素和媒体查询 -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
@ -36,12 +43,14 @@
</head>
<body>
{% block content %}
{% endblock %}
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<!-- 主要内容块,由子模板填充 -->
{% block content %}
{% endblock %}
<!-- IE10视口hack for Surface/桌面Windows 8 bug -->
</body>
<script type="text/javascript" src="{% static 'blog/js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'account/js/account.js' %}" type="text/javascript"></script>
<!-- JavaScript文件 -->
<script type="text/javascript" src="{% static 'blog/js/jquery-3.6.0.min.js' %}"></script> <!-- jQuery库 -->
<script src="{% static 'account/js/account.js' %}" type="text/javascript"></script> <!-- 账户相关JavaScript -->
</html>

@ -1,56 +1,69 @@
<footer id="colophon" role="contentinfo">
<footer id="colophon" role="contentinfo"> {# 页脚区域role属性用于无障碍访问 #}
<div class="site-info" style="text-align: center">
Copyright&copy;&nbsp;{{ CURRENT_YEAR }}&nbsp;
<a href="/" target="blank">{{ SITE_NAME }}</a>
{# 版权信息 #}
Copyright&copy;&nbsp;{{ CURRENT_YEAR }}&nbsp; {# 当前年份 #}
<a href="/" target="blank">{{ SITE_NAME }}</a> {# 网站名称链接 #}
&nbsp;|&nbsp;
{# 网站地图链接 #}
<a href="/sitemap.xml" title="SiteMap" target="_blank">
SiteMap
</a>
&nbsp;|&nbsp;
{# RSS订阅链接 #}
<a href="/feed" title="RSS Feed" target="_blank">
RSS Feed
</a>
&nbsp;|&nbsp;
{# 友情链接页面 #}
<a href="/links.html" title="友情链接" rel="nofollow" target="_blank">
友情链接
</a>
|&nbsp; Hosting On&nbsp;
{# 主机服务商链接 #}
<a href="https://www.linode.com/?r=b0d38794d05ef8816b357a929106e89b7c6452f9" target="blank" rel="nofollow">Linode</a>
|&nbsp;
{# 百度统计链接 #}
<a href="https://tongji.baidu.com/sc-web/3478620/home/ico?siteId=11261596" target="_blank"
rel="nofollow">百度统计</a>
</div>
<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&nbsp;
{# 百度统计链接 #}
<a href="https://tongji.baidu.com/sc-web/3478620/home/ico?siteId=11261596" target="_blank"
rel="nofollow">百度统计</a>
</div>
<div class="site-info" style="text-align: center">
{# 技术支持信息 #}
Powered by
<a href="https://www.djangoproject.com/" rel="nofollow" target="blank">Django</a> {# Django框架 #}
&nbsp;| {# 作者网站链接 #}
<a href="https://www.lylinux.net" target="blank">lylinux</a>
|
本页面加载耗时:<!!LOAD_TIMES!!>s
{# 页面加载时间统计 #}
本页面加载耗时:<!!LOAD_TIMES!!>s {# 动态替换的加载时间 #}
</div>
{% if BEIAN_CODE %}
{# 备案信息 #}
{% if BEIAN_CODE %} {# 如果有备案号 #}
<div class="site-info" style="text-align: center">
<a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">
<p style=" height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">
{{ BEIAN_CODE }}
{{ BEIAN_CODE }} {# 显示备案号 #}
</p>
</a>
{# 公安备案信息 #}
{% if BEIAN_CODE_GONGAN and SHOW_GONGAN_CODE %}
{{ BEIAN_CODE_GONGAN |safe }}
{% endif %}
</div>
{% endif %}
{% if ANALYTICS_CODE %}
{{ ANALYTICS_CODE| safe }}
{{ BEIAN_CODE_GONGAN |safe }} {# 安全输出统计代码如Google Analytics #}
{% endif %}
{# 全局页脚内容 #}
{% if GLOBAL_FOOTER %}
{{ GLOBAL_FOOTER|safe }}
{{ GLOBAL_FOOTER|safe }} {# 安全输出全局页脚HTML #}
{% endif %}
</footer><!-- #colophon -->

@ -1,29 +1,34 @@
{% load i18n %}
{% load i18n %} {# 加载国际化标签,支持多语言翻译 #}
<nav id="site-navigation" class="main-navigation" role="navigation">
<div class="menu-%e8%8f%9c%e5%8d%95-container">
<ul id="menu-%e8%8f%9c%e5%8d%95" class="nav-menu">
<div class="menu-%e8%8f%9c%e5%8d%95-container"> {# URL编码的"菜单"中文 #}
<ul id="menu-%e8%8f%9c%e5%8d%95" class="nav-menu"> {# 导航菜单列表 #}
{# 首页链接 #}
<li id="menu-item-3498"
class="menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-home menu-item-3498">
<a href="/">{% trans 'index' %}</a></li>
<a href="/">{% trans 'index' %}</a> {# 翻译:"首页" #}
</li>
{# 动态加载分类导航 #}
{% load blog_tags %}
{% query nav_category_list parent_category=None as root_categorys %}
{% for node in root_categorys %}
{% include 'share_layout/nav_node.html' %}
{% query nav_category_list parent_category=None as root_categorys %} {# 查询顶级分类 #}
{% for node in root_categorys %} {# 遍历所有顶级分类 #}
{% include 'share_layout/nav_node.html' %} {# 包含分类节点模板 #}
{% endfor %}
{% if nav_pages %}
{% for node in nav_pages %}
{# 静态页面导航 #}
{% if nav_pages %} {# 如果有导航页面 #}
{% for node in nav_pages %} {# 遍历导航页面 #}
<li id="menu-item-{{ node.pk }}"
class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-{{ node.pk }}">
<a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
<a href="{{ node.get_absolute_url }}">{{ node.title }}</a> {# 页面标题和链接 #}
</li>
{% endfor %}
{% endif %}
{# 文章归档链接 #}
<li class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children">
<a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a>
<a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a> {# 翻译:"文章归档" #}
</li>
</ul>
</div>

@ -1,19 +1,21 @@
{# 导航菜单节点模板 - 支持递归渲染子菜单 #}
<li id="menu-item-{{ node.pk }}"
class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-{{ node.pk }}">
{# 当前分类链接 #}
<a href="{{ node.get_absolute_url }}">{{ node.name }}</a>
{# 加载子分类 #}
{% load blog_tags %}
{% query nav_category_list parent_category=node as child_categorys %}
{% if child_categorys %}
<ul class="sub-menu">
{% for child in child_categorys %}
{% query nav_category_list parent_category=node as child_categorys %} {# 查询当前分类的子分类 #}
{% if child_categorys %} {# 如果存在子分类 #}
<ul class="sub-menu"> {# 子菜单容器 #}
{% for child in child_categorys %} {# 遍历所有子分类 #}
{# 递归调用自身模板渲染子分类 #}
{% with node=child template_name="share_layout/nav_node.html" %}
{% include template_name %}
{% endwith %}
{% endfor %}
</ul>
{% endif %}
</li>
</li>

@ -1 +1,2 @@
print('hello world')
# This is the main Python file

Loading…
Cancel
Save