diff --git a/README.md b/README.md deleted file mode 100644 index e713206..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# DjangoBlog - diff --git a/doc/第五周作业.docx b/doc/第五周作业.docx new file mode 100644 index 0000000..41fe40e Binary files /dev/null and b/doc/第五周作业.docx differ diff --git a/src/.coveragerc b/src/.coveragerc deleted file mode 100644 index 9757484..0000000 --- a/src/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -source = . -include = *.py -omit = - *migrations* - *tests* - *.html - *whoosh_cn_backend* - *settings.py* - *venv* diff --git a/src/.dockerignore b/src/.dockerignore index 2818c38..bd68a58 100644 --- a/src/.dockerignore +++ b/src/.dockerignore @@ -8,4 +8,5 @@ settings_production.py *.md docs/ logs/ -static/ \ No newline at end of file +static/ +.github/ diff --git a/src/.gitattributes b/src/.gitattributes deleted file mode 100644 index fd52ece..0000000 --- a/src/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -blog/static/* linguist-vendored -*.js linguist-vendored -*.css linguist-vendored -* text=auto -*.sh text eol=lf -*.conf text eol=lf \ No newline at end of file diff --git a/src/.github/ISSUE_TEMPLATE.md b/src/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2b5b7aa..0000000 --- a/src/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ - - -**我确定我已经查看了** (标注`[ ]`为`[x]`) - -- [ ] [DjangoBlog的readme](https://github.com/liangliangyy/DjangoBlog/blob/master/README.md) -- [ ] [配置说明](https://github.com/liangliangyy/DjangoBlog/blob/master/bin/config.md) -- [ ] [其他 Issues](https://github.com/liangliangyy/DjangoBlog/issues) - ----- - -**我要申请** (标注`[ ]`为`[x]`) - -- [ ] BUG 反馈 -- [ ] 添加新的特性或者功能 -- [ ] 请求技术支持 diff --git a/src/.github/workflows/codeql-analysis.yml b/src/.github/workflows/codeql-analysis.yml index 6b76522..52775e0 100644 --- a/src/.github/workflows/codeql-analysis.yml +++ b/src/.github/workflows/codeql-analysis.yml @@ -38,10 +38,12 @@ jobs: uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - + uses: github/codeql-action/init@v3 + with: + languages: python + - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file + uses: github/codeql-action/analyze@v3 \ No newline at end of file diff --git a/src/.github/workflows/deploy-master.yml b/src/.github/workflows/deploy-master.yml new file mode 100644 index 0000000..c07a326 --- /dev/null +++ b/src/.github/workflows/deploy-master.yml @@ -0,0 +1,176 @@ +name: 自动部署到生产环境 + +on: + workflow_run: + workflows: ["Django CI"] + types: + - completed + branches: + - master + workflow_dispatch: + inputs: + environment: + description: '部署环境' + required: true + default: 'production' + type: choice + options: + - production + - staging + image_tag: + description: '镜像标签 (默认: latest)' + required: false + default: 'latest' + type: string + skip_tests: + description: '跳过测试直接部署' + required: false + default: false + type: boolean + +env: + REGISTRY: registry.cn-shenzhen.aliyuncs.com + IMAGE_NAME: liangliangyy/djangoblog + NAMESPACE: djangoblog + +jobs: + deploy: + name: 构建镜像并部署到生产环境 + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置部署参数 + id: deploy-params + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "trigger_type=手动触发" >> $GITHUB_OUTPUT + echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT + echo "image_tag=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT + echo "skip_tests=${{ github.event.inputs.skip_tests }}" >> $GITHUB_OUTPUT + else + echo "trigger_type=CI自动触发" >> $GITHUB_OUTPUT + echo "environment=production" >> $GITHUB_OUTPUT + echo "image_tag=latest" >> $GITHUB_OUTPUT + echo "skip_tests=false" >> $GITHUB_OUTPUT + fi + + - name: 显示部署信息 + run: | + echo "🚀 部署信息:" + echo " 触发方式: ${{ steps.deploy-params.outputs.trigger_type }}" + echo " 部署环境: ${{ steps.deploy-params.outputs.environment }}" + echo " 镜像标签: ${{ steps.deploy-params.outputs.image_tag }}" + echo " 跳过测试: ${{ steps.deploy-params.outputs.skip_tests }}" + + - name: 设置Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: 登录私有镜像仓库 + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: 提取镜像元数据 + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,prefix={{branch}}- + type=raw,value=${{ steps.deploy-params.outputs.image_tag }} + + - name: 构建并推送Docker镜像 + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64 + + - name: 部署到生产服务器 + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PRODUCTION_HOST }} + username: ${{ secrets.PRODUCTION_USER }} + key: ${{ secrets.PRODUCTION_SSH_KEY }} + port: ${{ secrets.PRODUCTION_PORT || 22 }} + script: | + echo "🚀 开始部署 DjangoBlog..." + + # 检查kubectl是否可用 + if ! command -v kubectl &> /dev/null; then + echo "❌ 错误: kubectl 未安装或不在PATH中" + exit 1 + fi + + # 检查命名空间是否存在 + if ! kubectl get namespace ${{ env.NAMESPACE }} &> /dev/null; then + echo "❌ 错误: 命名空间 ${{ env.NAMESPACE }} 不存在" + exit 1 + fi + + # 更新deployment镜像 + echo "📦 更新deployment镜像为: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }}" + kubectl set image deployment/djangoblog \ + djangoblog=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.deploy-params.outputs.image_tag }} \ + -n ${{ env.NAMESPACE }} + + # 重启deployment + echo "🔄 重启deployment..." + kubectl -n ${{ env.NAMESPACE }} rollout restart deployment djangoblog + + # 等待deployment完成 + echo "⏳ 等待deployment完成..." + kubectl rollout status deployment/djangoblog -n ${{ env.NAMESPACE }} --timeout=300s + + # 检查deployment状态 + echo "✅ 检查deployment状态..." + kubectl get deployment djangoblog -n ${{ env.NAMESPACE }} + kubectl get pods -l app=djangoblog -n ${{ env.NAMESPACE }} + + echo "🎉 部署完成!" + + - name: 发送部署通知 + if: always() + run: | + # 设置通知内容 + if [ "${{ job.status }}" = "success" ]; then + TITLE="✅ DjangoBlog部署成功" + STATUS="成功" + else + TITLE="❌ DjangoBlog部署失败" + STATUS="失败" + fi + + MESSAGE="部署状态: ${STATUS} + 触发方式: ${{ steps.deploy-params.outputs.trigger_type }} + 部署环境: ${{ steps.deploy-params.outputs.environment }} + 镜像标签: ${{ steps.deploy-params.outputs.image_tag }} + 提交者: ${{ github.actor }} + 时间: $(date '+%Y-%m-%d %H:%M:%S') + + 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # 发送Server酱通知 + if [ -n "${{ secrets.SERVERCHAN_KEY }}" ]; then + echo "{\"title\": \"${TITLE}\", \"desp\": \"${MESSAGE}\"}" > /tmp/serverchan.json + + curl --location "https://sctapi.ftqq.com/${{ secrets.SERVERCHAN_KEY }}.send" \ + --header "Content-Type: application/json" \ + --data @/tmp/serverchan.json \ + --silent > /dev/null + + rm -f /tmp/serverchan.json + echo "📱 部署通知已发送" + fi \ No newline at end of file diff --git a/src/.github/workflows/django.yml b/src/.github/workflows/django.yml index bf23242..75060f4 100644 --- a/src/.github/workflows/django.yml +++ b/src/.github/workflows/django.yml @@ -21,15 +21,63 @@ on: - '**/*.yml' jobs: - build-normal: + test: runs-on: ubuntu-latest strategy: - max-parallel: 4 + fail-fast: false matrix: +<<<<<<< HEAD python-version: [ "3.8", "3.9","3.10","3.11" ] +======= + include: + # 标准测试 - Python 3.10 + - python-version: "3.10" + test-type: "standard" + database: "mysql" + elasticsearch: false + coverage: false + + # 标准测试 - Python 3.11 + - python-version: "3.11" + test-type: "standard" + database: "mysql" + elasticsearch: false + coverage: false + + # 完整测试 - 包含ES和覆盖率 + - python-version: "3.11" + test-type: "full" + database: "mysql" + elasticsearch: true + coverage: true + + # Docker构建测试 + - python-version: "3.11" + test-type: "docker" + database: "none" + elasticsearch: false + coverage: false +>>>>>>> develop + name: Test (${{ matrix.test-type }}, Python ${{ matrix.python-version }}) + steps: - - name: Start MySQL + - name: Checkout代码 + uses: actions/checkout@v4 + + - name: 设置测试信息 + id: test-info + run: | + echo "test_name=${{ matrix.test-type }}-py${{ matrix.python-version }}" >> $GITHUB_OUTPUT + if [ "${{ matrix.test-type }}" = "docker" ]; then + echo "skip_python_setup=true" >> $GITHUB_OUTPUT + else + echo "skip_python_setup=false" >> $GITHUB_OUTPUT + fi + + # MySQL数据库设置 (只有需要数据库的测试才执行) + - name: 启动MySQL数据库 + if: matrix.database == 'mysql' uses: samin/mysql-action@v1.3 with: host port: 3306 @@ -41,6 +89,7 @@ jobs: mysql database: djangoblog mysql user: root mysql password: root +<<<<<<< HEAD - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -83,11 +132,18 @@ jobs: mysql password: root - name: Configure sysctl limits +======= + + # Elasticsearch设置 (只有完整测试才执行) + - name: 配置系统参数 (ES) + if: matrix.elasticsearch == true +>>>>>>> develop run: | sudo swapoff -a sudo sysctl -w vm.swappiness=1 sudo sysctl -w fs.file-max=262144 sudo sysctl -w vm.max_map_count=262144 +<<<<<<< HEAD - uses: miyataka/elasticsearch-github-actions@1 with: @@ -97,40 +153,282 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 +======= + + - name: 启动Elasticsearch + if: matrix.elasticsearch == true + uses: miyataka/elasticsearch-github-actions@1 + with: + stack-version: '7.12.1' + plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip' + + # Python环境设置 (Docker测试跳过) + - name: 设置Python ${{ matrix.python-version }} + if: steps.test-info.outputs.skip_python_setup == 'false' + uses: actions/setup-python@v5 +>>>>>>> develop with: python-version: ${{ matrix.python-version }} cache: 'pip' - - name: Install Dependencies + cache-dependency-path: 'requirements.txt' + + # 多层缓存策略优化 + - name: 缓存Python依赖 + if: steps.test-info.outputs.skip_python_setup == 'false' + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + .pytest_cache + key: ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}- + ${{ runner.os }}-python-${{ matrix.python-version }}- + ${{ runner.os }}-python- + + # Django缓存优化 (测试数据库等) + - name: 缓存Django资源 + if: matrix.test-type != 'docker' + uses: actions/cache@v4 + with: + path: | + .coverage* + htmlcov/ + .django_cache/ + key: ${{ runner.os }}-django-${{ matrix.test-type }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-django-${{ matrix.test-type }}- + ${{ runner.os }}-django- + + - name: 安装Python依赖 + if: steps.test-info.outputs.skip_python_setup == 'false' run: | - python -m pip install --upgrade pip + echo "📦 安装Python依赖 (Python ${{ matrix.python-version }})" + python -m pip install --upgrade pip setuptools wheel + + # 安装基础依赖 pip install -r requirements.txt - - name: Run Tests + + # 根据测试类型安装额外依赖 + if [ "${{ matrix.coverage }}" = "true" ]; then + echo "📊 安装覆盖率工具" + pip install coverage[toml] + fi + + # 验证关键依赖 + echo "🔍 验证关键依赖安装" + python -c "import django; print(f'Django version: {django.get_version()}')" + python -c "import MySQLdb; print('MySQL client: OK')" || python -c "import pymysql; print('PyMySQL client: OK')" + + if [ "${{ matrix.elasticsearch }}" = "true" ]; then + python -c "import elasticsearch; print('Elasticsearch client: OK')" + fi + + # Django环境准备 + - name: 准备Django环境 + if: matrix.test-type != 'docker' env: DJANGO_MYSQL_PASSWORD: root DJANGO_MYSQL_HOST: 127.0.0.1 - DJANGO_ELASTICSEARCH_HOST: 127.0.0.1:9200 + DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} run: | - python manage.py makemigrations - python manage.py migrate - coverage run manage.py test - coverage xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - - docker: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Build and push - uses: docker/build-push-action@v3 + echo "🔧 准备Django测试环境" + + # 等待数据库就绪 + echo "⏳ 等待MySQL数据库启动..." + for i in {1..30}; do + if python -c "import MySQLdb; MySQLdb.connect(host='127.0.0.1', user='root', passwd='root', db='djangoblog')" 2>/dev/null; then + echo "✅ MySQL数据库连接成功" + break + fi + echo "🔄 等待数据库启动... ($i/30)" + sleep 2 + done + + # 等待Elasticsearch就绪 (如果启用) + if [ "${{ matrix.elasticsearch }}" = "true" ]; then + echo "⏳ 等待Elasticsearch启动..." + for i in {1..30}; do + if curl -s http://127.0.0.1:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; then + echo "✅ Elasticsearch连接成功" + break + fi + echo "🔄 等待Elasticsearch启动... ($i/30)" + sleep 2 + done + fi + + # Django测试执行 + - name: 执行数据库迁移 + if: matrix.test-type != 'docker' + env: + DJANGO_MYSQL_PASSWORD: root + DJANGO_MYSQL_HOST: 127.0.0.1 + DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} + run: | + echo "🗄️ 执行数据库迁移" + + # 检查迁移文件 + echo "📋 检查待应用的迁移..." + python manage.py showmigrations + + # 检查是否有未创建的迁移 + python manage.py makemigrations --check --verbosity 2 + + # 执行迁移 + python manage.py migrate --verbosity 2 + + echo "✅ 数据库迁移完成" + + - name: 运行Django测试 + if: matrix.test-type != 'docker' + env: + DJANGO_MYSQL_PASSWORD: root + DJANGO_MYSQL_HOST: 127.0.0.1 + DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }} + run: | + echo "🧪 开始执行 ${{ matrix.test-type }} 测试 (Python ${{ matrix.python-version }})" + + # 显示Django配置信息 + python manage.py diffsettings | head -20 + + # 运行测试 + if [ "${{ matrix.coverage }}" = "true" ]; then + echo "📊 运行测试并生成覆盖率报告" + coverage run --source='.' --omit='*/venv/*,*/migrations/*,*/tests/*,manage.py' manage.py test --verbosity=2 + + echo "📈 生成覆盖率报告" + coverage xml + coverage report --show-missing + coverage html + + echo "📋 覆盖率统计:" + coverage report | tail -1 + else + echo "🧪 运行标准测试" + python manage.py test --verbosity=2 --failfast + fi + + echo "✅ 测试执行完成" + + # 覆盖率报告上传 (只有完整测试才执行) + - name: 上传覆盖率到Codecov + if: matrix.coverage == true && success() + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml + flags: unittests + name: codecov-${{ steps.test-info.outputs.test_name }} + fail_ci_if_error: false + verbose: true + + - name: 上传覆盖率到Codecov (备用) + if: matrix.coverage == true && failure() + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + flags: unittests + name: codecov-${{ steps.test-info.outputs.test_name }}-fallback + fail_ci_if_error: false + verbose: true + + # Docker构建测试 + - name: 设置QEMU + if: matrix.test-type == 'docker' + uses: docker/setup-qemu-action@v3 + + - name: 设置Docker Buildx + if: matrix.test-type == 'docker' + uses: docker/setup-buildx-action@v3 + + - name: Docker构建测试 + if: matrix.test-type == 'docker' + uses: docker/build-push-action@v5 with: context: . push: false - tags: djangoblog/djangoblog:dev + tags: djangoblog/djangoblog:test-${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # 收集测试工件 (失败时收集调试信息) + - name: 收集测试工件 + if: failure() && matrix.test-type != 'docker' + run: | + echo "🔍 收集测试失败的调试信息" + + # 收集Django日志 + if [ -d "logs" ]; then + echo "📄 Django日志文件:" + ls -la logs/ + if [ -f "logs/djangoblog.log" ]; then + echo "🔍 最新日志内容:" + tail -100 logs/djangoblog.log + fi + fi + + # 显示数据库状态 + echo "🗄️ 数据库连接状态:" + python -c " + try: + from django.db import connection + cursor = connection.cursor() + cursor.execute('SELECT VERSION()') + print(f'MySQL版本: {cursor.fetchone()[0]}') + cursor.execute('SHOW TABLES') + tables = cursor.fetchall() + print(f'数据库表数量: {len(tables)}') + except Exception as e: + print(f'数据库连接错误: {e}') + " || true + + # Elasticsearch状态 (如果启用) + if [ "${{ matrix.elasticsearch }}" = "true" ]; then + echo "🔍 Elasticsearch状态:" + curl -s http://127.0.0.1:9200/_cluster/health?pretty || true + fi + + # 上传测试工件 + - name: 上传覆盖率HTML报告 + if: matrix.coverage == true && always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report-${{ steps.test-info.outputs.test_name }} + path: htmlcov/ + retention-days: 30 + + # 性能统计 + - name: 测试性能统计 + if: always() && matrix.test-type != 'docker' + run: | + echo "⚡ 测试性能统计:" + echo " 开始时间: $(date -d '@${{ job.started_at }}' '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '未知')" + echo " 当前时间: $(date '+%Y-%m-%d %H:%M:%S')" + + # 系统资源使用情况 + echo "💻 系统资源:" + echo " CPU使用: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%" + echo " 内存使用: $(free -h | awk '/^Mem:/ {printf "%.1f%%", $3/$2 * 100}')" + echo " 磁盘使用: $(df -h / | awk 'NR==2{printf "%s", $5}')" + + # 测试结果汇总 + - name: 测试完成总结 + if: always() + run: | + echo "📋 ============ 测试执行总结 ============" + echo " 🏷️ 测试类型: ${{ matrix.test-type }}" + echo " 🐍 Python版本: ${{ matrix.python-version }}" + echo " 🗄️ 数据库: ${{ matrix.database }}" + echo " 🔍 Elasticsearch: ${{ matrix.elasticsearch }}" + echo " 📊 覆盖率: ${{ matrix.coverage }}" + echo " ⚡ 状态: ${{ job.status }}" + echo " 📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "============================================" + + # 根据测试结果显示不同消息 + if [ "${{ job.status }}" = "success" ]; then + echo "🎉 测试执行成功!" + else + echo "❌ 测试执行失败,请检查上面的日志" + fi diff --git a/src/.github/workflows/docker.yml b/src/.github/workflows/docker.yml index a312e2f..904fef5 100644 --- a/src/.github/workflows/docker.yml +++ b/src/.github/workflows/docker.yml @@ -22,19 +22,19 @@ jobs: run: | echo "DOCKER_TAG=latest" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . push: true diff --git a/src/.github/workflows/publish-release.yml b/src/.github/workflows/publish-release.yml deleted file mode 100644 index 5eb0853..0000000 --- a/src/.github/workflows/publish-release.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: publish release - -on: - release: - types: [ published ] - -jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - images: name/app - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v3 - with: - context: . - push: true - platforms: | - linux/amd64 - linux/arm64 - linux/arm/v7 - linux/arm/v6 - linux/386 - tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{ github.event.release.tag_name }} diff --git a/src/.gitignore b/src/.gitignore index 1c1fcbf..855eaa4 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -62,7 +62,10 @@ target/ # http://www.jetbrains.com/pycharm/webhelp/project.html .idea .iml +<<<<<<< HEAD #static/ +======= +>>>>>>> develop # virtualenv venv/ diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json deleted file mode 100644 index f5f50ec..0000000 --- a/src/.vscode/launch.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 - - "version": "0.2.0", - "configurations": [ - { - "name": "Django: Run Server (Debug)", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/manage.py", - "args": [ - "runserver", - "--noreload", // 禁用自动重载以确保断点命中 - "127.0.0.1:8000" // 指定主机和端口 - ], - "django": true, // 启用Django特定支持 - "console": "integratedTerminal" // 在集成终端中输出 - } - ] -} diff --git a/src/accounts/__init__.py b/src/accounts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/accounts/__pycache__/__init__.cpython-311.pyc b/src/accounts/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..fde4d05 Binary files /dev/null and b/src/accounts/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/admin.cpython-311.pyc b/src/accounts/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..796783e Binary files /dev/null and b/src/accounts/__pycache__/admin.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/apps.cpython-311.pyc b/src/accounts/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..a59634c Binary files /dev/null and b/src/accounts/__pycache__/apps.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/forms.cpython-311.pyc b/src/accounts/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..22b4e8e Binary files /dev/null and b/src/accounts/__pycache__/forms.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/models.cpython-311.pyc b/src/accounts/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..20b237e Binary files /dev/null and b/src/accounts/__pycache__/models.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/urls.cpython-311.pyc b/src/accounts/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..66af091 Binary files /dev/null and b/src/accounts/__pycache__/urls.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/user_login_backend.cpython-311.pyc b/src/accounts/__pycache__/user_login_backend.cpython-311.pyc new file mode 100644 index 0000000..798bfd8 Binary files /dev/null and b/src/accounts/__pycache__/user_login_backend.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/utils.cpython-311.pyc b/src/accounts/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..9fb3583 Binary files /dev/null and b/src/accounts/__pycache__/utils.cpython-311.pyc differ diff --git a/src/accounts/__pycache__/views.cpython-311.pyc b/src/accounts/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..92a8bb5 Binary files /dev/null and b/src/accounts/__pycache__/views.cpython-311.pyc differ diff --git a/src/accounts/admin.py b/src/accounts/admin.py index 32e483c..0434b8a 100644 --- a/src/accounts/admin.py +++ b/src/accounts/admin.py @@ -4,7 +4,6 @@ from django.contrib.auth.forms import UserChangeForm from django.contrib.auth.forms import UsernameField from django.utils.translation import gettext_lazy as _ -# Register your models here. from .models import BlogUser @@ -17,7 +16,6 @@ class BlogUserCreationForm(forms.ModelForm): fields = ('email',) def clean_password2(self): - # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: @@ -25,7 +23,6 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): - # Save the provided password in hashed format user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) if commit: @@ -47,6 +44,24 @@ class BlogUserChangeForm(UserChangeForm): class BlogUserAdmin(UserAdmin): form = BlogUserChangeForm add_form = BlogUserCreationForm + + # 添加这些关键定义 + fieldsets = ( + (None, {'fields': ('username', 'password')}), + (_('Personal info'), {'fields': ('nickname', 'email', 'first_name', 'last_name')}), + (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', + 'groups', 'user_permissions')}), + (_('Important dates'), {'fields': ('last_login', 'date_joined')}), + (_('Source'), {'fields': ('source',)}), + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('username', 'email', 'password1', 'password2'), + }), + ) + list_display = ( 'id', 'nickname', @@ -57,3 +72,4 @@ class BlogUserAdmin(UserAdmin): 'source') list_display_links = ('id', 'username') ordering = ('-id',) + search_fields = ('username', 'nickname', 'email') \ No newline at end of file diff --git a/src/accounts/apps.py b/src/accounts/apps.py deleted file mode 100644 index 9b3fc5a..0000000 --- a/src/accounts/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'accounts' diff --git a/src/accounts/forms.py b/src/accounts/forms.py deleted file mode 100644 index fce4137..0000000 --- a/src/accounts/forms.py +++ /dev/null @@ -1,117 +0,0 @@ -from django import forms -from django.contrib.auth import get_user_model, password_validation -from django.contrib.auth.forms import AuthenticationForm, UserCreationForm -from django.core.exceptions import ValidationError -from django.forms import widgets -from django.utils.translation import gettext_lazy as _ -from . import utils -from .models import BlogUser - - -class LoginForm(AuthenticationForm): - def __init__(self, *args, **kwargs): - super(LoginForm, self).__init__(*args, **kwargs) - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['password'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) - - -class RegisterForm(UserCreationForm): - def __init__(self, *args, **kwargs): - super(RegisterForm, self).__init__(*args, **kwargs) - - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['email'].widget = widgets.EmailInput( - attrs={'placeholder': "email", "class": "form-control"}) - self.fields['password1'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) - self.fields['password2'].widget = widgets.PasswordInput( - attrs={'placeholder': "repeat password", "class": "form-control"}) - - def clean_email(self): - email = self.cleaned_data['email'] - if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) - return email - - class Meta: - model = get_user_model() - fields = ("username", "email") - - -class ForgetPasswordForm(forms.Form): - new_password1 = forms.CharField( - label=_("New password"), - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("New password") - } - ), - ) - - new_password2 = forms.CharField( - label="确认密码", - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("Confirm password") - } - ), - ) - - email = forms.EmailField( - label='邮箱', - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Email") - } - ), - ) - - code = forms.CharField( - label=_('Code'), - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Code") - } - ), - ) - - def clean_new_password2(self): - password1 = self.data.get("new_password1") - password2 = self.data.get("new_password2") - if password1 and password2 and password1 != password2: - raise ValidationError(_("passwords do not match")) - password_validation.validate_password(password2) - - return password2 - - def clean_email(self): - user_email = self.cleaned_data.get("email") - if not BlogUser.objects.filter( - email=user_email - ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 - raise ValidationError(_("email does not exist")) - return user_email - - def clean_code(self): - code = self.cleaned_data.get("code") - error = utils.verify( - email=self.cleaned_data.get("email"), - code=code, - ) - if error: - raise ValidationError(error) - return code - - -class ForgetPasswordCodeForm(forms.Form): - email = forms.EmailField( - label=_('Email'), - ) diff --git a/src/accounts/migrations/0001_initial.py b/src/accounts/migrations/0001_initial.py deleted file mode 100644 index d2fbcab..0000000 --- a/src/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='BlogUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': '用户', - 'verbose_name_plural': '用户', - 'ordering': ['-id'], - 'get_latest_by': 'id', - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ] diff --git a/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py deleted file mode 100644 index 1a9f509..0000000 --- a/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='bloguser', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, - ), - migrations.RemoveField( - model_name='bloguser', - name='created_time', - ), - migrations.RemoveField( - model_name='bloguser', - name='last_mod_time', - ), - migrations.AddField( - model_name='bloguser', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='bloguser', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='bloguser', - name='nickname', - field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), - ), - migrations.AlterField( - model_name='bloguser', - name='source', - field=models.CharField(blank=True, max_length=100, verbose_name='create source'), - ), - ] diff --git a/src/accounts/migrations/__init__.py b/src/accounts/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..6c26aa5 Binary files /dev/null and b/src/accounts/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-311.pyc b/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-311.pyc new file mode 100644 index 0000000..8aec6da Binary files /dev/null and b/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-311.pyc differ diff --git a/src/accounts/migrations/__pycache__/__init__.cpython-311.pyc b/src/accounts/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5e23d42 Binary files /dev/null and b/src/accounts/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/accounts/models.py b/src/accounts/models.py deleted file mode 100644 index 3baddbb..0000000 --- a/src/accounts/models.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.contrib.auth.models import AbstractUser -from django.db import models -from django.urls import reverse -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ -from djangoblog.utils import get_current_site - - -# Create your models here. - -class BlogUser(AbstractUser): - nickname = models.CharField(_('nick name'), max_length=100, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - source = models.CharField(_('create source'), max_length=100, blank=True) - - def get_absolute_url(self): - return reverse( - 'blog:author_detail', kwargs={ - 'author_name': self.username}) - - def __str__(self): - return self.email - - def get_full_url(self): - site = get_current_site().domain - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) - return url - - class Meta: - ordering = ['-id'] - verbose_name = _('user') - verbose_name_plural = verbose_name - get_latest_by = 'id' diff --git a/src/accounts/templatetags/__init__.py b/src/accounts/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/accounts/templatetags/__pycache__/__init__.cpython-311.pyc b/src/accounts/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..cbbaeae Binary files /dev/null and b/src/accounts/templatetags/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/accounts/urls.py b/src/accounts/urls.py deleted file mode 100644 index 107a801..0000000 --- a/src/accounts/urls.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.urls import path -from django.urls import re_path - -from . import views -from .forms import LoginForm - -app_name = "accounts" - -urlpatterns = [re_path(r'^login/$', - views.LoginView.as_view(success_url='/'), - name='login', - kwargs={'authentication_form': LoginForm}), - re_path(r'^register/$', - views.RegisterView.as_view(success_url="/"), - name='register'), - re_path(r'^logout/$', - views.LogoutView.as_view(), - name='logout'), - path(r'account/result.html', - views.account_result, - name='result'), - re_path(r'^forget_password/$', - views.ForgetPasswordView.as_view(), - name='forget_password'), - re_path(r'^forget_password_code/$', - views.ForgetPasswordEmailCode.as_view(), - name='forget_password_code'), - ] diff --git a/src/accounts/user_login_backend.py b/src/accounts/user_login_backend.py deleted file mode 100644 index 73cdca1..0000000 --- a/src/accounts/user_login_backend.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend - - -class EmailOrUsernameModelBackend(ModelBackend): - """ - 允许使用用户名或邮箱登录 - """ - - def authenticate(self, request, username=None, password=None, **kwargs): - if '@' in username: - kwargs = {'email': username} - else: - kwargs = {'username': username} - try: - user = get_user_model().objects.get(**kwargs) - if user.check_password(password): - return user - except get_user_model().DoesNotExist: - return None - - def get_user(self, username): - try: - return get_user_model().objects.get(pk=username) - except get_user_model().DoesNotExist: - return None diff --git a/src/accounts/utils.py b/src/accounts/utils.py deleted file mode 100644 index 4b94bdf..0000000 --- a/src/accounts/utils.py +++ /dev/null @@ -1,49 +0,0 @@ -import typing -from datetime import timedelta - -from django.core.cache import cache -from django.utils.translation import gettext -from django.utils.translation import gettext_lazy as _ - -from djangoblog.utils import send_email - -_code_ttl = timedelta(minutes=5) - - -def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 - Args: - to_mail: 接受邮箱 - subject: 邮件主题 - code: 验证码 - """ - html_content = _( - "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " - "properly") % {'code': code} - send_email([to_mail], subject, html_content) - - -def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 - Args: - email: 请求邮箱 - code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 - """ - cache_code = get_code(email) - if cache_code != code: - return gettext("Verification code error") - - -def set_code(email: str, code: str): - """设置code""" - cache.set(email, code, _code_ttl.seconds) - - -def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) diff --git a/src/accounts/views.py b/src/accounts/views.py deleted file mode 100644 index ae67aec..0000000 --- a/src/accounts/views.py +++ /dev/null @@ -1,204 +0,0 @@ -import logging -from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from django.contrib import auth -from django.contrib.auth import REDIRECT_FIELD_NAME -from django.contrib.auth import get_user_model -from django.contrib.auth import logout -from django.contrib.auth.forms import AuthenticationForm -from django.contrib.auth.hashers import make_password -from django.http import HttpResponseRedirect, HttpResponseForbidden -from django.http.request import HttpRequest -from django.http.response import HttpResponse -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -from django.urls import reverse -from django.utils.decorators import method_decorator -from django.utils.http import url_has_allowed_host_and_scheme -from django.views import View -from django.views.decorators.cache import never_cache -from django.views.decorators.csrf import csrf_protect -from django.views.decorators.debug import sensitive_post_parameters -from django.views.generic import FormView, RedirectView - -from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache -from . import utils -from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm -from .models import BlogUser - -logger = logging.getLogger(__name__) - - -# Create your views here. - -class RegisterView(FormView): - form_class = RegisterForm - template_name = 'account/registration_form.html' - - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - return super(RegisterView, self).dispatch(*args, **kwargs) - - def form_valid(self, form): - if form.is_valid(): - user = form.save(False) - user.is_active = False - user.source = 'Register' - user.save(True) - site = get_current_site().domain - sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - - if settings.DEBUG: - site = '127.0.0.1:8000' - path = reverse('account:result') - url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( - site=site, path=path, id=user.id, sign=sign) - - content = """ -

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

- - {url} - - 再次感谢您! -
- 如果上面链接无法打开,请将此链接复制至浏览器。 - {url} - """.format(url=url) - send_email( - emailto=[ - user.email, - ], - title='验证您的电子邮箱', - content=content) - - url = reverse('accounts:result') + \ - '?type=register&id=' + str(user.id) - return HttpResponseRedirect(url) - else: - return self.render_to_response({ - 'form': form - }) - - -class LogoutView(RedirectView): - url = '/login/' - - @method_decorator(never_cache) - def dispatch(self, request, *args, **kwargs): - return super(LogoutView, self).dispatch(request, *args, **kwargs) - - def get(self, request, *args, **kwargs): - logout(request) - delete_sidebar_cache() - return super(LogoutView, self).get(request, *args, **kwargs) - - -class LoginView(FormView): - form_class = LoginForm - template_name = 'account/login.html' - success_url = '/' - redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 - - @method_decorator(sensitive_post_parameters('password')) - @method_decorator(csrf_protect) - @method_decorator(never_cache) - def dispatch(self, request, *args, **kwargs): - - return super(LoginView, self).dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - redirect_to = self.request.GET.get(self.redirect_field_name) - if redirect_to is None: - redirect_to = '/' - kwargs['redirect_to'] = redirect_to - - return super(LoginView, self).get_context_data(**kwargs) - - def form_valid(self, form): - form = AuthenticationForm(data=self.request.POST, request=self.request) - - if form.is_valid(): - delete_sidebar_cache() - logger.info(self.redirect_field_name) - - auth.login(self.request, form.get_user()) - if self.request.POST.get("remember"): - self.request.session.set_expiry(self.login_ttl) - return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') - else: - return self.render_to_response({ - 'form': form - }) - - def get_success_url(self): - - redirect_to = self.request.POST.get(self.redirect_field_name) - if not url_has_allowed_host_and_scheme( - url=redirect_to, allowed_hosts=[ - self.request.get_host()]): - redirect_to = self.success_url - return redirect_to - - -def account_result(request): - type = request.GET.get('type') - id = request.GET.get('id') - - user = get_object_or_404(get_user_model(), id=id) - logger.info(type) - if user.is_active: - return HttpResponseRedirect('/') - if type and type in ['register', 'validation']: - if type == 'register': - content = ''' - 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 - ''' - title = '注册成功' - else: - c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - sign = request.GET.get('sign') - if sign != c_sign: - return HttpResponseForbidden() - user.is_active = True - user.save() - content = ''' - 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 - ''' - title = '验证成功' - return render(request, 'account/result.html', { - 'title': title, - 'content': content - }) - else: - return HttpResponseRedirect('/') - - -class ForgetPasswordView(FormView): - form_class = ForgetPasswordForm - template_name = 'account/forget_password.html' - - def form_valid(self, form): - if form.is_valid(): - blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() - blog_user.password = make_password(form.cleaned_data["new_password2"]) - blog_user.save() - return HttpResponseRedirect('/login/') - else: - return self.render_to_response({'form': form}) - - -class ForgetPasswordEmailCode(View): - - def post(self, request: HttpRequest): - form = ForgetPasswordCodeForm(request.POST) - if not form.is_valid(): - return HttpResponse("错误的邮箱") - to_email = form.cleaned_data["email"] - - code = generate_code() - utils.send_verify_email(to_email, code) - utils.set_code(to_email, code) - - return HttpResponse("ok") diff --git a/src/blog/__init__.py b/src/blog/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/blog/__pycache__/__init__.cpython-311.pyc b/src/blog/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7427588 Binary files /dev/null and b/src/blog/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/blog/__pycache__/admin.cpython-311.pyc b/src/blog/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..118e2b0 Binary files /dev/null and b/src/blog/__pycache__/admin.cpython-311.pyc differ diff --git a/src/blog/__pycache__/apps.cpython-311.pyc b/src/blog/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..4f47c02 Binary files /dev/null and b/src/blog/__pycache__/apps.cpython-311.pyc differ diff --git a/src/blog/__pycache__/context_processors.cpython-311.pyc b/src/blog/__pycache__/context_processors.cpython-311.pyc new file mode 100644 index 0000000..520645d Binary files /dev/null and b/src/blog/__pycache__/context_processors.cpython-311.pyc differ diff --git a/src/blog/__pycache__/documents.cpython-311.pyc b/src/blog/__pycache__/documents.cpython-311.pyc new file mode 100644 index 0000000..428ead8 Binary files /dev/null and b/src/blog/__pycache__/documents.cpython-311.pyc differ diff --git a/src/blog/__pycache__/middleware.cpython-311.pyc b/src/blog/__pycache__/middleware.cpython-311.pyc new file mode 100644 index 0000000..07d696b Binary files /dev/null and b/src/blog/__pycache__/middleware.cpython-311.pyc differ diff --git a/src/blog/__pycache__/models.cpython-311.pyc b/src/blog/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..a707f38 Binary files /dev/null and b/src/blog/__pycache__/models.cpython-311.pyc differ diff --git a/src/blog/__pycache__/urls.cpython-311.pyc b/src/blog/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..fa75302 Binary files /dev/null and b/src/blog/__pycache__/urls.cpython-311.pyc differ diff --git a/src/blog/__pycache__/views.cpython-311.pyc b/src/blog/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..53309ba Binary files /dev/null and b/src/blog/__pycache__/views.cpython-311.pyc differ diff --git a/src/blog/admin.py b/src/blog/admin.py index 5e1e035..09010f9 100644 --- a/src/blog/admin.py +++ b/src/blog/admin.py @@ -6,7 +6,7 @@ from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ # Register your models here. -from .models import Article +from .models import Article, Category, Tag, Links, SideBar, BlogSettings class ArticleListFilter(admin.SimpleListFilter): @@ -71,7 +71,12 @@ class ArticlelAdmin(admin.ModelAdmin): 'type', 'article_order') list_display_links = ('id', 'title') +<<<<<<< HEAD list_filter = (ArticleListFilter, 'status', 'type', 'category', 'tags') +======= + list_filter = ('status', 'type', 'category') + date_hierarchy = 'creation_time' +>>>>>>> develop filter_horizontal = ('tags',) exclude = ('creation_time', 'last_modify_time') view_on_site = True @@ -80,6 +85,7 @@ class ArticlelAdmin(admin.ModelAdmin): draft_article, close_article_commentstatus, open_article_commentstatus] + raw_id_fields = ('author', 'category',) def link_to_category(self, obj): info = (obj.category._meta.app_label, obj.category._meta.model_name) diff --git a/src/blog/apps.py b/src/blog/apps.py deleted file mode 100644 index 7930587..0000000 --- a/src/blog/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class BlogConfig(AppConfig): - name = 'blog' diff --git a/src/blog/context_processors.py b/src/blog/context_processors.py deleted file mode 100644 index 73e3088..0000000 --- a/src/blog/context_processors.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from django.utils import timezone - -from djangoblog.utils import cache, get_blog_setting -from .models import Category, Article - -logger = logging.getLogger(__name__) - - -def seo_processor(requests): - key = 'seo_processor' - value = cache.get(key) - if value: - return value - else: - logger.info('set processor cache.') - setting = get_blog_setting() - value = { - 'SITE_NAME': setting.site_name, - 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, - 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, - 'SITE_SEO_DESCRIPTION': setting.site_seo_description, - 'SITE_DESCRIPTION': setting.site_description, - 'SITE_KEYWORDS': setting.site_keywords, - 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', - 'ARTICLE_SUB_LENGTH': setting.article_sub_length, - 'nav_category_list': Category.objects.all(), - 'nav_pages': Article.objects.filter( - type='p', - status='p'), - 'OPEN_SITE_COMMENT': setting.open_site_comment, - 'BEIAN_CODE': setting.beian_code, - 'ANALYTICS_CODE': setting.analytics_code, - "BEIAN_CODE_GONGAN": setting.gongan_beiancode, - "SHOW_GONGAN_CODE": setting.show_gongan_code, - "CURRENT_YEAR": timezone.now().year, - "GLOBAL_HEADER": setting.global_header, - "GLOBAL_FOOTER": setting.global_footer, - "COMMENT_NEED_REVIEW": setting.comment_need_review, - } - cache.set(key, value, 60 * 60 * 10) - return value diff --git a/src/blog/documents.py b/src/blog/documents.py deleted file mode 100644 index 0f1db7b..0000000 --- a/src/blog/documents.py +++ /dev/null @@ -1,213 +0,0 @@ -import time - -import elasticsearch.client -from django.conf import settings -from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean -from elasticsearch_dsl.connections import connections - -from blog.models import Article - -ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') - -if ELASTICSEARCH_ENABLED: - connections.create_connection( - hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) - from elasticsearch import Elasticsearch - - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - from elasticsearch.client import IngestClient - - c = IngestClient(es) - try: - c.get_pipeline('geoip') - except elasticsearch.exceptions.NotFoundError: - c.put_pipeline('geoip', body='''{ - "description" : "Add geoip info", - "processors" : [ - { - "geoip" : { - "field" : "ip" - } - } - ] - }''') - - -class GeoIp(InnerDoc): - continent_name = Keyword() - country_iso_code = Keyword() - country_name = Keyword() - location = GeoPoint() - - -class UserAgentBrowser(InnerDoc): - Family = Keyword() - Version = Keyword() - - -class UserAgentOS(UserAgentBrowser): - pass - - -class UserAgentDevice(InnerDoc): - Family = Keyword() - Brand = Keyword() - Model = Keyword() - - -class UserAgent(InnerDoc): - browser = Object(UserAgentBrowser, required=False) - os = Object(UserAgentOS, required=False) - device = Object(UserAgentDevice, required=False) - string = Text() - is_bot = Boolean() - - -class ElapsedTimeDocument(Document): - url = Keyword() - time_taken = Long() - log_datetime = Date() - ip = Keyword() - geoip = Object(GeoIp, required=False) - useragent = Object(UserAgent, required=False) - - class Index: - name = 'performance' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 - } - - class Meta: - doc_type = 'ElapsedTime' - - -class ElaspedTimeDocumentManager: - @staticmethod - def build_index(): - from elasticsearch import Elasticsearch - client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - res = client.indices.exists(index="performance") - if not res: - ElapsedTimeDocument.init() - - @staticmethod - def delete_index(): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='performance', ignore=[400, 404]) - - @staticmethod - def create(url, time_taken, log_datetime, useragent, ip): - ElaspedTimeDocumentManager.build_index() - ua = UserAgent() - ua.browser = UserAgentBrowser() - ua.browser.Family = useragent.browser.family - ua.browser.Version = useragent.browser.version_string - - ua.os = UserAgentOS() - ua.os.Family = useragent.os.family - ua.os.Version = useragent.os.version_string - - ua.device = UserAgentDevice() - ua.device.Family = useragent.device.family - ua.device.Brand = useragent.device.brand - ua.device.Model = useragent.device.model - ua.string = useragent.ua_string - ua.is_bot = useragent.is_bot - - doc = ElapsedTimeDocument( - meta={ - 'id': int( - round( - time.time() * - 1000)) - }, - url=url, - time_taken=time_taken, - log_datetime=log_datetime, - useragent=ua, ip=ip) - doc.save(pipeline="geoip") - - -class ArticleDocument(Document): - body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - author = Object(properties={ - 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - category = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - tags = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() - }) - - pub_time = Date() - status = Text() - comment_status = Text() - type = Text() - views = Integer() - article_order = Integer() - - class Index: - name = 'blog' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 - } - - class Meta: - doc_type = 'Article' - - -class ArticleDocumentManager(): - - def __init__(self): - self.create_index() - - def create_index(self): - ArticleDocument.init() - - def delete_index(self): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='blog', ignore=[400, 404]) - - def convert_to_doc(self, articles): - return [ - ArticleDocument( - meta={ - 'id': article.id}, - body=article.body, - title=article.title, - author={ - 'nickname': article.author.username, - 'id': article.author.id}, - category={ - 'name': article.category.name, - 'id': article.category.id}, - tags=[ - { - 'name': t.name, - 'id': t.id} for t in article.tags.all()], - pub_time=article.pub_time, - status=article.status, - comment_status=article.comment_status, - type=article.type, - views=article.views, - article_order=article.article_order) for article in articles] - - def rebuild(self, articles=None): - ArticleDocument.init() - articles = articles if articles else Article.objects.all() - docs = self.convert_to_doc(articles) - for doc in docs: - doc.save() - - def update_docs(self, docs): - for doc in docs: - doc.save() diff --git a/src/blog/forms.py b/src/blog/forms.py deleted file mode 100644 index 715be76..0000000 --- a/src/blog/forms.py +++ /dev/null @@ -1,19 +0,0 @@ -import logging - -from django import forms -from haystack.forms import SearchForm - -logger = logging.getLogger(__name__) - - -class BlogSearchForm(SearchForm): - querydata = forms.CharField(required=True) - - def search(self): - datas = super(BlogSearchForm, self).search() - if not self.is_valid(): - return self.no_query_found() - - if self.cleaned_data['querydata']: - logger.info(self.cleaned_data['querydata']) - return datas diff --git a/src/blog/management/__init__.py b/src/blog/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/blog/management/__pycache__/__init__.cpython-311.pyc b/src/blog/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..da92857 Binary files /dev/null and b/src/blog/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/blog/management/commands/__init__.py b/src/blog/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/blog/management/commands/build_index.py b/src/blog/management/commands/build_index.py deleted file mode 100644 index 3c4acd7..0000000 --- a/src/blog/management/commands/build_index.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.core.management.base import BaseCommand - -from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \ - ELASTICSEARCH_ENABLED - - -# TODO 参数化 -class Command(BaseCommand): - help = 'build search index' - - def handle(self, *args, **options): - if ELASTICSEARCH_ENABLED: - ElaspedTimeDocumentManager.build_index() - manager = ElapsedTimeDocument() - manager.init() - manager = ArticleDocumentManager() - manager.delete_index() - manager.rebuild() diff --git a/src/blog/management/commands/build_search_words.py b/src/blog/management/commands/build_search_words.py deleted file mode 100644 index cfe7e0d..0000000 --- a/src/blog/management/commands/build_search_words.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.core.management.base import BaseCommand - -from blog.models import Tag, Category - - -# TODO 参数化 -class Command(BaseCommand): - help = 'build search words' - - def handle(self, *args, **options): - datas = set([t.name for t in Tag.objects.all()] + - [t.name for t in Category.objects.all()]) - print('\n'.join(datas)) diff --git a/src/blog/management/commands/clear_cache.py b/src/blog/management/commands/clear_cache.py deleted file mode 100644 index 0d66172..0000000 --- a/src/blog/management/commands/clear_cache.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.core.management.base import BaseCommand - -from djangoblog.utils import cache - - -class Command(BaseCommand): - help = 'clear the whole cache' - - def handle(self, *args, **options): - cache.clear() - self.stdout.write(self.style.SUCCESS('Cleared cache\n')) diff --git a/src/blog/management/commands/create_testdata.py b/src/blog/management/commands/create_testdata.py deleted file mode 100644 index 675d2ba..0000000 --- a/src/blog/management/commands/create_testdata.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.hashers import make_password -from django.core.management.base import BaseCommand - -from blog.models import Article, Tag, Category - - -class Command(BaseCommand): - help = 'create test datas' - - def handle(self, *args, **options): - user = get_user_model().objects.get_or_create( - email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] - - pcategory = Category.objects.get_or_create( - name='我是父类目', parent_category=None)[0] - - category = Category.objects.get_or_create( - name='子类目', parent_category=pcategory)[0] - - category.save() - basetag = Tag() - basetag.name = "标签" - basetag.save() - for i in range(1, 20): - article = Article.objects.get_or_create( - category=category, - title='nice title ' + str(i), - body='nice content ' + str(i), - author=user)[0] - tag = Tag() - tag.name = "标签" + str(i) - tag.save() - article.tags.add(tag) - article.tags.add(basetag) - article.save() - - from djangoblog.utils import cache - cache.clear() - self.stdout.write(self.style.SUCCESS('created test datas \n')) diff --git a/src/blog/management/commands/ping_baidu.py b/src/blog/management/commands/ping_baidu.py deleted file mode 100644 index 2c7fbdd..0000000 --- a/src/blog/management/commands/ping_baidu.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.core.management.base import BaseCommand - -from djangoblog.spider_notify import SpiderNotify -from djangoblog.utils import get_current_site -from blog.models import Article, Tag, Category - -site = get_current_site().domain - - -class Command(BaseCommand): - help = 'notify baidu url' - - def add_arguments(self, parser): - parser.add_argument( - 'data_type', - type=str, - choices=[ - 'all', - 'article', - 'tag', - 'category'], - help='article : all article,tag : all tag,category: all category,all: All of these') - - def get_full_url(self, path): - url = "https://{site}{path}".format(site=site, path=path) - return url - - def handle(self, *args, **options): - type = options['data_type'] - self.stdout.write('start get %s' % type) - - urls = [] - if type == 'article' or type == 'all': - for article in Article.objects.filter(status='p'): - urls.append(article.get_full_url()) - if type == 'tag' or type == 'all': - for tag in Tag.objects.all(): - url = tag.get_absolute_url() - urls.append(self.get_full_url(url)) - if type == 'category' or type == 'all': - for category in Category.objects.all(): - url = category.get_absolute_url() - urls.append(self.get_full_url(url)) - - self.stdout.write( - self.style.SUCCESS( - 'start notify %d urls' % - len(urls))) - SpiderNotify.baidu_notify(urls) - self.stdout.write(self.style.SUCCESS('finish notify')) diff --git a/src/blog/management/commands/sync_user_avatar.py b/src/blog/management/commands/sync_user_avatar.py deleted file mode 100644 index d0f4612..0000000 --- a/src/blog/management/commands/sync_user_avatar.py +++ /dev/null @@ -1,47 +0,0 @@ -import requests -from django.core.management.base import BaseCommand -from django.templatetags.static import static - -from djangoblog.utils import save_user_avatar -from oauth.models import OAuthUser -from oauth.oauthmanager import get_manager_by_type - - -class Command(BaseCommand): - help = 'sync user avatar' - - def test_picture(self, url): - try: - if requests.get(url, timeout=2).status_code == 200: - return True - except: - pass - - def handle(self, *args, **options): - static_url = static("../") - users = OAuthUser.objects.all() - self.stdout.write(f'开始同步{len(users)}个用户头像') - for u in users: - self.stdout.write(f'开始同步:{u.nickname}') - url = u.picture - if url: - if url.startswith(static_url): - if self.test_picture(url): - continue - else: - if u.metadata: - manage = get_manager_by_type(u.type) - url = manage.get_picture(u.metadata) - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - else: - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - if url: - self.stdout.write( - f'结束同步:{u.nickname}.url:{url}') - u.picture = url - u.save() - self.stdout.write('结束同步') diff --git a/src/blog/middleware.py b/src/blog/middleware.py deleted file mode 100644 index 94dd70c..0000000 --- a/src/blog/middleware.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging -import time - -from ipware import get_client_ip -from user_agents import parse - -from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager - -logger = logging.getLogger(__name__) - - -class OnlineMiddleware(object): - def __init__(self, get_response=None): - self.get_response = get_response - super().__init__() - - def __call__(self, request): - ''' page render time ''' - start_time = time.time() - response = self.get_response(request) - http_user_agent = request.META.get('HTTP_USER_AGENT', '') - ip, _ = get_client_ip(request) - user_agent = parse(http_user_agent) - if not response.streaming: - try: - cast_time = time.time() - start_time - if ELASTICSEARCH_ENABLED: - time_taken = round((cast_time) * 1000, 2) - url = request.path - from django.utils import timezone - ElaspedTimeDocumentManager.create( - url=url, - time_taken=time_taken, - log_datetime=timezone.now(), - useragent=user_agent, - ip=ip) - response.content = response.content.replace( - b'', str.encode(str(cast_time)[:5])) - except Exception as e: - logger.error("Error OnlineMiddleware: %s" % e) - - return response diff --git a/src/blog/migrations/0001_initial.py b/src/blog/migrations/0001_initial.py deleted file mode 100644 index 3d391b6..0000000 --- a/src/blog/migrations/0001_initial.py +++ /dev/null @@ -1,137 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import mdeditor.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='BlogSettings', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), - ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), - ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), - ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), - ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), - ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), - ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), - ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), - ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), - ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), - ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), - ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), - ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), - ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), - ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), - ], - options={ - 'verbose_name': '网站配置', - 'verbose_name_plural': '网站配置', - }, - ), - migrations.CreateModel( - name='Links', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), - ('link', models.URLField(verbose_name='链接地址')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '友情链接', - 'verbose_name_plural': '友情链接', - 'ordering': ['sequence'], - }, - ), - migrations.CreateModel( - name='SideBar', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='标题')), - ('content', models.TextField(verbose_name='内容')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '侧边栏', - 'verbose_name_plural': '侧边栏', - 'ordering': ['sequence'], - }, - ), - migrations.CreateModel( - name='Tag', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), - ], - options={ - 'verbose_name': '标签', - 'verbose_name_plural': '标签', - 'ordering': ['name'], - }, - ), - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), - ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), - ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), - ], - options={ - 'verbose_name': '分类', - 'verbose_name_plural': '分类', - 'ordering': ['-index'], - }, - ), - migrations.CreateModel( - name='Article', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), - ('body', mdeditor.fields.MDTextField(verbose_name='正文')), - ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), - ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), - ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), - ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), - ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), - ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), - ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), - ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), - ], - options={ - 'verbose_name': '文章', - 'verbose_name_plural': '文章', - 'ordering': ['-article_order', '-pub_time'], - 'get_latest_by': 'id', - }, - ), - ] diff --git a/src/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/blog/migrations/0002_blogsettings_global_footer_and_more.py deleted file mode 100644 index adbaa36..0000000 --- a/src/blog/migrations/0002_blogsettings_global_footer_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-29 06:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('blog', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='blogsettings', - name='global_footer', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), - ), - migrations.AddField( - model_name='blogsettings', - name='global_header', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), - ), - ] diff --git a/src/blog/migrations/0003_blogsettings_comment_need_review.py b/src/blog/migrations/0003_blogsettings_comment_need_review.py deleted file mode 100644 index e9f5502..0000000 --- a/src/blog/migrations/0003_blogsettings_comment_need_review.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-09 07:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('blog', '0002_blogsettings_global_footer_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='blogsettings', - name='comment_need_review', - field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), - ), - ] diff --git a/src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py deleted file mode 100644 index ceb1398..0000000 --- a/src/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.1 on 2023-05-09 07:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('blog', '0003_blogsettings_comment_need_review'), - ] - - operations = [ - migrations.RenameField( - model_name='blogsettings', - old_name='analyticscode', - new_name='analytics_code', - ), - migrations.RenameField( - model_name='blogsettings', - old_name='beiancode', - new_name='beian_code', - ), - migrations.RenameField( - model_name='blogsettings', - old_name='sitename', - new_name='site_name', - ), - ] diff --git a/src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py deleted file mode 100644 index d08e853..0000000 --- a/src/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py +++ /dev/null @@ -1,300 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import mdeditor.fields - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='article', - options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, - ), - migrations.AlterModelOptions( - name='category', - options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, - ), - migrations.AlterModelOptions( - name='links', - options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, - ), - migrations.AlterModelOptions( - name='sidebar', - options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, - ), - migrations.AlterModelOptions( - name='tag', - options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, - ), - migrations.RemoveField( - model_name='article', - name='created_time', - ), - migrations.RemoveField( - model_name='article', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='category', - name='created_time', - ), - migrations.RemoveField( - model_name='category', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='links', - name='created_time', - ), - migrations.RemoveField( - model_name='sidebar', - name='created_time', - ), - migrations.RemoveField( - model_name='tag', - name='created_time', - ), - migrations.RemoveField( - model_name='tag', - name='last_mod_time', - ), - migrations.AddField( - model_name='article', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='article', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AddField( - model_name='category', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='category', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AddField( - model_name='links', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='sidebar', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='tag', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='tag', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='article', - name='article_order', - field=models.IntegerField(default=0, verbose_name='order'), - ), - migrations.AlterField( - model_name='article', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='article', - name='body', - field=mdeditor.fields.MDTextField(verbose_name='body'), - ), - migrations.AlterField( - model_name='article', - name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), - ), - migrations.AlterField( - model_name='article', - name='comment_status', - field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), - ), - migrations.AlterField( - model_name='article', - name='pub_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), - ), - migrations.AlterField( - model_name='article', - name='show_toc', - field=models.BooleanField(default=False, verbose_name='show toc'), - ), - migrations.AlterField( - model_name='article', - name='status', - field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), - ), - migrations.AlterField( - model_name='article', - name='tags', - field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), - ), - migrations.AlterField( - model_name='article', - name='title', - field=models.CharField(max_length=200, unique=True, verbose_name='title'), - ), - migrations.AlterField( - model_name='article', - name='type', - field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), - ), - migrations.AlterField( - model_name='article', - name='views', - field=models.PositiveIntegerField(default=0, verbose_name='views'), - ), - migrations.AlterField( - model_name='blogsettings', - name='article_comment_count', - field=models.IntegerField(default=5, verbose_name='article comment count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='article_sub_length', - field=models.IntegerField(default=300, verbose_name='article sub length'), - ), - migrations.AlterField( - model_name='blogsettings', - name='google_adsense_codes', - field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), - ), - migrations.AlterField( - model_name='blogsettings', - name='open_site_comment', - field=models.BooleanField(default=True, verbose_name='open site comment'), - ), - migrations.AlterField( - model_name='blogsettings', - name='show_google_adsense', - field=models.BooleanField(default=False, verbose_name='show adsense'), - ), - migrations.AlterField( - model_name='blogsettings', - name='sidebar_article_count', - field=models.IntegerField(default=10, verbose_name='sidebar article count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='sidebar_comment_count', - field=models.IntegerField(default=5, verbose_name='sidebar comment count'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_description', - field=models.TextField(default='', max_length=1000, verbose_name='site description'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_keywords', - field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_name', - field=models.CharField(default='', max_length=200, verbose_name='site name'), - ), - migrations.AlterField( - model_name='blogsettings', - name='site_seo_description', - field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), - ), - migrations.AlterField( - model_name='category', - name='index', - field=models.IntegerField(default=0, verbose_name='index'), - ), - migrations.AlterField( - model_name='category', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='category name'), - ), - migrations.AlterField( - model_name='category', - name='parent_category', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'), - ), - migrations.AlterField( - model_name='links', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is show'), - ), - migrations.AlterField( - model_name='links', - name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='links', - name='link', - field=models.URLField(verbose_name='link'), - ), - migrations.AlterField( - model_name='links', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='link name'), - ), - migrations.AlterField( - model_name='links', - name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), - ), - migrations.AlterField( - model_name='links', - name='show_type', - field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'), - ), - migrations.AlterField( - model_name='sidebar', - name='content', - field=models.TextField(verbose_name='content'), - ), - migrations.AlterField( - model_name='sidebar', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is enable'), - ), - migrations.AlterField( - model_name='sidebar', - name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), - ), - migrations.AlterField( - model_name='sidebar', - name='name', - field=models.CharField(max_length=100, verbose_name='title'), - ), - migrations.AlterField( - model_name='sidebar', - name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), - ), - migrations.AlterField( - model_name='tag', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='tag name'), - ), - ] diff --git a/src/blog/migrations/0006_alter_blogsettings_options.py b/src/blog/migrations/0006_alter_blogsettings_options.py deleted file mode 100644 index e36feb4..0000000 --- a/src/blog/migrations/0006_alter_blogsettings_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-26 02:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='blogsettings', - options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, - ), - ] diff --git a/src/blog/migrations/__init__.py b/src/blog/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/blog/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/blog/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..a3cf1b7 Binary files /dev/null and b/src/blog/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-311.pyc b/src/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-311.pyc new file mode 100644 index 0000000..36df054 Binary files /dev/null and b/src/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-311.pyc b/src/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-311.pyc new file mode 100644 index 0000000..0a9fab9 Binary files /dev/null and b/src/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-311.pyc b/src/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-311.pyc new file mode 100644 index 0000000..04ad362 Binary files /dev/null and b/src/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-311.pyc b/src/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-311.pyc new file mode 100644 index 0000000..a8d8413 Binary files /dev/null and b/src/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-311.pyc b/src/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-311.pyc new file mode 100644 index 0000000..72f33bd Binary files /dev/null and b/src/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-311.pyc differ diff --git a/src/blog/migrations/__pycache__/__init__.cpython-311.pyc b/src/blog/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..91dfc28 Binary files /dev/null and b/src/blog/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/blog/search_indexes.py b/src/blog/search_indexes.py deleted file mode 100644 index 7f1dfac..0000000 --- a/src/blog/search_indexes.py +++ /dev/null @@ -1,13 +0,0 @@ -from haystack import indexes - -from blog.models import Article - - -class ArticleIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - - def get_model(self): - return Article - - def index_queryset(self, using=None): - return self.get_model().objects.filter(status='p') diff --git a/src/blog/static/blog/css/style.css b/src/blog/static/blog/css/style.css index d43f7f3..631c27d 100644 --- a/src/blog/static/blog/css/style.css +++ b/src/blog/static/blog/css/style.css @@ -2017,12 +2017,16 @@ img#wpstats { width: auto; } +<<<<<<< HEAD .commentlist .avatar { height: 39px; left: 2.2em; top: 2.2em; width: 39px; } +======= + +>>>>>>> develop .comments-area article header cite, .comments-area article header time { @@ -2150,17 +2154,82 @@ div { word-break: break-all; } +<<<<<<< HEAD .commentlist .comment-author, .commentlist .comment-meta, .commentlist .comment-awaiting-moderation { float: left; +======= +/* 评论整体布局 - 使用相对定位实现头像左侧布局 */ +.commentlist .comment-body { + position: relative; + padding-left: 60px; /* 为48px头像 + 12px间距留出空间 */ + min-height: 48px; /* 确保有足够高度容纳头像 */ +} + +/* 评论作者信息 - 用户名和时间在同一行 */ +.commentlist .comment-author { + display: inline-block; + margin: 0 10px 5px 0; + font-size: 13px; + position: relative; +} + +.commentlist .comment-meta { + display: inline-block; + margin: 0 0 8px 0; + font-size: 12px; + color: #666; +} + +.commentlist .comment-awaiting-moderation { +>>>>>>> develop display: block; font-size: 13px; line-height: 22px; } +<<<<<<< HEAD .commentlist .comment-author { margin-right: 6px; +======= +/* 头像样式 - 绝对定位到左侧 */ +.commentlist .comment-author .avatar { + position: absolute !important; + left: -60px; /* 定位到容器左侧 */ + top: 0; + width: 48px !important; + height: 48px !important; + border-radius: 50%; + display: block; + object-fit: cover; + background-color: #f5f5f5; + border: 1px solid #ddd; +} + +/* 评论作者名称样式 */ +.commentlist .comment-author .fn { + display: inline; + margin: 0; + font-weight: 600; + color: #2e7bb8; + font-size: 13px; +} + +.commentlist .comment-author .fn a { + color: #2e7bb8; + text-decoration: none; +} + +.commentlist .comment-author .fn a:hover { + text-decoration: underline; +} + +/* 评论内容样式 */ +.commentlist .comment-body p { + margin: 5px 0 10px 0; + line-height: 1.5; +>>>>>>> develop } .commentlist .fn, .pinglist .ping-link { @@ -2174,6 +2243,7 @@ div { display: none; } +<<<<<<< HEAD .commentlist .avatar { position: absolute; left: -60px; @@ -2181,6 +2251,17 @@ div { width: 48px; height: 48px; border-radius: 100%; +======= +/* 通用头像样式 */ +.commentlist .avatar { + width: 48px !important; + height: 48px !important; + border-radius: 50%; + display: block; + object-fit: cover; + background-color: #f5f5f5; + border: 1px solid #ddd; +>>>>>>> develop } .commentlist .comment-meta:before, .pinglist .ping-meta:before { @@ -2290,6 +2371,7 @@ div { padding-left: 48px; } +<<<<<<< HEAD .commentlist li li .avatar { top: 0; left: -48px; @@ -2299,6 +2381,89 @@ div { .commentlist li li .comment-meta { left: 70px; +======= +/* 嵌套评论整体布局 */ +.commentlist li li .comment-body { + padding-left: 60px; /* 为48px头像 + 12px间距留出空间 */ + min-height: 48px; /* 确保有足够高度容纳头像 */ +} + +/* 嵌套评论作者信息 */ +.commentlist li li .comment-author { + display: inline-block; + margin: 0 8px 5px 0; + font-size: 12px; /* 稍小一点 */ +} + +.commentlist li li .comment-meta { + display: inline-block; + margin: 0 0 8px 0; + font-size: 11px; /* 稍小一点 */ + color: #666; +} + +/* 评论容器整体左移 - 使用更高优先级 */ +#comments #commentlist-container.comment-tab { + margin-left: -15px !important; /* 在小屏幕上向左移动15px */ + padding-left: 0 !important; /* 移除左内边距 */ + position: relative !important; /* 确保定位正确 */ +} + +/* 在较大屏幕上进一步左移 */ +@media screen and (min-width: 600px) { + #comments #commentlist-container.comment-tab { + margin-left: -30px !important; /* 在大屏幕上向左移动30px */ + } + + /* 响应式设计下的评论布局 - 保持48px头像 */ + .commentlist .comment-body { + padding-left: 60px !important; /* 为48px头像 + 12px间距留出空间 */ + min-height: 48px !important; + } + + .commentlist .comment-author { + display: inline-block !important; + margin: 0 8px 5px 0 !important; + } + + .commentlist .comment-meta { + display: inline-block !important; + margin: 0 0 8px 0 !important; + } + + /* 响应式设计下头像保持48px */ + .commentlist .comment-author .avatar { + left: -60px !important; + width: 48px !important; + height: 48px !important; + } + + /* 嵌套评论在响应式设计下也保持48px头像 */ + .commentlist li li .comment-body { + padding-left: 60px !important; + min-height: 48px !important; + } + + .commentlist li li .comment-author .avatar { + left: -60px !important; + width: 48px !important; + height: 48px !important; + } +} + +/* 嵌套评论头像 */ +.commentlist li li .comment-author .avatar { + position: absolute !important; + left: -60px; /* 定位到容器左侧 */ + top: 0; + width: 48px !important; + height: 48px !important; + border-radius: 50%; + display: block; + object-fit: cover; + background-color: #f5f5f5; + border: 1px solid #ddd; +>>>>>>> develop } /* comments : nav @@ -2501,4 +2666,279 @@ li #reply-title { height: 1px; border: none; /*border-top: 1px dashed #f5d6d6;*/ +<<<<<<< HEAD +======= +} + +/* ============================================================================= + 评论内容溢出修复样式 + 解决代码块和长文本撑开页面布局的问题 + ============================================================================= */ + +/* 评论容器基础样式 */ +.comment-body { + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + max-width: 100%; + box-sizing: border-box; +} + +/* 修复评论中的代码块溢出 */ +.comment-content pre, +.comment-body pre { + white-space: pre-wrap !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; + max-width: 100% !important; + overflow-x: auto; + padding: 10px; + background-color: #f8f8f8; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 12px; + line-height: 1.4; + margin: 10px 0; +} + +/* 修复评论中的行内代码 */ +.comment-content code, +.comment-body code { + word-wrap: break-word !important; + overflow-wrap: break-word !important; + white-space: pre-wrap; + max-width: 100%; + display: inline-block; + vertical-align: top; +} + +/* 修复评论中的长链接 */ +.comment-content a, +.comment-body a { + word-wrap: break-word !important; + overflow-wrap: break-word !important; + word-break: break-all; + max-width: 100%; +} + +/* 修复评论段落 */ +.comment-content p, +.comment-body p { + word-wrap: break-word !important; + overflow-wrap: break-word !important; + max-width: 100%; + margin: 10px 0; +} + +/* 特殊处理代码高亮块 - 关键修复! */ +.comment-content .codehilite, +.comment-body .codehilite { + max-width: 100% !important; + overflow-x: auto; + margin: 10px 0; + background: #f8f8f8 !important; + border: 1px solid #ddd; + border-radius: 4px; + padding: 10px; + font-size: 12px; + line-height: 1.4; + /* 关键:防止内容撑开容器 */ + width: 100%; + box-sizing: border-box; + display: block; +} + +.comment-content .codehilite pre, +.comment-body .codehilite pre { + white-space: pre-wrap !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; + border: none !important; + font-size: inherit; + line-height: inherit; + /* 确保pre标签不会超出父容器 */ + max-width: 100%; + width: 100%; + box-sizing: border-box; +} + +/* 修复代码高亮中的span标签 */ +.comment-content .codehilite span, +.comment-body .codehilite span { + word-wrap: break-word !important; + overflow-wrap: break-word !important; + /* 防止行内元素导致的溢出 */ + display: inline; + max-width: 100%; +} + +/* 针对特定的代码高亮类 */ +.comment-content .codehilite .kt, +.comment-content .codehilite .nf, +.comment-content .codehilite .n, +.comment-content .codehilite .p, +.comment-body .codehilite .kt, +.comment-body .codehilite .nf, +.comment-body .codehilite .n, +.comment-body .codehilite .p { + word-wrap: break-word !important; + overflow-wrap: break-word !important; +} + +/* 搜索结果高亮样式 */ +.search-result { + margin-bottom: 30px; + padding: 20px; + border: 1px solid #e1e1e1; + border-radius: 5px; + background: #fff; +} + +.search-result .entry-title { + margin: 0 0 10px 0; + font-size: 1.5em; +} + +.search-result .entry-title a { + color: #2c3e50; + text-decoration: none; +} + +.search-result .entry-title a:hover { + color: #3498db; +} + +.search-result .entry-meta { + color: #7f8c8d; + font-size: 0.9em; + margin-bottom: 15px; +} + +.search-result .entry-meta span { + margin-right: 15px; +} + +.search-excerpt { + line-height: 1.6; + color: #555; +} + +.search-excerpt p { + margin: 10px 0; +} + +/* 搜索关键词高亮 */ +.search-excerpt em, +.search-result .entry-title em { + background-color: #fff3cd; + color: #856404; + font-style: normal; + font-weight: bold; + padding: 2px 4px; + border-radius: 3px; +} + +.more-link { + color: #3498db; + text-decoration: none; + font-weight: bold; +} + +.more-link:hover { + text-decoration: underline; +} +.comment-content .codehilite .w, +.comment-content .codehilite .o, +.comment-body .codehilite .kt, +.comment-body .codehilite .nf, +.comment-body .codehilite .n, +.comment-body .codehilite .p, +.comment-body .codehilite .w, +.comment-body .codehilite .o { + word-break: break-all; + overflow-wrap: break-word; +} + +/* 修复评论列表项 */ +.commentlist li { + max-width: 100%; + overflow: hidden; + box-sizing: border-box; +} + +/* 确保评论内容不超出容器 */ +.commentlist .comment-body { + max-width: calc(100% - 20px); /* 留出一些边距 */ + margin-left: 10px; + margin-right: 10px; + overflow: hidden; /* 防止内容溢出 */ + word-wrap: break-word; +} + +/* 重要:限制评论列表项的最大宽度 */ +.commentlist li[style*="margin-left"] { + max-width: calc(100% - 2rem) !important; + overflow: hidden; + box-sizing: border-box; +} + +/* 特别处理深层嵌套的评论 */ +.commentlist li[style*="margin-left: 3rem"], +.commentlist li[style*="margin-left: 6rem"], +.commentlist li[style*="margin-left: 9rem"] { + max-width: calc(100% - 1rem) !important; +} + +/* 移动端优化 */ +@media (max-width: 768px) { + .comment-content pre, + .comment-body pre { + font-size: 11px; + padding: 8px; + margin: 8px 0; + } + + .commentlist .comment-body { + max-width: calc(100% - 10px); + margin-left: 5px; + margin-right: 5px; + } + + /* 移动端评论缩进调整 */ + .commentlist li[style*="margin-left"] { + margin-left: 1rem !important; + max-margin-left: 2rem !important; + } +} + +/* 防止表格溢出 */ +.comment-content table, +.comment-body table { + max-width: 100%; + overflow-x: auto; + display: block; + white-space: nowrap; +} + +/* 修复图片溢出 */ +.comment-content img, +.comment-body img { + max-width: 100% !important; + height: auto !important; +} + +/* 修复引用块 */ +.comment-content blockquote, +.comment-body blockquote { + max-width: 100%; + overflow-wrap: break-word; + word-wrap: break-word; + padding: 10px 15px; + margin: 10px 0; + border-left: 4px solid #ddd; + background-color: #f9f9f9; +>>>>>>> develop } \ No newline at end of file diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 new file mode 100644 index 0000000..0fb066c Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 new file mode 100644 index 0000000..bc2aea0 Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 new file mode 100644 index 0000000..fcce594 Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 new file mode 100644 index 0000000..ffc8e9c Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 new file mode 100644 index 0000000..6375e9c Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 new file mode 100644 index 0000000..2e849f6 Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 new file mode 100644 index 0000000..5de3fea Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 new file mode 100644 index 0000000..e5c936b Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 new file mode 100644 index 0000000..5cf8aff Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 new file mode 100644 index 0000000..bdc12e8 Binary files /dev/null and b/src/blog/static/blog/fonts/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 new file mode 100644 index 0000000..b5d54e7 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 new file mode 100644 index 0000000..bed5b67 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 new file mode 100644 index 0000000..9164ccb Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 new file mode 100644 index 0000000..08bed85 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 new file mode 100644 index 0000000..307b214 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 new file mode 100644 index 0000000..0b0b3a4 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 new file mode 100644 index 0000000..4bce1d0 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 new file mode 100644 index 0000000..5bd7b8f Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 new file mode 100644 index 0000000..b969602 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 new file mode 100644 index 0000000..a804b10 Binary files /dev/null and b/src/blog/static/blog/fonts/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 differ diff --git a/src/blog/static/blog/fonts/open-sans.css b/src/blog/static/blog/fonts/open-sans.css new file mode 100644 index 0000000..e6dd4a9 --- /dev/null +++ b/src/blog/static/blog/fonts/open-sans.css @@ -0,0 +1,600 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2'); + unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; +} +/* hebrew */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2'); + unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* math */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2'); + unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; +} +/* symbols */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2'); + unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF; +} +/* vietnamese */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + font-stretch: 100%; + font-display: swap; + src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/src/blog/static/blog/js/mathjax-loader.js b/src/blog/static/blog/js/mathjax-loader.js new file mode 100644 index 0000000..c922fc7 --- /dev/null +++ b/src/blog/static/blog/js/mathjax-loader.js @@ -0,0 +1,142 @@ +/** + * MathJax 智能加载器 + * 检测页面是否包含数学公式,如果有则动态加载和配置MathJax + */ +(function() { + 'use strict'; + + /** + * 检测页面是否包含数学公式 + * @returns {boolean} 是否包含数学公式 + */ + function hasMathFormulas() { + const content = document.body.textContent || document.body.innerText || ''; + // 检测常见的数学公式语法 + return /\$.*?\$|\$\$.*?\$\$|\\begin\{.*?\}|\\end\{.*?\}|\\[a-zA-Z]+\{/.test(content); + } + + /** + * 配置MathJax + */ + function configureMathJax() { + window.MathJax = { + tex: { + // 行内公式和块级公式分隔符 + inlineMath: [['$', '$']], + displayMath: [['$$', '$$']], + // 处理转义字符和LaTeX环境 + processEscapes: true, + processEnvironments: true, + // 自动换行 + tags: 'ams' + }, + options: { + // 跳过这些HTML标签,避免处理代码块等 + skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'], + // CSS类控制 + ignoreHtmlClass: 'tex2jax_ignore', + processHtmlClass: 'tex2jax_process' + }, + // 启动配置 + startup: { + ready() { + console.log('MathJax配置完成,开始初始化...'); + MathJax.startup.defaultReady(); + + // 处理特定区域的数学公式 + const contentEl = document.getElementById('content'); + const commentsEl = document.getElementById('comments'); + + const promises = []; + if (contentEl) { + promises.push(MathJax.typesetPromise([contentEl])); + } + if (commentsEl) { + promises.push(MathJax.typesetPromise([commentsEl])); + } + + // 等待所有渲染完成 + Promise.all(promises).then(() => { + console.log('MathJax渲染完成'); + // 触发自定义事件,通知其他脚本MathJax已就绪 + document.dispatchEvent(new CustomEvent('mathjaxReady')); + }).catch(error => { + console.error('MathJax渲染失败:', error); + }); + } + }, + // 输出配置 + chtml: { + scale: 1, + minScale: 0.5, + matchFontHeight: false, + displayAlign: 'center', + displayIndent: '0' + } + }; + } + + /** + * 加载MathJax库 + */ + function loadMathJax() { + console.log('检测到数学公式,开始加载MathJax...'); + + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'; + script.async = true; + script.defer = true; + + script.onload = function() { + console.log('MathJax库加载成功'); + }; + + script.onerror = function() { + console.error('MathJax库加载失败,尝试备用CDN...'); + // 备用CDN + const fallbackScript = document.createElement('script'); + fallbackScript.src = 'https://polyfill.io/v3/polyfill.min.js?features=es6'; + fallbackScript.onload = function() { + const mathJaxScript = document.createElement('script'); + mathJaxScript.src = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML'; + mathJaxScript.async = true; + document.head.appendChild(mathJaxScript); + }; + document.head.appendChild(fallbackScript); + }; + + document.head.appendChild(script); + } + + /** + * 初始化函数 + */ + function init() { + // 等待DOM完全加载 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + return; + } + + // 检测是否需要加载MathJax + if (hasMathFormulas()) { + // 先配置,再加载 + configureMathJax(); + loadMathJax(); + } else { + console.log('未检测到数学公式,跳过MathJax加载'); + } + } + + // 提供重新渲染的全局方法,供动态内容使用 + window.rerenderMathJax = function(element) { + if (window.MathJax && window.MathJax.typesetPromise) { + const target = element || document.body; + return window.MathJax.typesetPromise([target]); + } + return Promise.resolve(); + }; + + // 启动初始化 + init(); +})(); diff --git a/src/blog/templatetags/__init__.py b/src/blog/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/blog/templatetags/__pycache__/__init__.cpython-311.pyc b/src/blog/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..3889cef Binary files /dev/null and b/src/blog/templatetags/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/blog/templatetags/__pycache__/blog_tags.cpython-311.pyc b/src/blog/templatetags/__pycache__/blog_tags.cpython-311.pyc new file mode 100644 index 0000000..0edc96f Binary files /dev/null and b/src/blog/templatetags/__pycache__/blog_tags.cpython-311.pyc differ diff --git a/src/blog/templatetags/blog_tags.py b/src/blog/templatetags/blog_tags.py index 110b22b..ef4ba11 100644 --- a/src/blog/templatetags/blog_tags.py +++ b/src/blog/templatetags/blog_tags.py @@ -45,7 +45,75 @@ def datetimeformat(data): @register.filter() @stringfilter def custom_markdown(content): - return mark_safe(CommonMarkdown.get_markdown(content)) + """ + 通用markdown过滤器,应用文章内容插件 + 主要用于文章内容处理 + """ + html_content = CommonMarkdown.get_markdown(content) + + # 然后应用插件过滤器优化HTML + from djangoblog.plugin_manage import hooks + from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + optimized_html = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, html_content) + + return mark_safe(optimized_html) + + +@register.filter() +@stringfilter +def sidebar_markdown(content): + html_content = CommonMarkdown.get_markdown(content) + return mark_safe(html_content) + + +@register.simple_tag(takes_context=True) +def render_article_content(context, article, is_summary=False): + """ + 渲染文章内容,包含完整的上下文信息供插件使用 + + Args: + context: 模板上下文 + article: 文章对象 + is_summary: 是否为摘要模式(首页使用) + """ + if not article or not hasattr(article, 'body'): + return '' + + # 先转换Markdown为HTML + html_content = CommonMarkdown.get_markdown(article.body) + + # 如果是摘要模式,先截断内容再应用插件 + if is_summary: + # 截断HTML内容到合适的长度(约300字符) + from django.utils.html import strip_tags + from django.template.defaultfilters import truncatechars + + # 先去除HTML标签,截断纯文本,然后重新转换为HTML + plain_text = strip_tags(html_content) + truncated_text = truncatechars(plain_text, 300) + + # 重新转换截断后的文本为HTML(简化版,避免复杂的插件处理) + html_content = CommonMarkdown.get_markdown(truncated_text) + + # 然后应用插件过滤器,传递完整的上下文 + from djangoblog.plugin_manage import hooks + from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + + # 获取request对象 + request = context.get('request') + + # 应用所有文章内容相关的插件 + # 注意:摘要模式下某些插件(如版权声明)可能不适用 + optimized_html = hooks.apply_filters( + ARTICLE_CONTENT_HOOK_NAME, + html_content, + article=article, + request=request, + context=context, + is_summary=is_summary # 传递摘要标志,插件可以据此调整行为 + ) + + return mark_safe(optimized_html) @register.simple_tag @@ -286,38 +354,49 @@ def load_article_detail(article, isindex, user): } -# return only the URL of the gravatar -# TEMPLATE USE: {{ email|gravatar_url:150 }} +# 返回用户头像URL +# 模板使用方法: {{ email|gravatar_url:150 }} @register.filter def gravatar_url(email, size=40): - """获得gravatar头像""" - cachekey = 'gravatat/' + email + """获得用户头像 - 优先使用OAuth头像,否则使用默认头像""" + cachekey = 'avatar/' + email url = cache.get(cachekey) if url: return url - else: - usermodels = OAuthUser.objects.filter(email=email) - if usermodels: - o = list(filter(lambda x: x.picture is not None, usermodels)) - if o: - return o[0].picture - email = email.encode('utf-8') - - default = static('blog/img/avatar.png') - - url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( - email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) - cache.set(cachekey, url, 60 * 60 * 10) - logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) - return url + + # 检查OAuth用户是否有自定义头像 + usermodels = OAuthUser.objects.filter(email=email) + if usermodels: + # 过滤出有头像的用户 + users_with_picture = list(filter(lambda x: x.picture is not None, usermodels)) + if users_with_picture: + # 获取默认头像路径用于比较 + default_avatar_path = static('blog/img/avatar.png') + + # 优先选择非默认头像的用户,否则选择第一个 + non_default_users = [u for u in users_with_picture if u.picture != default_avatar_path and not u.picture.endswith('/avatar.png')] + selected_user = non_default_users[0] if non_default_users else users_with_picture[0] + + url = selected_user.picture + cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 + + avatar_type = 'non-default' if non_default_users else 'default' + logger.info('Using {} OAuth avatar for {} from {}'.format(avatar_type, email, selected_user.type)) + return url + + # 使用默认头像 + url = static('blog/img/avatar.png') + cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 + logger.info('Using default avatar for {}'.format(email)) + return url @register.filter def gravatar(email, size=40): - """获得gravatar头像""" + """获得用户头像HTML标签""" url = gravatar_url(email, size) return mark_safe( - '' % + '用户头像' % (url, size, size)) @@ -336,3 +415,134 @@ def query(qs, **kwargs): def addstr(arg1, arg2): """concatenate arg1 & arg2""" return str(arg1) + str(arg2) + + +# === 插件系统模板标签 === + +@register.simple_tag(takes_context=True) +def render_plugin_widgets(context, position, **kwargs): + """ + 渲染指定位置的所有插件组件 + + Args: + context: 模板上下文 + position: 位置标识 + **kwargs: 传递给插件的额外参数 + + Returns: + 按优先级排序的所有插件HTML内容 + """ + from djangoblog.plugin_manage.loader import get_loaded_plugins + + widgets = [] + + for plugin in get_loaded_plugins(): + try: + widget_data = plugin.render_position_widget( + position=position, + context=context, + **kwargs + ) + if widget_data: + widgets.append(widget_data) + except Exception as e: + logger.error(f"Error rendering widget from plugin {plugin.PLUGIN_NAME}: {e}") + + # 按优先级排序(数字越小优先级越高) + widgets.sort(key=lambda x: x['priority']) + + # 合并HTML内容 + html_parts = [widget['html'] for widget in widgets] + return mark_safe(''.join(html_parts)) + + +@register.simple_tag(takes_context=True) +def plugin_head_resources(context): + """渲染所有插件的head资源(仅自定义HTML,CSS已集成到压缩系统)""" + from djangoblog.plugin_manage.loader import get_loaded_plugins + + resources = [] + + for plugin in get_loaded_plugins(): + try: + # 只处理自定义head HTML(CSS文件已通过压缩系统处理) + head_html = plugin.get_head_html(context) + if head_html: + resources.append(head_html) + + except Exception as e: + logger.error(f"Error loading head resources from plugin {plugin.PLUGIN_NAME}: {e}") + + return mark_safe('\n'.join(resources)) + + +@register.simple_tag(takes_context=True) +def plugin_body_resources(context): + """渲染所有插件的body资源(仅自定义HTML,JS已集成到压缩系统)""" + from djangoblog.plugin_manage.loader import get_loaded_plugins + + resources = [] + + for plugin in get_loaded_plugins(): + try: + # 只处理自定义body HTML(JS文件已通过压缩系统处理) + body_html = plugin.get_body_html(context) + if body_html: + resources.append(body_html) + + except Exception as e: + logger.error(f"Error loading body resources from plugin {plugin.PLUGIN_NAME}: {e}") + + return mark_safe('\n'.join(resources)) + + +@register.inclusion_tag('plugins/css_includes.html') +def plugin_compressed_css(): + """插件CSS压缩包含模板""" + from djangoblog.plugin_manage.loader import get_loaded_plugins + + css_files = [] + for plugin in get_loaded_plugins(): + for css_file in plugin.get_css_files(): + css_url = plugin.get_static_url(css_file) + css_files.append(css_url) + + return {'css_files': css_files} + + +@register.inclusion_tag('plugins/js_includes.html') +def plugin_compressed_js(): + """插件JS压缩包含模板""" + from djangoblog.plugin_manage.loader import get_loaded_plugins + + js_files = [] + for plugin in get_loaded_plugins(): + for js_file in plugin.get_js_files(): + js_url = plugin.get_static_url(js_file) + js_files.append(js_url) + + return {'js_files': js_files} + + + + +@register.simple_tag(takes_context=True) +def plugin_widget(context, plugin_name, widget_type='default', **kwargs): + """ + 渲染指定插件的组件 + + 使用方式: + {% plugin_widget 'article_recommendation' 'bottom' article=article count=5 %} + """ + from djangoblog.plugin_manage.loader import get_plugin_by_slug + + plugin = get_plugin_by_slug(plugin_name) + if plugin and hasattr(plugin, 'render_template'): + try: + widget_context = {**context.flatten(), **kwargs} + template_name = f"{widget_type}.html" + return mark_safe(plugin.render_template(template_name, widget_context)) + except Exception as e: + logger.error(f"Error rendering plugin widget {plugin_name}.{widget_type}: {e}") + + return "" \ No newline at end of file diff --git a/src/blog/urls.py b/src/blog/urls.py deleted file mode 100644 index adf2703..0000000 --- a/src/blog/urls.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.urls import path -from django.views.decorators.cache import cache_page - -from . import views - -app_name = "blog" -urlpatterns = [ - path( - r'', - views.IndexView.as_view(), - name='index'), - path( - r'page//', - views.IndexView.as_view(), - name='index_page'), - path( - r'article////.html', - views.ArticleDetailView.as_view(), - name='detailbyid'), - path( - r'category/.html', - views.CategoryDetailView.as_view(), - name='category_detail'), - path( - r'category//.html', - views.CategoryDetailView.as_view(), - name='category_detail_page'), - path( - r'author/.html', - views.AuthorDetailView.as_view(), - name='author_detail'), - path( - r'author//.html', - views.AuthorDetailView.as_view(), - name='author_detail_page'), - path( - r'tag/.html', - views.TagDetailView.as_view(), - name='tag_detail'), - path( - r'tag//.html', - views.TagDetailView.as_view(), - name='tag_detail_page'), - path( - 'archives.html', - cache_page( - 60 * 60)( - views.ArchivesView.as_view()), - name='archives'), - path( - 'links.html', - views.LinkListView.as_view(), - name='links'), - path( - r'upload', - views.fileupload, - name='upload'), - path( - r'clean', - views.clean_cache_view, - name='clean'), -] diff --git a/src/blog/views.py b/src/blog/views.py index 4af9242..32a93a0 100644 --- a/src/blog/views.py +++ b/src/blog/views.py @@ -154,7 +154,20 @@ class ArticleDetailView(DetailView): kwargs['next_article'] = self.object.next_article kwargs['prev_article'] = self.object.prev_article +<<<<<<< HEAD return super(ArticleDetailView, self).get_context_data(**kwargs) +======= + context = super(ArticleDetailView, self).get_context_data(**kwargs) + article = self.object + + # 触发文章详情加载钩子,让插件可以添加额外的上下文数据 + from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD + hooks.run_action(ARTICLE_DETAIL_LOAD, article=article, context=context, request=self.request) + + # Action Hook, 通知插件"文章详情已获取" + hooks.run_action('after_article_body_get', article=article, request=self.request) + return context +>>>>>>> develop class CategoryDetailView(ArticleListView): diff --git a/src/codecov.yml b/src/codecov.yml new file mode 100644 index 0000000..2298829 --- /dev/null +++ b/src/codecov.yml @@ -0,0 +1,87 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: no + +ignore: + # Django 相关 + - "*/migrations/*" + - "manage.py" + - "*/settings.py" + - "*/wsgi.py" + - "*/asgi.py" + + # 测试相关 + - "*/tests/*" + - "*/test_*.py" + - "*/*test*.py" + + # 静态文件和模板 + - "*/static/*" + - "*/templates/*" + - "*/collectedstatic/*" + + # 国际化文件 + - "*/locale/*" + - "**/*.po" + - "**/*.mo" + + # 文档和部署 + - "*/docs/*" + - "*/deploy/*" + - "README*.md" + - "LICENSE" + - "Dockerfile" + - "docker-compose*.yml" + - "*.yaml" + - "*.yml" + + # 开发环境 + - "*/venv/*" + - "*/__pycache__/*" + - "*.pyc" + - ".coverage" + - "coverage.xml" + + # 日志文件 + - "*/logs/*" + - "*.log" + + # 特定文件 + - "*/whoosh_cn_backend.py" # 搜索后端 + - "*/elasticsearch_backend.py" # 搜索后端 + - "*/MemcacheStorage.py" # 缓存存储 + - "*/robot.py" # 机器人相关 + + # 配置文件 + - "codecov.yml" + - ".coveragerc" + - "requirements*.txt" diff --git a/src/collectedstatic/compressed/css/output.f76962182efc.css b/src/collectedstatic/compressed/css/output.f76962182efc.css new file mode 100644 index 0000000..7436e06 --- /dev/null +++ b/src/collectedstatic/compressed/css/output.f76962182efc.css @@ -0,0 +1 @@ +.icon-sn-google{background-position:0 -28px}.icon-sn-bg-google{background-color:#4285f4;background-position:0 0}.fa-sn-google{color:#4285f4}.icon-sn-github{background-position:-28px -28px}.icon-sn-bg-github{background-color:#333;background-position:-28px 0}.fa-sn-github{color:#333}.icon-sn-weibo{background-position:-56px -28px}.icon-sn-bg-weibo{background-color:#e90d24;background-position:-56px 0}.fa-sn-weibo{color:#e90d24}.icon-sn-qq{background-position:-84px -28px}.icon-sn-bg-qq{background-color:#0098e6;background-position:-84px 0}.fa-sn-qq{color:#0098e6}.icon-sn-twitter{background-position:-112px -28px}.icon-sn-bg-twitter{background-color:#50abf1;background-position:-112px 0}.fa-sn-twitter{color:#50abf1}.icon-sn-facebook{background-position:-140px -28px}.icon-sn-bg-facebook{background-color:#4862a3;background-position:-140px 0}.fa-sn-facebook{color:#4862a3}.icon-sn-renren{background-position:-168px -28px}.icon-sn-bg-renren{background-color:#197bc8;background-position:-168px 0}.fa-sn-renren{color:#197bc8}.icon-sn-tqq{background-position:-196px -28px}.icon-sn-bg-tqq{background-color:#1f9ed2;background-position:-196px 0}.fa-sn-tqq{color:#1f9ed2}.icon-sn-douban{background-position:-224px -28px}.icon-sn-bg-douban{background-color:#279738;background-position:-224px 0}.fa-sn-douban{color:#279738}.icon-sn-weixin{background-position:-252px -28px}.icon-sn-bg-weixin{background-color:#00b500;background-position:-252px 0}.fa-sn-weixin{color:#00b500}.icon-sn-dotted{background-position:-280px -28px}.icon-sn-bg-dotted{background-color:#eee;background-position:-280px 0}.fa-sn-dotted{color:#eee}.icon-sn-site{background-position:-308px -28px}.icon-sn-bg-site{background-color:#00b500;background-position:-308px 0}.fa-sn-site{color:#00b500}.icon-sn-linkedin{background-position:-336px -28px}.icon-sn-bg-linkedin{background-color:#0077b9;background-position:-336px 0}.fa-sn-linkedin{color:#0077b9}[class*=icon-sn-]{display:inline-block;background-image:url('/static/blog/img/icon-sn.svg');background-repeat:no-repeat;width:28px;height:28px;vertical-align:middle;background-size:auto 56px}[class*=icon-sn-]:hover{opacity:.8;filter:alpha(opacity=80)}.btn-sn-google{background:#4285f4}.btn-sn-google:active,.btn-sn-google:focus,.btn-sn-google:hover{background:#2a75f3}.btn-sn-github{background:#333}.btn-sn-github:active,.btn-sn-github:focus,.btn-sn-github:hover{background:#262626}.btn-sn-weibo{background:#e90d24}.btn-sn-weibo:active,.btn-sn-weibo:focus,.btn-sn-weibo:hover{background:#d10c20}.btn-sn-qq{background:#0098e6}.btn-sn-qq:active,.btn-sn-qq:focus,.btn-sn-qq:hover{background:#0087cd}.btn-sn-twitter{background:#50abf1}.btn-sn-twitter:active,.btn-sn-twitter:focus,.btn-sn-twitter:hover{background:#38a0ef}.btn-sn-facebook{background:#4862a3}.btn-sn-facebook:active,.btn-sn-facebook:focus,.btn-sn-facebook:hover{background:#405791}.btn-sn-renren{background:#197bc8}.btn-sn-renren:active,.btn-sn-renren:focus,.btn-sn-renren:hover{background:#166db1}.btn-sn-tqq{background:#1f9ed2}.btn-sn-tqq:active,.btn-sn-tqq:focus,.btn-sn-tqq:hover{background:#1c8dbc}.btn-sn-douban{background:#279738}.btn-sn-douban:active,.btn-sn-douban:focus,.btn-sn-douban:hover{background:#228330}.btn-sn-weixin{background:#00b500}.btn-sn-weixin:active,.btn-sn-weixin:focus,.btn-sn-weixin:hover{background:#009c00}.btn-sn-dotted{background:#eee}.btn-sn-dotted:active,.btn-sn-dotted:focus,.btn-sn-dotted:hover{background:#e1e1e1}.btn-sn-site{background:#00b500}.btn-sn-site:active,.btn-sn-site:focus,.btn-sn-site:hover{background:#009c00}.btn-sn-linkedin{background:#0077b9}.btn-sn-linkedin:active,.btn-sn-linkedin:focus,.btn-sn-linkedin:hover{background:#0067a0}[class*=btn-sn-],[class*=btn-sn-]:active,[class*=btn-sn-]:focus,[class*=btn-sn-]:hover{border:none;color:#fff}.btn-sn-more{padding:0}.btn-sn-more,.btn-sn-more:active,.btn-sn-more:hover{box-shadow:none}[class*=btn-sn-] [class*=icon-sn-]{background-color:transparent}.codehilite .hll{background-color:#ffffcc}.codehilite{background:#ffffff}.codehilite .c{color:#177500}.codehilite .err{color:#000000}.codehilite .k{color:#A90D91}.codehilite .l{color:#1C01CE}.codehilite .n{color:#000000}.codehilite .o{color:#000000}.codehilite .ch{color:#177500}.codehilite .cm{color:#177500}.codehilite .cp{color:#633820}.codehilite .cpf{color:#177500}.codehilite .c1{color:#177500}.codehilite .cs{color:#177500}.codehilite .kc{color:#A90D91}.codehilite .kd{color:#A90D91}.codehilite .kn{color:#A90D91}.codehilite .kp{color:#A90D91}.codehilite .kr{color:#A90D91}.codehilite .kt{color:#A90D91}.codehilite .ld{color:#1C01CE}.codehilite .m{color:#1C01CE}.codehilite .s{color:#C41A16}.codehilite .na{color:#836C28}.codehilite .nb{color:#A90D91}.codehilite .nc{color:#3F6E75}.codehilite .no{color:#000000}.codehilite .nd{color:#000000}.codehilite .ni{color:#000000}.codehilite .ne{color:#000000}.codehilite .nf{color:#000000}.codehilite .nl{color:#000000}.codehilite .nn{color:#000000}.codehilite .nx{color:#000000}.codehilite .py{color:#000000}.codehilite .nt{color:#000000}.codehilite .nv{color:#000000}.codehilite .ow{color:#000000}.codehilite .mb{color:#1C01CE}.codehilite .mf{color:#1C01CE}.codehilite .mh{color:#1C01CE}.codehilite .mi{color:#1C01CE}.codehilite .mo{color:#1C01CE}.codehilite .sb{color:#C41A16}.codehilite .sc{color:#2300CE}.codehilite .sd{color:#C41A16}.codehilite .s2{color:#C41A16}.codehilite .se{color:#C41A16}.codehilite .sh{color:#C41A16}.codehilite .si{color:#C41A16}.codehilite .sx{color:#C41A16}.codehilite .sr{color:#C41A16}.codehilite .s1{color:#C41A16}.codehilite .ss{color:#C41A16}.codehilite .bp{color:#5B269A}.codehilite .vc{color:#000000}.codehilite .vg{color:#000000}.codehilite .vi{color:#000000}.codehilite .il{color:#1C01CE}#nprogress{pointer-events:none}#nprogress .bar{background:red;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0px;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1.0;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translate(0px,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:red;border-left-color:red;border-radius:50%;-webkit-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .spinner,.nprogress-custom-parent #nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.article-recommendations{margin:30px 0;padding:20px;background:#fff;border:1px solid #e1e1e1;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.1)}.recommendations-title{margin:0 0 15px 0;font-size:18px;color:#444;font-weight:bold;padding-bottom:8px;border-bottom:2px solid #21759b;display:inline-block}.recommendations-icon{margin-right:5px;font-size:16px}.recommendations-grid{display:grid;gap:15px;grid-template-columns:1fr;margin-top:15px}.recommendation-card{background:#fff;border:1px solid #e1e1e1;border-radius:3px;transition:all 0.2s ease;overflow:hidden}.recommendation-card:hover{border-color:#21759b;box-shadow:0 2px 5px rgba(33,117,155,0.1)}.recommendation-link{display:block;padding:15px;text-decoration:none;color:inherit}.recommendation-title{margin:0 0 8px 0;font-size:15px;font-weight:normal;color:#444;line-height:1.4;transition:color 0.2s ease}.recommendation-card:hover .recommendation-title{color:#21759b}.recommendation-meta{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:#757575}.recommendation-category{background:#ebebeb;color:#5e5e5e;padding:2px 6px;border-radius:2px;font-size:11px;font-weight:normal}.recommendation-date{font-weight:normal;color:#757575}.widget_recommendations{margin-bottom:20px}.widget_recommendations .widget-title{font-size:16px;font-weight:bold;margin-bottom:15px;color:#333;border-bottom:2px solid #007cba;padding-bottom:5px}.recommendations-list{list-style:none;padding:0;margin:0}.recommendations-list .recommendation-item{padding:8px 0;border-bottom:1px solid #eee;background:none;border:none;border-radius:0}.recommendations-list .recommendation-item:last-child{border-bottom:none}.recommendations-list .recommendation-item a{color:#333;text-decoration:none;font-size:14px;line-height:1.4;display:block;margin-bottom:4px;transition:color 0.3s ease}.recommendations-list .recommendation-item a:hover{color:#007cba}.recommendations-list .recommendation-meta{font-size:11px;color:#999;margin:0}.recommendations-list .recommendation-meta span{margin-right:10px}@media (min-width:768px){.recommendations-grid{grid-template-columns:repeat(2,1fr);gap:15px}}@media (min-width:1024px){.recommendations-grid{grid-template-columns:repeat(3,1fr);gap:15px}}@media (min-width:1200px){.recommendations-grid{grid-template-columns:repeat(4,1fr);gap:15px}} \ No newline at end of file diff --git a/src/collectedstatic/compressed/css/output.fbe498417efd.css b/src/collectedstatic/compressed/css/output.fbe498417efd.css new file mode 100644 index 0000000..61fbfea --- /dev/null +++ b/src/collectedstatic/compressed/css/output.fbe498417efd.css @@ -0,0 +1 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}caption,th,td{font-weight:normal;text-align:left}h1,h2,h3,h4,h5,h6{clear:both}html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none}del{color:#333}ins{background:#fff9c0;text-decoration:none}hr{background-color:#ccc;border:0;height:1px;margin:24px;margin-bottom:1.714285714rem}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}small{font-size:smaller}img{border:0;-ms-interpolation-mode:bicubic}.clear:after,.wrapper:after,.format-status .entry-header:after{clear:both}.clear:before,.clear:after,.wrapper:before,.wrapper:after,.format-status .entry-header:before,.format-status .entry-header:after{display:table;content:""}.archive-title,.page-title,.widget-title,.entry-content th,.comment-content th{font-size:11px;font-size:0.785714286rem;line-height:2.181818182;font-weight:bold;text-transform:uppercase;color:#636363}article.format-quote footer.entry-meta,article.format-link footer.entry-meta,article.format-status footer.entry-meta{font-size:11px;font-size:0.785714286rem;line-height:2.181818182}button,input,select,textarea{border:1px solid #ccc;border-radius:3px;font-family:inherit;padding:6px;padding:0.428571429rem}button,input{line-height:normal}textarea{font-size:100%;overflow:auto;vertical-align:top}input[type="checkbox"],input[type="radio"],input[type="file"],input[type="hidden"],input[type="image"],input[type="color"]{border:0;border-radius:0;padding:0}.menu-toggle,input[type="submit"],input[type="button"],input[type="reset"],article.post-password-required input[type=submit],.bypostauthor cite span{padding:6px 10px;padding:0.428571429rem 0.714285714rem;font-size:11px;font-size:0.785714286rem;line-height:1.428571429;font-weight:normal;color:#7c7c7c;background-color:#e6e6e6;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-ms-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-webkit-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:-o-linear-gradient(top,#f4f4f4,#e6e6e6);background-image:linear-gradient(to bottom,#f4f4f4,#e6e6e6);border:1px solid #d2d2d2;border-radius:3px;box-shadow:0 1px 2px rgba(64,64,64,0.1)}.menu-toggle,button,input[type="submit"],input[type="button"],input[type="reset"]{cursor:pointer}button[disabled],input[disabled]{cursor:default}.menu-toggle:hover,.menu-toggle:focus,button:hover,input[type="submit"]:hover,input[type="button"]:hover,input[type="reset"]:hover,article.post-password-required input[type=submit]:hover{color:#5e5e5e;background-color:#ebebeb;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-ms-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-webkit-linear-gradient(top,#f9f9f9,#ebebeb);background-image:-o-linear-gradient(top,#f9f9f9,#ebebeb);background-image:linear-gradient(to bottom,#f9f9f9,#ebebeb)}.menu-toggle:active,.menu-toggle.toggled-on,button:active,input[type="submit"]:active,input[type="button"]:active,input[type="reset"]:active{color:#757575;background-color:#e1e1e1;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-ms-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-webkit-linear-gradient(top,#ebebeb,#e1e1e1);background-image:-o-linear-gradient(top,#ebebeb,#e1e1e1);background-image:linear-gradient(to bottom,#ebebeb,#e1e1e1);box-shadow:inset 0 0 8px 2px #c6c6c6,0 1px 0 0 #f4f4f4;border-color:transparent}.bypostauthor cite span{color:#fff;background-color:#21759b;background-image:none;border:1px solid #1f6f93;border-radius:2px;box-shadow:none;padding:0}.entry-content img,.comment-content img,.widget img{max-width:100%}img[class*="align"],img[class*="wp-image-"],img[class*="attachment-"]{height:auto}img.size-full,img.size-large,img.header-image,img.wp-post-image{max-width:100%;height:auto}embed,iframe,object,video{max-width:100%}.entry-content .twitter-tweet-rendered{max-width:100%!important}.alignleft{float:left}.alignright{float:right}.aligncenter{display:block;margin-left:auto;margin-right:auto}.entry-content img,.comment-content img,.widget img,img.header-image,.author-avatar img,img.wp-post-image{border-radius:3px;box-shadow:0 1px 4px rgba(0,0,0,0.2)}.wp-caption{max-width:100%;padding:4px}.wp-caption .wp-caption-text,.gallery-caption,.entry-caption{font-style:italic;font-size:12px;font-size:0.857142857rem;line-height:2;color:#757575}img.wp-smiley,.rsswidget img{border:0;border-radius:0;box-shadow:none;margin-bottom:0;margin-top:0;padding:0}.entry-content dl.gallery-item{margin:0}.gallery-item a,.gallery-caption{width:90%}.gallery-item a{display:block}.gallery-caption a{display:inline}.gallery-columns-1 .gallery-item a{max-width:100%;width:auto}.gallery .gallery-icon img{height:auto;max-width:90%;padding:5%}.gallery-columns-1 .gallery-icon img{padding:3%}.site-content nav{clear:both;line-height:2;overflow:hidden}#nav-above{padding:24px 0;padding:1.714285714rem 0}#nav-above{display:none}.paged #nav-above{display:block}.nav-previous,.previous-image{float:left;width:50%}.nav-next,.next-image{float:right;text-align:right;width:50%}.nav-single + .comments-area,#comment-nav-above{margin:48px 0;margin:3.428571429rem 0}.author .archive-header{margin-bottom:24px;margin-bottom:1.714285714rem}.author-info{border-top:1px solid #ededed;margin:24px 0;margin:1.714285714rem 0;padding-top:24px;padding-top:1.714285714rem;overflow:hidden}.author-description p{color:#757575;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.author.archive .author-info{border-top:0;margin:0 0 48px;margin:0 0 3.428571429rem}.author.archive .author-avatar{margin-top:0}html{font-size:87.5%}body{font-size:14px;font-size:1rem;font-family:Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility;color:#444}body.custom-font-enabled{font-family:"Open Sans",Helvetica,Arial,sans-serif}a{outline:none;color:#21759b}a:hover{color:#0f3647}.assistive-text,.site .screen-reader-text{position:absolute!important;clip:rect(1px,1px,1px,1px);overflow:hidden;height:1px;width:1px}.main-navigation .assistive-text:focus,.site .screen-reader-text:hover,.site .screen-reader-text:active,.site .screen-reader-text:focus{background:#fff;border:2px solid #333;border-radius:3px;clip:auto!important;color:#000;display:block;font-size:12px;height:auto;padding:12px;position:absolute;top:5px;left:5px;width:auto;z-index:100000}.site{padding:0 24px;padding:0 1.714285714rem;background-color:#fff}.site-content{margin:24px 0 0;margin:1.714285714rem 0 0}.widget-area{margin:24px 0 0;margin:1.714285714rem 0 0}.site-header{padding:24px 0;padding:1.714285714rem 0}.site-header h1,.site-header h2{text-align:center}.site-header h1 a,.site-header h2 a{color:#515151;display:inline-block;text-decoration:none}.site-header h1 a:hover,.site-header h2 a:hover{color:#21759b}.site-header h1{font-size:24px;font-size:1.714285714rem;line-height:1.285714286;margin-bottom:14px;margin-bottom:1rem}.site-header h2{font-weight:normal;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.header-image{margin-top:24px;margin-top:1.714285714rem}.main-navigation{margin-top:24px;margin-top:1.714285714rem;text-align:center}.main-navigation li{margin-top:24px;margin-top:1.714285714rem;font-size:12px;font-size:0.857142857rem;line-height:1.42857143}.main-navigation a{color:#5e5e5e}.main-navigation a:hover,.main-navigation a:focus{color:#21759b}.main-navigation ul.nav-menu,.main-navigation div.nav-menu>ul{display:none}.main-navigation ul.nav-menu.toggled-on,.menu-toggle{display:inline-block}section[role="banner"]{margin-bottom:48px;margin-bottom:3.428571429rem}.widget-area .widget{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;margin-bottom:48px;margin-bottom:3.428571429rem;word-wrap:break-word}.widget-area .widget h3{margin-bottom:24px;margin-bottom:1.714285714rem}.widget-area .widget p,.widget-area .widget li,.widget-area .widget .textwidget{font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.widget-area .widget p{margin-bottom:24px;margin-bottom:1.714285714rem}.widget-area .textwidget ul,.widget-area .textwidget ol{list-style:disc outside;margin:0 0 24px;margin:0 0 1.714285714rem}.widget-area .textwidget li>ul,.widget-area .textwidget li>ol{margin-bottom:0}.widget-area .textwidget ol{list-style:decimal}.widget-area .textwidget li{margin-left:36px;margin-left:2.571428571rem}.widget-area .widget a{color:#757575}.widget-area .widget a:hover{color:#21759b}.widget-area .widget a:visited{color:#9f9f9f}.widget-area #s{width:53.66666666666%}footer[role="contentinfo"]{border-top:1px solid #ededed;clear:both;font-size:12px;font-size:0.857142857rem;line-height:2;max-width:960px;max-width:68.571428571rem;margin-top:24px;margin-top:1.714285714rem;margin-left:auto;margin-right:auto;padding:24px 0;padding:1.714285714rem 0}footer[role="contentinfo"] a{color:#686868}footer[role="contentinfo"] a:hover{color:#21759b}.site-info span[role=separator]{padding:0 0.3em 0 0.6em}.site-info span[role=separator]::before{content:'\002f'}.entry-meta{clear:both}.entry-header{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-header img.wp-post-image{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-header .entry-title{font-size:20px;font-size:1.428571429rem;line-height:1.2;font-weight:normal}.entry-header .entry-title a{text-decoration:none}.entry-header .entry-format{margin-top:24px;margin-top:1.714285714rem;font-weight:normal}.entry-header .comments-link{margin-top:24px;margin-top:1.714285714rem;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.comments-link a,.entry-meta a{color:#757575}.comments-link a:hover,.entry-meta a:hover{color:#21759b}article.sticky .featured-post{border-top:4px double #ededed;border-bottom:4px double #ededed;color:#757575;font-size:13px;font-size:0.928571429rem;line-height:3.692307692;margin-bottom:24px;margin-bottom:1.714285714rem;text-align:center}.entry-content,.entry-summary,.mu_register{line-height:1.714285714}.entry-content h1,.comment-content h1,.entry-content h2,.comment-content h2,.entry-content h3,.comment-content h3,.entry-content h4,.comment-content h4,.entry-content h5,.comment-content h5,.entry-content h6,.comment-content h6{margin:24px 0;margin:1.714285714rem 0;line-height:1.714285714}.entry-content h1,.comment-content h1{font-size:21px;font-size:1.5rem;line-height:1.5}.entry-content h2,.comment-content h2,.mu_register h2{font-size:18px;font-size:1.285714286rem;line-height:1.6}.entry-content h3,.comment-content h3{font-size:16px;font-size:1.142857143rem;line-height:1.846153846}.entry-content h4,.comment-content h4{font-size:14px;font-size:1rem;line-height:1.846153846}.entry-content h5,.comment-content h5{font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.entry-content h6,.comment-content h6{font-size:12px;font-size:0.857142857rem;line-height:1.846153846}.entry-content p,.entry-summary p,.comment-content p,.mu_register p{margin:0 0 24px;margin:0 0 1.714285714rem;line-height:1.714285714}.entry-content a:visited,.comment-content a:visited{color:#9f9f9f}.entry-content .more-link{white-space:nowrap}.entry-content ol,.comment-content ol,.entry-content ul,.comment-content ul,.mu_register ul{margin:0 0 24px;margin:0 0 1.714285714rem;line-height:1.714285714}.entry-content ul ul,.comment-content ul ul,.entry-content ol ol,.comment-content ol ol,.entry-content ul ol,.comment-content ul ol,.entry-content ol ul,.comment-content ol ul{margin-bottom:0}.entry-content ul,.comment-content ul,.mu_register ul{list-style:disc outside}.entry-content ol,.comment-content ol{list-style:decimal outside}.entry-content li,.comment-content li,.mu_register li{margin:0 0 0 36px;margin:0 0 0 2.571428571rem}.entry-content blockquote,.comment-content blockquote{margin-bottom:24px;margin-bottom:1.714285714rem;padding:24px;padding:1.714285714rem;font-style:italic}.entry-content blockquote p:last-child,.comment-content blockquote p:last-child{margin-bottom:0}.entry-content code,.comment-content code{font-family:Consolas,Monaco,Lucida Console,monospace;font-size:12px;font-size:0.857142857rem;line-height:2}.entry-content pre,.comment-content pre{border:1px solid #ededed;color:#666;font-family:Consolas,Monaco,Lucida Console,monospace;font-size:12px;font-size:0.857142857rem;line-height:1.714285714;margin:24px 0;margin:1.714285714rem 0;overflow:auto;padding:24px;padding:1.714285714rem}.entry-content pre code,.comment-content pre code{display:block}.entry-content abbr,.comment-content abbr,.entry-content dfn,.comment-content dfn,.entry-content acronym,.comment-content acronym{border-bottom:1px dotted #666;cursor:help}.entry-content address,.comment-content address{display:block;line-height:1.714285714;margin:0 0 24px;margin:0 0 1.714285714rem}img.alignleft,.wp-caption.alignleft{margin:12px 24px 12px 0;margin:0.857142857rem 1.714285714rem 0.857142857rem 0}img.alignright,.wp-caption.alignright{margin:12px 0 12px 24px;margin:0.857142857rem 0 0.857142857rem 1.714285714rem}img.aligncenter,.wp-caption.aligncenter{clear:both;margin-top:12px;margin-top:0.857142857rem;margin-bottom:12px;margin-bottom:0.857142857rem}.entry-content embed,.entry-content iframe,.entry-content object,.entry-content video{margin-bottom:24px;margin-bottom:1.714285714rem}.entry-content dl,.comment-content dl{margin:0 24px;margin:0 1.714285714rem}.entry-content dt,.comment-content dt{font-weight:bold;line-height:1.714285714}.entry-content dd,.comment-content dd{line-height:1.714285714;margin-bottom:24px;margin-bottom:1.714285714rem}.entry-content table,.comment-content table{border-bottom:1px solid #ededed;color:#757575;font-size:12px;font-size:0.857142857rem;line-height:2;margin:0 0 24px;margin:0 0 1.714285714rem;width:100%}.entry-content table caption,.comment-content table caption{font-size:16px;font-size:1.142857143rem;margin:24px 0;margin:1.714285714rem 0}.entry-content td,.comment-content td{border-top:1px solid #ededed;padding:6px 10px 6px 0}.site-content article{border-bottom:4px double #ededed;margin-bottom:72px;margin-bottom:5.142857143rem;padding-bottom:24px;padding-bottom:1.714285714rem;word-wrap:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.page-links{clear:both;line-height:1.714285714}footer.entry-meta{margin-top:24px;margin-top:1.714285714rem;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#757575}.single-author .entry-meta .by-author{display:none}.mu_register h2{color:#757575;font-weight:normal}.archive-header,.page-header{margin-bottom:48px;margin-bottom:3.428571429rem;padding-bottom:22px;padding-bottom:1.571428571rem;border-bottom:1px solid #ededed}.archive-meta{color:#757575;font-size:12px;font-size:0.857142857rem;line-height:2;margin-top:22px;margin-top:1.571428571rem}.attachment .entry-content .mejs-audio{max-width:400px}.attachment .entry-content .mejs-container{margin-bottom:24px}.article.attachment{overflow:hidden}.image-attachment div.attachment{text-align:center}.image-attachment div.attachment p{text-align:center}.image-attachment div.attachment img{display:block;height:auto;margin:0 auto;max-width:100%}.image-attachment .entry-caption{margin-top:8px;margin-top:0.571428571rem}article.format-aside h1{margin-bottom:24px;margin-bottom:1.714285714rem}article.format-aside h1 a{text-decoration:none;color:#4d525a}article.format-aside h1 a:hover{color:#2e3542}article.format-aside .aside{padding:24px 24px 0;padding:1.714285714rem;background:#d2e0f9;border-left:22px solid #a8bfe8}article.format-aside p{font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#4a5466}article.format-aside blockquote:last-child,article.format-aside p:last-child{margin-bottom:0}article.format-image footer h1{font-size:13px;font-size:0.928571429rem;line-height:1.846153846;font-weight:normal}article.format-image footer h2{font-size:11px;font-size:0.785714286rem;line-height:2.181818182}article.format-image footer a h2{font-weight:normal}article.format-link header{padding:0 10px;padding:0 0.714285714rem;float:right;font-size:11px;font-size:0.785714286rem;line-height:2.181818182;font-weight:bold;font-style:italic;text-transform:uppercase;color:#848484;background-color:#ebebeb;border-radius:3px}article.format-link .entry-content{max-width:80%;float:left}article.format-link .entry-content a{font-size:22px;font-size:1.571428571rem;line-height:1.090909091;text-decoration:none}article.format-quote .entry-content p{margin:0;padding-bottom:24px;padding-bottom:1.714285714rem}article.format-quote .entry-content blockquote{display:block;padding:24px 24px 0;padding:1.714285714rem 1.714285714rem 0;font-size:15px;font-size:1.071428571rem;line-height:1.6;font-style:normal;color:#6a6a6a;background:#efefef}.format-status .entry-header{margin-bottom:24px;margin-bottom:1.714285714rem}.format-status .entry-header header{display:inline-block}.format-status .entry-header h1{font-size:15px;font-size:1.071428571rem;font-weight:normal;line-height:1.6;margin:0}.format-status .entry-header h2{font-size:12px;font-size:0.857142857rem;font-weight:normal;line-height:2;margin:0}.format-status .entry-header header a{color:#757575}.format-status .entry-header header a:hover{color:#21759b}.format-status .entry-header img{float:left;margin-right:21px;margin-right:1.5rem}.comments-title{margin-bottom:48px;margin-bottom:3.428571429rem;font-size:16px;font-size:1.142857143rem;line-height:1.5;font-weight:normal}.comments-area article{margin:24px 0;margin:1.714285714rem 0}.comments-area article header{margin:0 0 48px;margin:0 0 3.428571429rem;overflow:hidden;position:relative}.comments-area article header img{float:left;padding:0;line-height:0}.comments-area article header cite,.comments-area article header time{display:block;margin-left:85px;margin-left:6.071428571rem}.comments-area article header cite{font-style:normal;font-size:15px;font-size:1.071428571rem;line-height:1.42857143}.comments-area cite b{font-weight:normal}.comments-area article header time{line-height:1.714285714;text-decoration:none;font-size:12px;font-size:0.857142857rem;color:#5e5e5e}.comments-area article header a{text-decoration:none;color:#5e5e5e}.comments-area article header a:hover{color:#21759b}.comments-area article header cite a{color:#444}.comments-area article header cite a:hover{text-decoration:underline}.comments-area article header h4{position:absolute;top:0;right:0;padding:6px 12px;padding:0.428571429rem 0.857142857rem;font-size:12px;font-size:0.857142857rem;font-weight:normal;color:#fff;background-color:#0088d0;background-repeat:repeat-x;background-image:-moz-linear-gradient(top,#009cee,#0088d0);background-image:-ms-linear-gradient(top,#009cee,#0088d0);background-image:-webkit-linear-gradient(top,#009cee,#0088d0);background-image:-o-linear-gradient(top,#009cee,#0088d0);background-image:linear-gradient(to bottom,#009cee,#0088d0);border-radius:3px;border:1px solid #007cbd}.comments-area .bypostauthor cite span{position:absolute;margin-left:5px;margin-left:0.357142857rem;padding:2px 5px;padding:0.142857143rem 0.357142857rem;font-size:10px;font-size:0.714285714rem}.comments-area .bypostauthor cite b{font-weight:bold}a.comment-reply-link,a.comment-edit-link{color:#686868;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}a.comment-reply-link:hover,a.comment-edit-link:hover{color:#21759b}.commentlist .pingback{line-height:1.714285714;margin-bottom:24px;margin-bottom:1.714285714rem}#respond{margin-top:48px;margin-top:3.428571429rem}#respond h3#reply-title{font-size:16px;font-size:1.142857143rem;line-height:1.5}#respond h3#reply-title #cancel-comment-reply-link{margin-left:10px;margin-left:0.714285714rem;font-weight:normal;font-size:12px;font-size:0.857142857rem}#respond form{margin:24px 0;margin:1.714285714rem 0}#respond form p{margin:11px 0;margin:0.785714286rem 0}#respond form p.logged-in-as{margin-bottom:24px;margin-bottom:1.714285714rem}#respond form label{display:block;line-height:1.714285714}#respond form input[type="text"],#respond form textarea{-moz-box-sizing:border-box;box-sizing:border-box;font-size:12px;font-size:0.857142857rem;line-height:1.714285714;padding:10px;padding:0.714285714rem;width:100%}#respond form p.form-allowed-tags{margin:0;font-size:12px;font-size:0.857142857rem;line-height:2;color:#5e5e5e}#respond #wp-comment-cookies-consent{margin:0 10px 0 0}#respond .comment-form-cookies-consent label{display:inline}.required{color:red}.entry-page-image{margin-bottom:14px;margin-bottom:1rem}.template-front-page .site-content article{border:0;margin-bottom:0}.template-front-page .widget-area{clear:both;float:none;width:auto;padding-top:24px;padding-top:1.714285714rem;border-top:1px solid #ededed}.template-front-page .widget-area .widget li{margin:8px 0 0;margin:0.571428571rem 0 0;font-size:13px;font-size:0.928571429rem;line-height:1.714285714;list-style-type:square;list-style-position:inside}.template-front-page .widget-area .widget li a{color:#757575}.template-front-page .widget-area .widget li a:hover{color:#21759b}.template-front-page .widget-area .widget_text img{float:left;margin:8px 24px 8px 0;margin:0.571428571rem 1.714285714rem 0.571428571rem 0}.widget select{max-width:100%}.widget-area .widget ul ul{margin-left:12px;margin-left:0.857142857rem}.widget_rss li{margin:12px 0;margin:0.857142857rem 0}.widget_recent_entries .post-date,.widget_rss .rss-date{color:#aaa;font-size:11px;font-size:0.785714286rem;margin-left:12px;margin-left:0.857142857rem}.wp-calendar-nav,#wp-calendar{margin:0;width:100%;font-size:13px;font-size:0.928571429rem;line-height:1.846153846;color:#686868}#wp-calendar th,#wp-calendar td,#wp-calendar caption{text-align:left}.wp-calendar-nav{display:table}.wp-calendar-nav span{display:table-cell}.wp-calendar-nav-next,#wp-calendar #next{padding-right:24px;padding-right:1.714285714rem;text-align:right}.widget_search label{display:block;font-size:13px;font-size:0.928571429rem;line-height:1.846153846}.widget_twitter li{list-style-type:none}.widget_twitter .timesince{display:block;text-align:right}.tagcloud ul{list-style-type:none}.tagcloud ul li{display:inline-block}.widget-area .widget.widget_tag_cloud li{line-height:1}.template-front-page .widget-area .widget.widget_tag_cloud li{margin:0}.widget-area .gallery-columns-2.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-3.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-4.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-5.gallery-size-full .gallery-icon img,.widget-area .gallery-columns-6 .gallery-icon img,.widget-area .gallery-columns-7 .gallery-icon img,.widget-area .gallery-columns-8 .gallery-icon img,.widget-area .gallery-columns-9 .gallery-icon img{height:auto;max-width:80%}img#wpstats{display:block;margin:0 auto 24px;margin:0 auto 1.714285714rem}@-ms-viewport{width:device-width}@viewport{width:device-width}@media screen and (min-width:600px){.author-avatar{float:left;margin-top:8px;margin-top:0.571428571rem}.author-description{float:right;width:80%}.site{margin:0 auto;max-width:960px;max-width:68.571428571rem;overflow:hidden}.site-content{float:left;width:65.104166667%}body.template-front-page .site-content,body.attachment .site-content,body.full-width .site-content{width:100%}.widget-area{float:right;width:26.041666667%}.site-header h1,.site-header h2{text-align:left}.site-header h1{font-size:26px;font-size:1.857142857rem;line-height:1.846153846;margin-bottom:0}.main-navigation ul.nav-menu,.main-navigation div.nav-menu>ul{border-bottom:1px solid #ededed;border-top:1px solid #ededed;display:inline-block!important;text-align:left;width:100%}.main-navigation ul{margin:0;text-indent:0}.main-navigation li a,.main-navigation li{display:inline-block;text-decoration:none}.main-navigation li a{border-bottom:0;color:#6a6a6a;line-height:3.692307692;text-transform:uppercase;white-space:nowrap}.main-navigation li a:hover,.main-navigation li a:focus{color:#000}.main-navigation li{margin:0 40px 0 0;margin:0 2.857142857rem 0 0;position:relative}.main-navigation li ul{margin:0;padding:0;position:absolute;top:100%;z-index:1;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px)}.main-navigation li ul ul{top:0;left:100%}.main-navigation ul li:hover>ul,.main-navigation ul li:focus>ul,.main-navigation .focus>ul{border-left:0;clip:inherit;overflow:inherit;height:inherit;width:inherit}.main-navigation li ul li a{background:#efefef;border-bottom:1px solid #ededed;display:block;font-size:11px;font-size:0.785714286rem;line-height:2.181818182;padding:8px 10px;padding:0.571428571rem 0.714285714rem;width:180px;width:12.85714286rem;white-space:normal}.main-navigation li ul li a:hover,.main-navigation li ul li a:focus{background:#e3e3e3;color:#444}.main-navigation .current-menu-item>a,.main-navigation .current-menu-ancestor>a,.main-navigation .current_page_item>a,.main-navigation .current_page_ancestor>a{color:#636363;font-weight:bold}.menu-toggle{display:none}.entry-header .entry-title{font-size:22px;font-size:1.571428571rem}#respond form input[type="text"]{width:46.333333333%}#respond form textarea.blog-textarea{width:79.666666667%}.template-front-page .site-content,.template-front-page article{overflow:hidden}.template-front-page.has-post-thumbnail article{float:left;width:47.916666667%}.entry-page-image{float:right;margin-bottom:0;width:47.916666667%}.template-front-page .widget-area .widget,.template-front-page.two-sidebars .widget-area .front-widgets{float:left;width:51.875%;margin-bottom:24px;margin-bottom:1.714285714rem}.template-front-page .widget-area .widget:nth-child(odd){clear:right}.template-front-page .widget-area .widget:nth-child(even),.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets{float:right;width:39.0625%;margin:0 0 24px;margin:0 0 1.714285714rem}.template-front-page.two-sidebars .widget,.template-front-page.two-sidebars .widget:nth-child(even){float:none;width:auto}.commentlist .children{margin-left:48px;margin-left:3.428571429rem}}@media screen and (min-width:960px){body{background-color:#e6e6e6}body .site{padding:0 40px;padding:0 2.857142857rem;margin-top:48px;margin-top:3.428571429rem;margin-bottom:48px;margin-bottom:3.428571429rem;box-shadow:0 2px 6px rgba(100,100,100,0.3)}body.custom-background-empty{background-color:#fff}body.custom-background-empty .site,body.custom-background-white .site{padding:0;margin-top:0;margin-bottom:0;box-shadow:none}}@media print{body{background:none!important;color:#000;font-size:10pt}footer a[rel=bookmark]:link:after,footer a[rel=bookmark]:visited:after{content:" [" attr(href) "] "}a{text-decoration:none}.entry-content img,.comment-content img,.author-avatar img,img.wp-post-image{border-radius:0;box-shadow:none}.site{clear:both!important;display:block!important;float:none!important;max-width:100%;position:relative!important}.site-header{margin-bottom:72px;margin-bottom:5.142857143rem;text-align:left}.site-header h1{font-size:21pt;line-height:1;text-align:left}.site-header h2{color:#000;font-size:10pt;text-align:left}.site-header h1 a,.site-header h2 a{color:#000}.author-avatar,#colophon,#respond,.commentlist .comment-edit-link,.commentlist .reply,.entry-header .comments-link,.entry-meta .edit-link a,.page-link,.site-content nav,.widget-area,img.header-image,.main-navigation{display:none}.wrapper{border-top:none;box-shadow:none}.site-content{margin:0;width:auto}.entry-header .entry-title,.entry-title{font-size:21pt}footer.entry-meta,footer.entry-meta a{color:#444;font-size:10pt}.author-description{float:none;width:auto}.commentlist>li.comment{background:none;position:relative;width:auto}.comments-area article header cite,.comments-area article header time{margin-left:50px;margin-left:3.57142857rem}}.breadcrumb div{display:inline;font-size:13px;margin-left:-3px}#wp-auto-top{position:fixed;top:45%;right:50%;display:block;margin-right:-540px;z-index:9999}#wp-auto-top-top,#wp-auto-top-comment,#wp-auto-top-bottom{background:url(https://www.lylinux.org/wp-content/plugins/wp-auto-top/img/1.png) no-repeat;position:relative;cursor:pointer;height:25px;width:29px;margin:10px 0 0}#wp-auto-top-comment{background-position:left -30px;height:32px}#wp-auto-top-bottom{background-position:left -68px}#wp-auto-top-comment:hover{background-position:right -30px}#wp-auto-top-top:hover{background-position:right 0}#wp-auto-top-bottom:hover{background-position:right -68px}.widget-login{margin-top:15px!important}#comments{margin-top:20px}#pinglist-container{display:none}.comment-tabs{margin-bottom:20px;font-size:15px;border-bottom:2px solid #e5e5e5}.comment-tabs li{float:left;margin-bottom:-2px}.comment-tabs li a{display:block;padding:0 10px 10px;font-weight:600;color:#aaa;border-bottom:2px solid #e5e5e5}.comment-tabs li a:hover{color:#444;border-color:#ccc}.comment-tabs li span{margin-left:8px;padding:0 6px;border-radius:4px;background-color:#e5e5e5}.comment-tabs li i{margin-right:6px}.comment-tabs li.active a{color:#e8554e;border-bottom-color:#e8554e}.commentlist,.pinglist{margin-bottom:20px}.commentlist li,.pinglist li{padding-left:60px;font-size:14px;line-height:22px;font-weight:400}.commentlist .comment-body,.pinglist li{position:relative;padding-bottom:20px;clear:both;word-break:break-all}.commentlist .comment-body{position:relative;padding-left:60px;min-height:48px}.commentlist .comment-author{display:inline-block;margin:0 10px 5px 0;font-size:13px;position:relative}.commentlist .comment-meta{display:inline-block;margin:0 0 8px 0;font-size:12px;color:#666}.commentlist .comment-awaiting-moderation{display:block;font-size:13px;line-height:22px}.commentlist .comment-author .avatar{position:absolute!important;left:-60px;top:0;width:48px!important;height:48px!important;border-radius:50%;display:block;object-fit:cover;background-color:#f5f5f5;border:1px solid #ddd}.commentlist .comment-author .fn{display:inline;margin:0;font-weight:600;color:#2e7bb8;font-size:13px}.commentlist .comment-author .fn a{color:#2e7bb8;text-decoration:none}.commentlist .comment-author .fn a:hover{text-decoration:underline}.commentlist .comment-body p{margin:5px 0 10px 0;line-height:1.5}.commentlist .fn,.pinglist .ping-link{color:#444;font-size:13px;font-style:normal;font-weight:600}.commentlist .says{display:none}.commentlist .avatar{width:48px!important;height:48px!important;border-radius:50%;display:block;object-fit:cover;background-color:#f5f5f5;border:1px solid #ddd}.commentlist .comment-meta:before,.pinglist .ping-meta:before{vertical-align:4%;margin-right:3px;font-size:10px;font-family:FontAwesome;color:#ccc}.commentlist .comment-meta a,.pinglist .ping-meta{color:#aaa}.commentlist .reply{font-size:13px;line-height:16px}.commentlist .reply a,.commentlist .comment-reply-chain{color:#aaa}.commentlist .reply a:hover,.commentlist .comment-reply-chain:hover{color:#444}.comment-awaiting-moderation{color:#e8554e;font-style:normal}.pinglist li{padding-left:0}.commentlist .comment-body p{margin-bottom:8px;color:#777;clear:both}.commentlist .comment-body strong{font-weight:600}.commentlist .comment-body ol li{margin-left:2em;padding:0;list-style:decimal}.commentlist .comment-body ul li{margin-left:2em;padding:0;list-style:square}.commentlist li.bypostauthor>.comment-body:after,.commentlist li.comment-author-admin>.comment-body:after{display:block;position:absolute;content:"\f040";width:12px;line-height:12px;font-style:normal;font-family:FontAwesome;text-align:center;color:#fff;background-color:#e8554e}.commentlist li.comment-author-admin>.comment-body:after{content:"\f005"}.commentlist li.bypostauthor>.comment-body:after,.commentlist li.comment-author-admin>.comment-body:after{padding:3px;top:32px;left:-28px;font-size:12px;border-radius:100%}.commentlist li li.bypostauthor>.comment-body:after,.commentlist li li.comment-author-admin>.comment-body:after{padding:2px;top:22px;left:-26px;font-size:10px;border-radius:100%}.commentlist li ul{}.commentlist li li{margin:0;padding-left:48px}.commentlist li li .comment-body{padding-left:60px;min-height:48px}.commentlist li li .comment-author{display:inline-block;margin:0 8px 5px 0;font-size:12px}.commentlist li li .comment-meta{display:inline-block;margin:0 0 8px 0;font-size:11px;color:#666}#comments #commentlist-container.comment-tab{margin-left:-15px!important;padding-left:0!important;position:relative!important}@media screen and (min-width:600px){#comments #commentlist-container.comment-tab{margin-left:-30px!important}.commentlist .comment-body{padding-left:60px!important;min-height:48px!important}.commentlist .comment-author{display:inline-block!important;margin:0 8px 5px 0!important}.commentlist .comment-meta{display:inline-block!important;margin:0 0 8px 0!important}.commentlist .comment-author .avatar{left:-60px!important;width:48px!important;height:48px!important}.commentlist li li .comment-body{padding-left:60px!important;min-height:48px!important}.commentlist li li .comment-author .avatar{left:-60px!important;width:48px!important;height:48px!important}}.commentlist li li .comment-author .avatar{position:absolute!important;left:-60px;top:0;width:48px!important;height:48px!important;border-radius:50%;display:block;object-fit:cover;background-color:#f5f5f5;border:1px solid #ddd}.comments-nav{margin-bottom:20px}.comments-nav a{font-weight:600}.comments-nav .nav-previous{float:left}.comments-nav .nav-next{float:right}.logged-in-as,.comment-notes,.form-allowed-tags{display:none}#respond{position:relative}#reply-title{margin-bottom:20px}li #reply-title{margin:0!important;padding:0;height:0;font-size:0;border-top:0}#cancel-comment-reply-link{float:right;bottom:26px;right:20px;font-size:12px;color:#999}#cancel-comment-reply-link:hover{color:#777}#commentform{margin-bottom:20px;padding:10px 20px 20px;border-radius:4px;background-color:#e5e5e5}#commentform p.comment-form-author{float:left;width:48%}#commentform p.comment-form-email{float:right;width:48%}#commentform p.comment-form-url,#commentform p.comment-form-comment{clear:both}#commentform label{display:block;padding:6px 0;font-weight:600}#commentform input[type="text"],#commentform textarea{max-width:100%;width:100%}#commentform textarea{height:100px}#commentform p.form-submit{margin-top:10px}.logged-in #reply-title{margin-bottom:20px}.logged-in #commentform p.comment-form-comment{margin-top:10px}.logged-in #commentform p.comment-form-comment label{display:none}.heading,#reply-title{margin-bottom:1em;font-size:18px;font-weight:600;text-transform:uppercase;color:#222}.heading i{margin-right:6px;font-size:22px}.group:before{content:"";display:table}.group:after{content:"";display:table;clear:both}.cancel-comment{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}#rocket{position:fixed;right:50px;bottom:50px;display:block;visibility:hidden;width:26px;height:48px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAB8CAYAAAB356CJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAbdSURBVHja5NlbbBRVGAfw5VID+LAK8cEoxqTgmw8kPPhwipTGxJTDUAVBQBMNKtZboiDE2ES8pFEjGhNkkCrin3JbZo4YCqloUOoKJCDIRWyRAgW6R3dobU2bJtj6+eCMTqczs2d3Zh6Mm3xpdvc7++vMnHNmzvlSRJQqJgA8B8AC8EQx7YoBxgD4CAC54i0Ao2KDAIwCsNGDOPF6nNBLAYgTiyNDAKYDGCwA/Q7gtpIhAKMBHC+AOPF5FGiBIuLEXaVCR4uEzKIhAHcViRCAP4OuVRi0pgSIACwvFurw/ohhGJTP56m7u5vy+TwZhuEHHVKGANzmh3R3d48IH2wQwPWq0CIv5ByJN/L5vN9RzVKF3vQ29kOcULlOQZAZ8YjWq0JHI1wjAvClKnTJr+sq9joCcEoV6itxDDmRU4UoYvT8f6GeiFCXKpSLCJ1XhU5GhI6oQs0RoT2qUENESFeFlkeEXlCFZkeEqlWhWyNCtxSE7GdsPSL0AYAxgRCACQB2xzAzEAABYMIIyEYOxIQ4sR/AOC+UiRlxYvM/EID5CSFO1DjQoYShFmfFMJgwdC0FYHzCCAEYck5dZ8LQWQdCwpAe19xWKCocqAzA1YSQiwBGuwfs2yHJpwDcEBJHQtqu9s4MU0KSHy+wBF0c1NsATPabVL/ye6IBML4AVAbgik/bvUGz9zyf5HrFTY9VPm0XBkFlAH7xrN5uVYQmAuh3P0Q6M3fQje81V/LWIne+1gY9oPglTwLQai+Wby8SugnAj/Y2W7nqqnyUz2cagDb7P24DoAXshI2Nsl9XZXdXb/etintjMBswVrJxQ0H3rMG4oYEAaOA/e+rqAqC6uKHyAKg8VsjGDnqQg7Hve9tQrQeqTQpKuybOfgDpRCDParAhkZKBC5pmQ9MShWysvtg2RSOZTKYu0WqLYRhjTdMUQghqbGxMrtpimuYuIQQJIWj79u3JVFsMw3jHQYQQfhuC0asthmFUCiGG3JAQgjZv3hxftaW5uXmMEOJnLyKEoK1bt8ZXbTEMY5kfIoSgHTt2xFdtEUK0BkE7d+6Mp9piGMY9QYgQgkzTjKfaYprmJvcPn/vhOHV8+D511j5EuUWzqXPZEmpd9x59/102WrVFCPGrG7myopZkzUyS2ox/Ijf3bjq/8mkvpl5tMQzjDvfRdKx7l+TcmZR7bAH1nThGf167Rn0njlHn0gcoV1NJrWvXlFZtMQzjaTfU+eQSknMqqP+n0+R+9Z05RXJOBXUsW1xatcUwjAY3lLu/iuScCvJ7SW0GXVlUXVq1xTTN/cOghfcGH5E2w++I1Kot3vFzceP6vy++5xrlli6gXM1MOvOxXlq1RQiR946by6tXkpw7vNfJmko698qL1NzUVFq1RQgx4DdIL2z7lDqfephyD2l05dlH6ELjRj9EvdoSNiMozA7qtQlVSAjx34H6IkJdqlBXROi86oBtjwgdUYUOR4T2qEJmREhXnVTrI0IvqEJLIg7YalWoXAUKqSwXrrZIzsZIzvSfT5woCTr2zdckOftAchZcbZGcTZCc7ZacUfu+vQWhTCYzAjq9vZEkZyQ5E5KzkdUWGzlgJ9GFjetLgtrerXcgkpztl5yN80IZVwJdWvVMQcizqiAAdPHZR90QSc7+rbZIzuZ7vqTcfZXUdvp0KOR9/j78bQvlaiq9EEnOahzokM+X1P7FnlBoy5Ytw69P4yd+CEnOWlKSs9GSs0G/hI41bxQ1WNtffj4IupaSnI0P+JJyD1bT8aNHlbr24ZYWys2rCoKGnFPXGYS1N+1S6nFnPtaDEJKcnXUgBCVdfrHWF9q2bdswqGPZ4jBId6DZIUnUnm0J7Qgnd5lhCEnOKhyoTHJ2NSjx0qurQifTCytqw5CLkrPR7gH7dkhy6HaZ5OzbkLarvTPDlJDkRQWg+UG9TXI22W9S/conWUrOrisAjbVPkbft3qDZe55P8qsqmx6SsxU+bRcGQWWSs19ciX9Izm5WhG6UnPW52vY4M3fQje81V3JR1RbJ2Vr32Cl0h50kOWuVnHVIzm4vErpJcvaj5MySnKlVWyRnw7bHLF1L9WbTWm823dabTZP9V7N0bUQ7yVnp1RZL16p69k0eshHqzaapZ9/kIUvX4q22WLqW7cpMJzfUlZlOlq5l44YGrQ3VwyBrQzVZujYYNzRg6Rr1tkz8G2qZSJaukaVrA7GfOkvX6LemqdSbTdNvTVMdKPZTV2fpGl3dNIt6s2m6ummWA9XFDZXbP0zdn93pIGTpWnncUMrStYMugOz3qSSgWg9UmxSUtnSt30b67feJQClL1xpsqMH5LClomg1NSxpKWbpW736v0v6vAQCo4CbBrd8RBQAAAABJRU5ErkJggg==") no-repeat 50% 0;cursor:pointer;-webkit-transition:all 0s;transition:all 0s}#rocket:hover{background-position:50% -62px}#rocket.show{visibility:visible;opacity:1}#rocket.move{background-position:50% -62px;-webkit-animation:toTop .8s ease-in;animation:toTop .8s ease-in;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.comment-markdown{float:right;font-size:small}.breadcrumb{margin-bottom:20px;list-style:none;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li + li:before{color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.break_line{height:1px;border:none}.comment-body{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;max-width:100%;box-sizing:border-box}.comment-content pre,.comment-body pre{white-space:pre-wrap!important;word-wrap:break-word!important;overflow-wrap:break-word!important;max-width:100%!important;overflow-x:auto;padding:10px;background-color:#f8f8f8;border:1px solid #ddd;border-radius:4px;font-size:12px;line-height:1.4;margin:10px 0}.comment-content code,.comment-body code{word-wrap:break-word!important;overflow-wrap:break-word!important;white-space:pre-wrap;max-width:100%;display:inline-block;vertical-align:top}.comment-content a,.comment-body a{word-wrap:break-word!important;overflow-wrap:break-word!important;word-break:break-all;max-width:100%}.comment-content p,.comment-body p{word-wrap:break-word!important;overflow-wrap:break-word!important;max-width:100%;margin:10px 0}.comment-content .codehilite,.comment-body .codehilite{max-width:100%!important;overflow-x:auto;margin:10px 0;background:#f8f8f8!important;border:1px solid #ddd;border-radius:4px;padding:10px;font-size:12px;line-height:1.4;width:100%;box-sizing:border-box;display:block}.comment-content .codehilite pre,.comment-body .codehilite pre{white-space:pre-wrap!important;word-wrap:break-word!important;overflow-wrap:break-word!important;margin:0!important;padding:0!important;background:transparent!important;border:none!important;font-size:inherit;line-height:inherit;max-width:100%;width:100%;box-sizing:border-box}.comment-content .codehilite span,.comment-body .codehilite span{word-wrap:break-word!important;overflow-wrap:break-word!important;display:inline;max-width:100%}.comment-content .codehilite .kt,.comment-content .codehilite .nf,.comment-content .codehilite .n,.comment-content .codehilite .p,.comment-body .codehilite .kt,.comment-body .codehilite .nf,.comment-body .codehilite .n,.comment-body .codehilite .p{word-wrap:break-word!important;overflow-wrap:break-word!important}.search-result{margin-bottom:30px;padding:20px;border:1px solid #e1e1e1;border-radius:5px;background:#fff}.search-result .entry-title{margin:0 0 10px 0;font-size:1.5em}.search-result .entry-title a{color:#2c3e50;text-decoration:none}.search-result .entry-title a:hover{color:#3498db}.search-result .entry-meta{color:#7f8c8d;font-size:0.9em;margin-bottom:15px}.search-result .entry-meta span{margin-right:15px}.search-excerpt{line-height:1.6;color:#555}.search-excerpt p{margin:10px 0}.search-excerpt em,.search-result .entry-title em{background-color:#fff3cd;color:#856404;font-style:normal;font-weight:bold;padding:2px 4px;border-radius:3px}.more-link{color:#3498db;text-decoration:none;font-weight:bold}.more-link:hover{text-decoration:underline}.comment-content .codehilite .w,.comment-content .codehilite .o,.comment-body .codehilite .kt,.comment-body .codehilite .nf,.comment-body .codehilite .n,.comment-body .codehilite .p,.comment-body .codehilite .w,.comment-body .codehilite .o{word-break:break-all;overflow-wrap:break-word}.commentlist li{max-width:100%;overflow:hidden;box-sizing:border-box}.commentlist .comment-body{max-width:calc(100% - 20px);margin-left:10px;margin-right:10px;overflow:hidden;word-wrap:break-word}.commentlist li[style*="margin-left"]{max-width:calc(100% - 2rem)!important;overflow:hidden;box-sizing:border-box}.commentlist li[style*="margin-left: 3rem"],.commentlist li[style*="margin-left: 6rem"],.commentlist li[style*="margin-left: 9rem"]{max-width:calc(100% - 1rem)!important}@media (max-width:768px){.comment-content pre,.comment-body pre{font-size:11px;padding:8px;margin:8px 0}.commentlist .comment-body{max-width:calc(100% - 10px);margin-left:5px;margin-right:5px}.commentlist li[style*="margin-left"]{margin-left:1rem!important;max-margin-left:2rem!important}}.comment-content table,.comment-body table{max-width:100%;overflow-x:auto;display:block;white-space:nowrap}.comment-content img,.comment-body img{max-width:100%!important;height:auto!important}.comment-content blockquote,.comment-body blockquote{max-width:100%;overflow-wrap:break-word;word-wrap:break-word;padding:10px 15px;margin:10px 0;border-left:4px solid #ddd;background-color:#f9f9f9} \ No newline at end of file diff --git a/src/collectedstatic/compressed/js/output.c56f0a57c4ca.js b/src/collectedstatic/compressed/js/output.c56f0a57c4ca.js new file mode 100644 index 0000000..7c28331 --- /dev/null +++ b/src/collectedstatic/compressed/js/output.c56f0a57c4ca.js @@ -0,0 +1,42 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0
'};NProgress.configure=function(options){var key,value;for(key in options){value=options[key];if(value!==undefined&&options.hasOwnProperty(key))Settings[key]=value;} +return this;};NProgress.status=null;NProgress.set=function(n){var started=NProgress.isStarted();n=clamp(n,Settings.minimum,1);NProgress.status=(n===1?null:n);var progress=NProgress.render(!started),bar=progress.querySelector(Settings.barSelector),speed=Settings.speed,ease=Settings.easing;progress.offsetWidth;queue(function(next){if(Settings.positionUsing==='')Settings.positionUsing=NProgress.getPositioningCSS();css(bar,barPositionCSS(n,speed,ease));if(n===1){css(progress,{transition:'none',opacity:1});progress.offsetWidth;setTimeout(function(){css(progress,{transition:'all '+speed+'ms linear',opacity:0});setTimeout(function(){NProgress.remove();next();},speed);},speed);}else{setTimeout(next,speed);}});return this;};NProgress.isStarted=function(){return typeof NProgress.status==='number';};NProgress.start=function(){if(!NProgress.status)NProgress.set(0);var work=function(){setTimeout(function(){if(!NProgress.status)return;NProgress.trickle();work();},Settings.trickleSpeed);};if(Settings.trickle)work();return this;};NProgress.done=function(force){if(!force&&!NProgress.status)return this;return NProgress.inc(0.3+0.5*Math.random()).set(1);};NProgress.inc=function(amount){var n=NProgress.status;if(!n){return NProgress.start();}else if(n>1){}else{if(typeof amount!=='number'){if(n>=0&&n<0.2){amount=0.1;} +else if(n>=0.2&&n<0.5){amount=0.04;} +else if(n>=0.5&&n<0.8){amount=0.02;} +else if(n>=0.8&&n<0.99){amount=0.005;} +else{amount=0;}} +n=clamp(n+amount,0,0.994);return NProgress.set(n);}};NProgress.trickle=function(){return NProgress.inc();};(function(){var initial=0,current=0;NProgress.promise=function($promise){if(!$promise||$promise.state()==="resolved"){return this;} +if(current===0){NProgress.start();} +initial++;current++;$promise.always(function(){current--;if(current===0){initial=0;NProgress.done();}else{NProgress.set((initial-current)/initial);}});return this;};})();NProgress.render=function(fromStart){if(NProgress.isRendered())return document.getElementById('nprogress');addClass(document.documentElement,'nprogress-busy');var progress=document.createElement('div');progress.id='nprogress';progress.innerHTML=Settings.template;var bar=progress.querySelector(Settings.barSelector),perc=fromStart?'-100':toBarPerc(NProgress.status||0),parent=document.querySelector(Settings.parent),spinner;css(bar,{transition:'all 0 linear',transform:'translate3d('+perc+'%,0,0)'});if(!Settings.showSpinner){spinner=progress.querySelector(Settings.spinnerSelector);spinner&&removeElement(spinner);} +if(parent!=document.body){addClass(parent,'nprogress-custom-parent');} +parent.appendChild(progress);return progress;};NProgress.remove=function(){removeClass(document.documentElement,'nprogress-busy');removeClass(document.querySelector(Settings.parent),'nprogress-custom-parent');var progress=document.getElementById('nprogress');progress&&removeElement(progress);};NProgress.isRendered=function(){return!!document.getElementById('nprogress');};NProgress.getPositioningCSS=function(){var bodyStyle=document.body.style;var vendorPrefix=('WebkitTransform'in bodyStyle)?'Webkit':('MozTransform'in bodyStyle)?'Moz':('msTransform'in bodyStyle)?'ms':('OTransform'in bodyStyle)?'O':'';if(vendorPrefix+'Perspective'in bodyStyle){return'translate3d';}else if(vendorPrefix+'Transform'in bodyStyle){return'translate';}else{return'margin';}};function clamp(n,min,max){if(nmax)return max;return n;} +function toBarPerc(n){return(-1+n)*100;} +function barPositionCSS(n,speed,ease){var barCSS;if(Settings.positionUsing==='translate3d'){barCSS={transform:'translate3d('+toBarPerc(n)+'%,0,0)'};}else if(Settings.positionUsing==='translate'){barCSS={transform:'translate('+toBarPerc(n)+'%,0)'};}else{barCSS={'margin-left':toBarPerc(n)+'%'};} +barCSS.transition='all '+speed+'ms '+ease;return barCSS;} +var queue=(function(){var pending=[];function next(){var fn=pending.shift();if(fn){fn(next);}} +return function(fn){pending.push(fn);if(pending.length==1)next();};})();var css=(function(){var cssPrefixes=['Webkit','O','Moz','ms'],cssProps={};function camelCase(string){return string.replace(/^-ms-/,'ms-').replace(/-([\da-z])/gi,function(match,letter){return letter.toUpperCase();});} +function getVendorProp(name){var style=document.body.style;if(name in style)return name;var i=cssPrefixes.length,capName=name.charAt(0).toUpperCase()+name.slice(1),vendorName;while(i--){vendorName=cssPrefixes[i]+capName;if(vendorName in style)return vendorName;} +return name;} +function getStyleProp(name){name=camelCase(name);return cssProps[name]||(cssProps[name]=getVendorProp(name));} +function applyCss(element,prop,value){prop=getStyleProp(prop);element.style[prop]=value;} +return function(element,properties){var args=arguments,prop,value;if(args.length==2){for(prop in properties){value=properties[prop];if(value!==undefined&&properties.hasOwnProperty(prop))applyCss(element,prop,value);}}else{applyCss(element,args[1],args[2]);}}})();function hasClass(element,name){var list=typeof element=='string'?element:classList(element);return list.indexOf(' '+name+' ')>=0;} +function addClass(element,name){var oldList=classList(element),newList=oldList+name;if(hasClass(oldList,name))return;element.className=newList.substring(1);} +function removeClass(element,name){var oldList=classList(element),newList;if(!hasClass(element,name))return;newList=oldList.replace(' '+name+' ',' ');element.className=newList.substring(1,newList.length-1);} +function classList(element){return(' '+(element&&element.className||'')+' ').replace(/\s+/gi,' ');} +function removeElement(element){element&&element.parentNode&&element.parentNode.removeChild(element);} +return NProgress;});;function do_reply(parentid){console.log(parentid);$("#id_parent_comment_id").val(parentid) +$("#commentform").appendTo($("#div-comment-"+parentid));$("#reply-title").hide();$("#cancel_comment").show();} +function cancel_reply(){$("#reply-title").show();$("#cancel_comment").hide();$("#id_parent_comment_id").val('') +$("#commentform").appendTo($("#respond"));} +NProgress.start();NProgress.set(0.4);var interval=setInterval(function(){NProgress.inc();},1000);$(document).ready(function(){NProgress.done();clearInterval(interval);});var rocket=$('#rocket');$(window).on('scroll',debounce(slideTopSet,300));function debounce(func,wait){var timeout;return function(){clearTimeout(timeout);timeout=setTimeout(func,wait);};} +function slideTopSet(){var top=$(document).scrollTop();if(top>200){rocket.addClass('show');}else{rocket.removeClass('show');}} +$(document).on('click','#rocket',function(event){rocket.addClass('move');$('body, html').animate({scrollTop:0},800);});$(document).on('animationEnd',function(){setTimeout(function(){rocket.removeClass('move');},400);});$(document).on('webkitAnimationEnd',function(){setTimeout(function(){rocket.removeClass('move');},400);});window.onload=function(){var replyLinks=document.querySelectorAll(".comment-reply-link");for(var i=0;i a, .page_item_has_children > a',function(e){var el=$(this).parent('li');if(!el.hasClass('focus')){e.preventDefault();el.toggleClass('focus');el.siblings('.focus').removeClass('focus');}});}})(jQuery);;(function(){'use strict';document.addEventListener('DOMContentLoaded',function(){initRecommendations();});function initRecommendations(){trackRecommendationClicks();lazyLoadRecommendations();} +function trackRecommendationClicks(){const recommendationLinks=document.querySelectorAll('.recommendation-item a');recommendationLinks.forEach(function(link){link.addEventListener('click',function(e){const articleTitle=this.textContent.trim();const articleUrl=this.href;if(typeof gtag!=='undefined'){gtag('event','click',{'event_category':'recommendation','event_label':articleTitle,'value':1});} +console.log('Recommendation clicked:',articleTitle,articleUrl);});});} +function lazyLoadRecommendations(){const recommendationContainer=document.querySelector('.article-recommendations');if(!recommendationContainer){return;} +const observer=new IntersectionObserver(function(entries){entries.forEach(function(entry){if(entry.isIntersecting){entry.target.classList.add('loaded');observer.unobserve(entry.target);}});},{threshold:0.1});const recommendationItems=document.querySelectorAll('.recommendation-item');recommendationItems.forEach(function(item){observer.observe(item);});} +function addAnimations(){const recommendationItems=document.querySelectorAll('.recommendation-item');recommendationItems.forEach(function(item,index){item.style.opacity='0';item.style.transform='translateY(20px)';item.style.transition='opacity 0.5s ease, transform 0.5s ease';setTimeout(function(){item.style.opacity='1';item.style.transform='translateY(0)';},index*100);});} +window.ArticleRecommendation={init:initRecommendations,track:trackRecommendationClicks,animate:addAnimations};})();; \ No newline at end of file diff --git a/src/comments/__init__.py b/src/comments/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/comments/__pycache__/__init__.cpython-311.pyc b/src/comments/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..728d159 Binary files /dev/null and b/src/comments/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/comments/__pycache__/admin.cpython-311.pyc b/src/comments/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..ab8ac24 Binary files /dev/null and b/src/comments/__pycache__/admin.cpython-311.pyc differ diff --git a/src/comments/__pycache__/apps.cpython-311.pyc b/src/comments/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..643ad39 Binary files /dev/null and b/src/comments/__pycache__/apps.cpython-311.pyc differ diff --git a/src/comments/__pycache__/forms.cpython-311.pyc b/src/comments/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..76e09d5 Binary files /dev/null and b/src/comments/__pycache__/forms.cpython-311.pyc differ diff --git a/src/comments/__pycache__/models.cpython-311.pyc b/src/comments/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..ae6d064 Binary files /dev/null and b/src/comments/__pycache__/models.cpython-311.pyc differ diff --git a/src/comments/__pycache__/urls.cpython-311.pyc b/src/comments/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..1989e5a Binary files /dev/null and b/src/comments/__pycache__/urls.cpython-311.pyc differ diff --git a/src/comments/__pycache__/utils.cpython-311.pyc b/src/comments/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..368d9e1 Binary files /dev/null and b/src/comments/__pycache__/utils.cpython-311.pyc differ diff --git a/src/comments/__pycache__/views.cpython-311.pyc b/src/comments/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..3b9cd58 Binary files /dev/null and b/src/comments/__pycache__/views.cpython-311.pyc differ diff --git a/src/comments/admin.py b/src/comments/admin.py index 5622781..e3925bf 100644 --- a/src/comments/admin.py +++ b/src/comments/admin.py @@ -29,6 +29,8 @@ class CommentAdmin(admin.ModelAdmin): list_filter = ('is_enable', 'author', 'article',) exclude = ('creation_time', 'last_modify_time') actions = [disable_commentstatus, enable_commentstatus] + raw_id_fields = ('author', 'article') + search_fields = ('body',) def link_to_userinfo(self, obj): info = (obj.author._meta.app_label, obj.author._meta.model_name) diff --git a/src/comments/apps.py b/src/comments/apps.py deleted file mode 100644 index ff01b77..0000000 --- a/src/comments/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class CommentsConfig(AppConfig): - name = 'comments' diff --git a/src/comments/forms.py b/src/comments/forms.py deleted file mode 100644 index e83737d..0000000 --- a/src/comments/forms.py +++ /dev/null @@ -1,13 +0,0 @@ -from django import forms -from django.forms import ModelForm - -from .models import Comment - - -class CommentForm(ModelForm): - parent_comment_id = forms.IntegerField( - widget=forms.HiddenInput, required=False) - - class Meta: - model = Comment - fields = ['body'] diff --git a/src/comments/migrations/0001_initial.py b/src/comments/migrations/0001_initial.py deleted file mode 100644 index 61d1e53..0000000 --- a/src/comments/migrations/0001_initial.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('blog', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('body', models.TextField(max_length=300, verbose_name='正文')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), - ], - options={ - 'verbose_name': '评论', - 'verbose_name_plural': '评论', - 'ordering': ['-id'], - 'get_latest_by': 'id', - }, - ), - ] diff --git a/src/comments/migrations/0002_alter_comment_is_enable.py b/src/comments/migrations/0002_alter_comment_is_enable.py deleted file mode 100644 index 17c44db..0000000 --- a/src/comments/migrations/0002_alter_comment_is_enable.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-24 13:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('comments', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='是否显示'), - ), - ] diff --git a/src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py deleted file mode 100644 index a1ca970..0000000 --- a/src/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ('comments', '0002_alter_comment_is_enable'), - ] - - operations = [ - migrations.AlterModelOptions( - name='comment', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, - ), - migrations.RemoveField( - model_name='comment', - name='created_time', - ), - migrations.RemoveField( - model_name='comment', - name='last_mod_time', - ), - migrations.AddField( - model_name='comment', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='comment', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='comment', - name='article', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), - ), - migrations.AlterField( - model_name='comment', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='enable'), - ), - migrations.AlterField( - model_name='comment', - name='parent_comment', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), - ), - ] diff --git a/src/comments/migrations/__init__.py b/src/comments/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/comments/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/comments/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..2679ff5 Binary files /dev/null and b/src/comments/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-311.pyc b/src/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-311.pyc new file mode 100644 index 0000000..7ff0895 Binary files /dev/null and b/src/comments/migrations/__pycache__/0002_alter_comment_is_enable.cpython-311.pyc differ diff --git a/src/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-311.pyc b/src/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-311.pyc new file mode 100644 index 0000000..97f76df Binary files /dev/null and b/src/comments/migrations/__pycache__/0003_alter_comment_options_remove_comment_created_time_and_more.cpython-311.pyc differ diff --git a/src/comments/migrations/__pycache__/__init__.cpython-311.pyc b/src/comments/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..4902e16 Binary files /dev/null and b/src/comments/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/comments/models.py b/src/comments/models.py deleted file mode 100644 index 7c3bbc8..0000000 --- a/src/comments/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.conf import settings -from django.db import models -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ - -from blog.models import Article - - -# Create your models here. - -class Comment(models.Model): - body = models.TextField('正文', max_length=300) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - on_delete=models.CASCADE) - article = models.ForeignKey( - Article, - verbose_name=_('article'), - on_delete=models.CASCADE) - parent_comment = models.ForeignKey( - 'self', - verbose_name=_('parent comment'), - blank=True, - null=True, - on_delete=models.CASCADE) - is_enable = models.BooleanField(_('enable'), - default=False, blank=False, null=False) - - class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name - get_latest_by = 'id' - - def __str__(self): - return self.body diff --git a/src/comments/templatetags/__init__.py b/src/comments/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/comments/templatetags/__pycache__/__init__.cpython-311.pyc b/src/comments/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..3fb7b3c Binary files /dev/null and b/src/comments/templatetags/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/comments/templatetags/__pycache__/comments_tags.cpython-311.pyc b/src/comments/templatetags/__pycache__/comments_tags.cpython-311.pyc new file mode 100644 index 0000000..d2f2e94 Binary files /dev/null and b/src/comments/templatetags/__pycache__/comments_tags.cpython-311.pyc differ diff --git a/src/comments/templatetags/comments_tags.py b/src/comments/templatetags/comments_tags.py deleted file mode 100644 index fde02b4..0000000 --- a/src/comments/templatetags/comments_tags.py +++ /dev/null @@ -1,30 +0,0 @@ -from django import template - -register = template.Library() - - -@register.simple_tag -def parse_commenttree(commentlist, comment): - """获得当前评论子评论的列表 - 用法: {% parse_commenttree article_comments comment as childcomments %} - """ - datas = [] - - def parse(c): - childs = commentlist.filter(parent_comment=c, is_enable=True) - for child in childs: - datas.append(child) - parse(child) - - parse(comment) - return datas - - -@register.inclusion_tag('comments/tags/comment_item.html') -def show_comment_item(comment, ischild): - """评论""" - depth = 1 if ischild else 2 - return { - 'comment_item': comment, - 'depth': depth - } diff --git a/src/comments/tests.py b/src/comments/tests.py deleted file mode 100644 index 2a7f55f..0000000 --- a/src/comments/tests.py +++ /dev/null @@ -1,109 +0,0 @@ -from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse - -from accounts.models import BlogUser -from blog.models import Category, Article -from comments.models import Comment -from comments.templatetags.comments_tags import * -from djangoblog.utils import get_max_articleid_commentid - - -# Create your tests here. - -class CommentsTest(TransactionTestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - from blog.models import BlogSettings - value = BlogSettings() - value.comment_need_review = True - value.save() - - self.user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - def update_article_comment_status(self, article): - comments = article.comment_set.all() - for comment in comments: - comment.is_enable = True - comment.save() - - def test_validate_comment(self): - self.client.login(username='liangliangyy1', password='liangliangyy1') - - category = Category() - category.name = "categoryccc" - category.save() - - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = self.user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - - comment_url = reverse( - 'comments:postcomment', kwargs={ - 'article_id': article.id}) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 0) - self.update_article_comment_status(article) - - self.assertEqual(len(article.comment_list()), 1) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - - article = Article.objects.get(pk=article.pk) - self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].id - - response = self.client.post(comment_url, - { - 'body': ''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) - - self.assertEqual(response.status_code, 302) - self.update_article_comment_status(article) - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 3) - comment = Comment.objects.get(id=parent_comment_id) - tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) - data = show_comment_item(comment, True) - self.assertIsNotNone(data) - s = get_max_articleid_commentid() - self.assertIsNotNone(s) - - from comments.utils import send_comment_email - send_comment_email(comment) diff --git a/src/comments/urls.py b/src/comments/urls.py deleted file mode 100644 index 7df3fab..0000000 --- a/src/comments/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "comments" -urlpatterns = [ - path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'), -] diff --git a/src/comments/utils.py b/src/comments/utils.py deleted file mode 100644 index f01dba7..0000000 --- a/src/comments/utils.py +++ /dev/null @@ -1,38 +0,0 @@ -import logging - -from django.utils.translation import gettext_lazy as _ - -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email - -logger = logging.getLogger(__name__) - - -def send_comment_email(comment): - site = get_current_site().domain - subject = _('Thanks for your comment') - article_url = f"https://{site}{comment.article.get_absolute_url()}" - html_content = _("""

Thank you very much for your comments on this site

- You can visit %(article_title)s - to review your comments, - Thank you again! -
- If the link above cannot be opened, please copy this link to your browser. - %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title} - tomail = comment.author.email - send_email([tomail], subject, html_content) - try: - if comment.parent_comment: - html_content = _("""Your comment on %(article_title)s
has - received a reply.
%(comment_body)s -
- go check it out! -
- If the link above cannot be opened, please copy this link to your browser. - %(article_url)s - """) % {'article_url': article_url, 'article_title': comment.article.title, - 'comment_body': comment.parent_comment.body} - tomail = comment.parent_comment.author.email - send_email([tomail], subject, html_content) - except Exception as e: - logger.error(e) diff --git a/src/comments/views.py b/src/comments/views.py deleted file mode 100644 index ad9b2b9..0000000 --- a/src/comments/views.py +++ /dev/null @@ -1,63 +0,0 @@ -# Create your views here. -from django.core.exceptions import ValidationError -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.generic.edit import FormView - -from accounts.models import BlogUser -from blog.models import Article -from .forms import CommentForm -from .models import Comment - - -class CommentPostView(FormView): - form_class = CommentForm - template_name = 'blog/article_detail.html' - - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - return super(CommentPostView, self).dispatch(*args, **kwargs) - - def get(self, request, *args, **kwargs): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - url = article.get_absolute_url() - return HttpResponseRedirect(url + "#comments") - - def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - return self.render_to_response({ - 'form': form, - 'article': article - }) - - def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - user = self.request.user - author = BlogUser.objects.get(pk=user.pk) - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - if article.comment_status == 'c' or article.status == 'c': - raise ValidationError("该文章评论已关闭.") - comment = form.save(False) - comment.article = article - from djangoblog.utils import get_blog_setting - settings = get_blog_setting() - if not settings.comment_need_review: - comment.is_enable = True - comment.author = author - - if form.cleaned_data['parent_comment_id']: - parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment - - comment.save(True) - return HttpResponseRedirect( - "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) diff --git a/src/deploy/docker-compose/docker-compose.es.yml b/src/deploy/docker-compose/docker-compose.es.yml deleted file mode 100644 index 83e35ff..0000000 --- a/src/deploy/docker-compose/docker-compose.es.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: '3' - -services: - es: - image: liangliangyy/elasticsearch-analysis-ik:8.6.1 - container_name: es - restart: always - environment: - - discovery.type=single-node - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - ports: - - 9200:9200 - volumes: - - ./bin/datas/es/:/usr/share/elasticsearch/data/ - - kibana: - image: kibana:8.6.1 - restart: always - container_name: kibana - ports: - - 5601:5601 - environment: - - ELASTICSEARCH_HOSTS=http://es:9200 - - djangoblog: - build: . - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: - - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_MEMCACHED_LOCATION=memcached:11211 - - DJANGO_ELASTICSEARCH_HOST=es:9200 - links: - - db - - memcached - depends_on: - - db - container_name: djangoblog - diff --git a/src/deploy/docker-compose/docker-compose.yml b/src/deploy/docker-compose/docker-compose.yml deleted file mode 100644 index 9609af3..0000000 --- a/src/deploy/docker-compose/docker-compose.yml +++ /dev/null @@ -1,60 +0,0 @@ -version: '3' - -services: - db: - image: mysql:latest - restart: always - environment: - - MYSQL_DATABASE=djangoblog - - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E - ports: - - 3306:3306 - volumes: - - ./bin/datas/mysql/:/var/lib/mysql - depends_on: - - redis - container_name: db - - djangoblog: - build: - context: ../../ - restart: always - command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' - ports: - - "8000:8000" - volumes: - - ./collectedstatic:/code/djangoblog/collectedstatic - - ./logs:/code/djangoblog/logs - - ./uploads:/code/djangoblog/uploads - environment: - - DJANGO_MYSQL_DATABASE=djangoblog - - DJANGO_MYSQL_USER=root - - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E - - DJANGO_MYSQL_HOST=db - - DJANGO_MYSQL_PORT=3306 - - DJANGO_REDIS_URL=redis:6379 - links: - - db - - redis - depends_on: - - db - container_name: djangoblog - nginx: - restart: always - image: nginx:latest - ports: - - "80:80" - - "443:443" - volumes: - - ./bin/nginx.conf:/etc/nginx/nginx.conf - - ./collectedstatic:/code/djangoblog/collectedstatic - links: - - djangoblog:djangoblog - container_name: nginx - - redis: - restart: always - image: redis:latest - container_name: redis - ports: - - "6379:6379" diff --git a/src/deploy/entrypoint.sh b/src/deploy/entrypoint.sh deleted file mode 100644 index 2fb6491..0000000 --- a/src/deploy/entrypoint.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -NAME="djangoblog" -DJANGODIR=/code/djangoblog -USER=root -GROUP=root -NUM_WORKERS=1 -DJANGO_WSGI_MODULE=djangoblog.wsgi - - -echo "Starting $NAME as `whoami`" - -cd $DJANGODIR - -export PYTHONPATH=$DJANGODIR:$PYTHONPATH - -python manage.py makemigrations && \ - python manage.py migrate && \ - python manage.py collectstatic --noinput && \ - python manage.py compress --force && \ - python manage.py build_index && \ - python manage.py compilemessages || exit 1 - -exec gunicorn ${DJANGO_WSGI_MODULE}:application \ ---name $NAME \ ---workers $NUM_WORKERS \ ---user=$USER --group=$GROUP \ ---bind 0.0.0.0:8000 \ ---log-level=debug \ ---log-file=- \ ---worker-class gevent \ ---threads 4 diff --git a/src/deploy/k8s/configmap.yaml b/src/deploy/k8s/configmap.yaml deleted file mode 100644 index 835d4ad..0000000 --- a/src/deploy/k8s/configmap.yaml +++ /dev/null @@ -1,119 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: web-nginx-config - namespace: djangoblog -data: - nginx.conf: | - user nginx; - worker_processes auto; - error_log /var/log/nginx/error.log notice; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - multi_accept on; - use epoll; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - keepalive_timeout 65; - gzip on; - gzip_disable "msie6"; - - gzip_vary on; - gzip_proxied any; - gzip_comp_level 8; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - - # Include server configurations - include /etc/nginx/conf.d/*.conf; - } - djangoblog.conf: | - server { - server_name lylinux.net; - root /code/djangoblog/collectedstatic/; - listen 80; - keepalive_timeout 70; - location /static/ { - expires max; - alias /code/djangoblog/collectedstatic/; - } - - location ~* (robots\.txt|ads\.txt|favicon\.ico|favion\.ico|crossdomain\.xml|google93fd32dbd906620a\.html|BingSiteAuth\.xml|baidu_verify_Ijeny6KrmS\.html)$ { - root /resource/djangopub; - expires 1d; - access_log off; - error_log off; - } - - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - proxy_redirect off; - if (!-f $request_filename) { - proxy_pass http://djangoblog:8000; - break; - } - } - } - server { - server_name www.lylinux.net; - listen 80; - return 301 https://lylinux.net$request_uri; - } - resource.lylinux.net.conf: | - server { - index index.html index.htm; - server_name resource.lylinux.net; - root /resource/; - - location /djangoblog/ { - alias /code/djangoblog/collectedstatic/; - } - - access_log off; - error_log off; - include lylinux/resource.conf; - } - lylinux.resource.conf: | - expires max; - access_log off; - log_not_found off; - add_header Pragma public; - add_header Cache-Control "public"; - add_header "Access-Control-Allow-Origin" "*"; - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: djangoblog-env - namespace: djangoblog -data: - DJANGO_MYSQL_DATABASE: djangoblog - DJANGO_MYSQL_USER: db_user - DJANGO_MYSQL_PASSWORD: db_password - DJANGO_MYSQL_HOST: db_host - DJANGO_MYSQL_PORT: db_port - DJANGO_REDIS_URL: "redis:6379" - DJANGO_DEBUG: "False" - MYSQL_ROOT_PASSWORD: db_password - MYSQL_DATABASE: djangoblog - MYSQL_PASSWORD: db_password - DJANGO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx - diff --git a/src/deploy/k8s/deployment.yaml b/src/deploy/k8s/deployment.yaml index 414fdcc..b50c411 100644 --- a/src/deploy/k8s/deployment.yaml +++ b/src/deploy/k8s/deployment.yaml @@ -26,13 +26,13 @@ spec: name: djangoblog-env readinessProbe: httpGet: - path: / + path: /health/ port: 8000 initialDelaySeconds: 10 periodSeconds: 30 livenessProbe: httpGet: - path: / + path: /health/ port: 8000 initialDelaySeconds: 10 periodSeconds: 30 diff --git a/src/deploy/k8s/gateway.yaml b/src/deploy/k8s/gateway.yaml deleted file mode 100644 index a8de073..0000000 --- a/src/deploy/k8s/gateway.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: nginx - namespace: djangoblog -spec: - ingressClassName: nginx - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nginx - port: - number: 80 \ No newline at end of file diff --git a/src/deploy/k8s/pv.yaml b/src/deploy/k8s/pv.yaml deleted file mode 100644 index 874b72f..0000000 --- a/src/deploy/k8s/pv.yaml +++ /dev/null @@ -1,94 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-db -spec: - capacity: - storage: 10Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-db - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-djangoblog -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-djangoblog - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master - - ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-resource -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/resource/ - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master - ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: local-pv-elasticsearch -spec: - capacity: - storage: 5Gi - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: local-storage - local: - path: /mnt/local-storage-elasticsearch - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/hostname - operator: In - values: - - master \ No newline at end of file diff --git a/src/deploy/k8s/pvc.yaml b/src/deploy/k8s/pvc.yaml deleted file mode 100644 index ef238c5..0000000 --- a/src/deploy/k8s/pvc.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: db-pvc - namespace: djangoblog -spec: - storageClassName: local-storage - volumeName: local-pv-db - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: djangoblog-pvc - namespace: djangoblog -spec: - volumeName: local-pv-djangoblog - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: resource-pvc - namespace: djangoblog -spec: - volumeName: local-pv-resource - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: elasticsearch-pvc - namespace: djangoblog -spec: - volumeName: local-pv-elasticsearch - storageClassName: local-storage - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - \ No newline at end of file diff --git a/src/deploy/k8s/service.yaml b/src/deploy/k8s/service.yaml deleted file mode 100644 index 4ef2931..0000000 --- a/src/deploy/k8s/service.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: djangoblog - namespace: djangoblog - labels: - app: djangoblog -spec: - selector: - app: djangoblog - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: nginx - namespace: djangoblog - labels: - app: nginx -spec: - selector: - app: nginx - ports: - - protocol: TCP - port: 80 - targetPort: 80 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: redis - namespace: djangoblog - labels: - app: redis -spec: - selector: - app: redis - ports: - - protocol: TCP - port: 6379 - targetPort: 6379 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: db - namespace: djangoblog - labels: - app: db -spec: - selector: - app: db - ports: - - protocol: TCP - port: 3306 - targetPort: 3306 - type: ClusterIP ---- -apiVersion: v1 -kind: Service -metadata: - name: elasticsearch - namespace: djangoblog - labels: - app: elasticsearch -spec: - selector: - app: elasticsearch - ports: - - protocol: TCP - port: 9200 - targetPort: 9200 - type: ClusterIP - diff --git a/src/deploy/k8s/storageclass.yaml b/src/deploy/k8s/storageclass.yaml deleted file mode 100644 index 5d5a14c..0000000 --- a/src/deploy/k8s/storageclass.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: local-storage - annotations: - storageclass.kubernetes.io/is-default-class: "true" -provisioner: kubernetes.io/no-provisioner -volumeBindingMode: Immediate - - diff --git a/src/deploy/nginx.conf b/src/deploy/nginx.conf deleted file mode 100644 index 32161d8..0000000 --- a/src/deploy/nginx.conf +++ /dev/null @@ -1,50 +0,0 @@ -user nginx; -worker_processes auto; - -error_log /var/log/nginx/error.log notice; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - server { - root /code/djangoblog/collectedstatic/; - listen 80; - keepalive_timeout 70; - location /static/ { - expires max; - alias /code/djangoblog/collectedstatic/; - } - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header X-NginX-Proxy true; - proxy_redirect off; - if (!-f $request_filename) { - proxy_pass http://djangoblog:8000; - break; - } - } - } -} diff --git a/src/djangoblog/__pycache__/__init__.cpython-311.pyc b/src/djangoblog/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..ca737f1 Binary files /dev/null and b/src/djangoblog/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/admin_site.cpython-311.pyc b/src/djangoblog/__pycache__/admin_site.cpython-311.pyc new file mode 100644 index 0000000..bedc2bd Binary files /dev/null and b/src/djangoblog/__pycache__/admin_site.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/apps.cpython-311.pyc b/src/djangoblog/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..04dd331 Binary files /dev/null and b/src/djangoblog/__pycache__/apps.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/blog_signals.cpython-311.pyc b/src/djangoblog/__pycache__/blog_signals.cpython-311.pyc new file mode 100644 index 0000000..a57555a Binary files /dev/null and b/src/djangoblog/__pycache__/blog_signals.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/elasticsearch_backend.cpython-311.pyc b/src/djangoblog/__pycache__/elasticsearch_backend.cpython-311.pyc new file mode 100644 index 0000000..a7adca7 Binary files /dev/null and b/src/djangoblog/__pycache__/elasticsearch_backend.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/feeds.cpython-311.pyc b/src/djangoblog/__pycache__/feeds.cpython-311.pyc new file mode 100644 index 0000000..045476a Binary files /dev/null and b/src/djangoblog/__pycache__/feeds.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/logentryadmin.cpython-311.pyc b/src/djangoblog/__pycache__/logentryadmin.cpython-311.pyc new file mode 100644 index 0000000..42131af Binary files /dev/null and b/src/djangoblog/__pycache__/logentryadmin.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/settings.cpython-311.pyc b/src/djangoblog/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000..521fefa Binary files /dev/null and b/src/djangoblog/__pycache__/settings.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/sitemap.cpython-311.pyc b/src/djangoblog/__pycache__/sitemap.cpython-311.pyc new file mode 100644 index 0000000..527cd15 Binary files /dev/null and b/src/djangoblog/__pycache__/sitemap.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/spider_notify.cpython-311.pyc b/src/djangoblog/__pycache__/spider_notify.cpython-311.pyc new file mode 100644 index 0000000..3137872 Binary files /dev/null and b/src/djangoblog/__pycache__/spider_notify.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/urls.cpython-311.pyc b/src/djangoblog/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..2a9f10a Binary files /dev/null and b/src/djangoblog/__pycache__/urls.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/utils.cpython-311.pyc b/src/djangoblog/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..5da0bed Binary files /dev/null and b/src/djangoblog/__pycache__/utils.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-311.pyc b/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-311.pyc new file mode 100644 index 0000000..63db9a9 Binary files /dev/null and b/src/djangoblog/__pycache__/whoosh_cn_backend.cpython-311.pyc differ diff --git a/src/djangoblog/__pycache__/wsgi.cpython-311.pyc b/src/djangoblog/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000..bed74b9 Binary files /dev/null and b/src/djangoblog/__pycache__/wsgi.cpython-311.pyc differ diff --git a/src/djangoblog/admin_site.py b/src/djangoblog/admin_site.py deleted file mode 100644 index f120405..0000000 --- a/src/djangoblog/admin_site.py +++ /dev/null @@ -1,64 +0,0 @@ -from django.contrib.admin import AdminSite -from django.contrib.admin.models import LogEntry -from django.contrib.sites.admin import SiteAdmin -from django.contrib.sites.models import Site - -from accounts.admin import * -from blog.admin import * -from blog.models import * -from comments.admin import * -from comments.models import * -from djangoblog.logentryadmin import LogEntryAdmin -from oauth.admin import * -from oauth.models import * -from owntracks.admin import * -from owntracks.models import * -from servermanager.admin import * -from servermanager.models import * - - -class DjangoBlogAdminSite(AdminSite): - site_header = 'djangoblog administration' - site_title = 'djangoblog site admin' - - def __init__(self, name='admin'): - super().__init__(name) - - def has_permission(self, request): - return request.user.is_superuser - - # def get_urls(self): - # urls = super().get_urls() - # from django.urls import path - # from blog.views import refresh_memcache - # - # my_urls = [ - # path('refresh/', self.admin_view(refresh_memcache), name="refresh"), - # ] - # return urls + my_urls - - -admin_site = DjangoBlogAdminSite(name='admin') - -admin_site.register(Article, ArticlelAdmin) -admin_site.register(Category, CategoryAdmin) -admin_site.register(Tag, TagAdmin) -admin_site.register(Links, LinksAdmin) -admin_site.register(SideBar, SideBarAdmin) -admin_site.register(BlogSettings, BlogSettingsAdmin) - -admin_site.register(commands, CommandsAdmin) -admin_site.register(EmailSendLog, EmailSendLogAdmin) - -admin_site.register(BlogUser, BlogUserAdmin) - -admin_site.register(Comment, CommentAdmin) - -admin_site.register(OAuthUser, OAuthUserAdmin) -admin_site.register(OAuthConfig, OAuthConfigAdmin) - -admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) - -admin_site.register(Site, SiteAdmin) - -admin_site.register(LogEntry, LogEntryAdmin) diff --git a/src/djangoblog/apps.py b/src/djangoblog/apps.py deleted file mode 100644 index d29e318..0000000 --- a/src/djangoblog/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.apps import AppConfig - -class DjangoblogAppConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'djangoblog' - - def ready(self): - super().ready() - # Import and load plugins here - from .plugin_manage.loader import load_plugins - load_plugins() \ No newline at end of file diff --git a/src/djangoblog/blog_signals.py b/src/djangoblog/blog_signals.py deleted file mode 100644 index 393f441..0000000 --- a/src/djangoblog/blog_signals.py +++ /dev/null @@ -1,122 +0,0 @@ -import _thread -import logging - -import django.dispatch -from django.conf import settings -from django.contrib.admin.models import LogEntry -from django.contrib.auth.signals import user_logged_in, user_logged_out -from django.core.mail import EmailMultiAlternatives -from django.db.models.signals import post_save -from django.dispatch import receiver - -from comments.models import Comment -from comments.utils import send_comment_email -from djangoblog.spider_notify import SpiderNotify -from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache -from djangoblog.utils import get_current_site -from oauth.models import OAuthUser - -logger = logging.getLogger(__name__) - -oauth_user_login_signal = django.dispatch.Signal(['id']) -send_email_signal = django.dispatch.Signal( - ['emailto', 'title', 'content']) - - -@receiver(send_email_signal) -def send_email_signal_handler(sender, **kwargs): - emailto = kwargs['emailto'] - title = kwargs['title'] - content = kwargs['content'] - - msg = EmailMultiAlternatives( - title, - content, - from_email=settings.DEFAULT_FROM_EMAIL, - to=emailto) - msg.content_subtype = "html" - - from servermanager.models import EmailSendLog - log = EmailSendLog() - log.title = title - log.content = content - log.emailto = ','.join(emailto) - - try: - result = msg.send() - log.send_result = result > 0 - except Exception as e: - logger.error(f"失败邮箱号: {emailto}, {e}") - log.send_result = False - log.save() - - -@receiver(oauth_user_login_signal) -def oauth_user_login_signal_handler(sender, **kwargs): - id = kwargs['id'] - oauthuser = OAuthUser.objects.get(id=id) - site = get_current_site().domain - if oauthuser.picture and not oauthuser.picture.find(site) >= 0: - from djangoblog.utils import save_user_avatar - oauthuser.picture = save_user_avatar(oauthuser.picture) - oauthuser.save() - - delete_sidebar_cache() - - -@receiver(post_save) -def model_post_save_callback( - sender, - instance, - created, - raw, - using, - update_fields, - **kwargs): - clearcache = False - if isinstance(instance, LogEntry): - return - if 'get_full_url' in dir(instance): - is_update_views = update_fields == {'views'} - if not settings.TESTING and not is_update_views: - try: - notify_url = instance.get_full_url() - SpiderNotify.baidu_notify([notify_url]) - except Exception as ex: - logger.error("notify sipder", ex) - if not is_update_views: - clearcache = True - - if isinstance(instance, Comment): - if instance.is_enable: - path = instance.article.get_absolute_url() - site = get_current_site().domain - if site.find(':') > 0: - site = site[0:site.find(':')] - - expire_view_cache( - path, - servername=site, - serverport=80, - key_prefix='blogdetail') - if cache.get('seo_processor'): - cache.delete('seo_processor') - comment_cache_key = 'article_comments_{id}'.format( - id=instance.article.id) - cache.delete(comment_cache_key) - delete_sidebar_cache() - delete_view_cache('article_comments', [str(instance.article.pk)]) - - _thread.start_new_thread(send_comment_email, (instance,)) - - if clearcache: - cache.clear() - - -@receiver(user_logged_in) -@receiver(user_logged_out) -def user_auth_callback(sender, request, user, **kwargs): - if user and user.username: - logger.info(user) - delete_sidebar_cache() - # cache.clear() diff --git a/src/djangoblog/elasticsearch_backend.py b/src/djangoblog/elasticsearch_backend.py deleted file mode 100644 index 4afe498..0000000 --- a/src/djangoblog/elasticsearch_backend.py +++ /dev/null @@ -1,183 +0,0 @@ -from django.utils.encoding import force_str -from elasticsearch_dsl import Q -from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query -from haystack.forms import ModelSearchForm -from haystack.models import SearchResult -from haystack.utils import log as logging - -from blog.documents import ArticleDocument, ArticleDocumentManager -from blog.models import Article - -logger = logging.getLogger(__name__) - - -class ElasticSearchBackend(BaseSearchBackend): - def __init__(self, connection_alias, **connection_options): - super( - ElasticSearchBackend, - self).__init__( - connection_alias, - **connection_options) - self.manager = ArticleDocumentManager() - self.include_spelling = True - - def _get_models(self, iterable): - models = iterable if iterable and iterable[0] else Article.objects.all() - docs = self.manager.convert_to_doc(models) - return docs - - def _create(self, models): - self.manager.create_index() - docs = self._get_models(models) - self.manager.rebuild(docs) - - def _delete(self, models): - for m in models: - m.delete() - return True - - def _rebuild(self, models): - models = models if models else Article.objects.all() - docs = self.manager.convert_to_doc(models) - self.manager.update_docs(docs) - - def update(self, index, iterable, commit=True): - - models = self._get_models(iterable) - self.manager.update_docs(models) - - def remove(self, obj_or_string): - models = self._get_models([obj_or_string]) - self._delete(models) - - def clear(self, models=None, commit=True): - self.remove(None) - - @staticmethod - def get_suggestion(query: str) -> str: - """获取推荐词, 如果没有找到添加原搜索词""" - - search = ArticleDocument.search() \ - .query("match", body=query) \ - .suggest('suggest_search', query, term={'field': 'body'}) \ - .execute() - - keywords = [] - for suggest in search.suggest.suggest_search: - if suggest["options"]: - keywords.append(suggest["options"][0]["text"]) - else: - keywords.append(suggest["text"]) - - return ' '.join(keywords) - - @log_query - def search(self, query_string, **kwargs): - logger.info('search query_string:' + query_string) - - start_offset = kwargs.get('start_offset') - end_offset = kwargs.get('end_offset') - - # 推荐词搜索 - if getattr(self, "is_suggest", None): - suggestion = self.get_suggestion(query_string) - else: - suggestion = query_string - - q = Q('bool', - should=[Q('match', body=suggestion), Q('match', title=suggestion)], - minimum_should_match="70%") - - search = ArticleDocument.search() \ - .query('bool', filter=[q]) \ - .filter('term', status='p') \ - .filter('term', type='a') \ - .source(False)[start_offset: end_offset] - - results = search.execute() - hits = results['hits'].total - raw_results = [] - for raw_result in results['hits']['hits']: - app_label = 'blog' - model_name = 'Article' - additional_fields = {} - - result_class = SearchResult - - result = result_class( - app_label, - model_name, - raw_result['_id'], - raw_result['_score'], - **additional_fields) - raw_results.append(result) - facets = {} - spelling_suggestion = None if query_string == suggestion else suggestion - - return { - 'results': raw_results, - 'hits': hits, - 'facets': facets, - 'spelling_suggestion': spelling_suggestion, - } - - -class ElasticSearchQuery(BaseSearchQuery): - def _convert_datetime(self, date): - if hasattr(date, 'hour'): - return force_str(date.strftime('%Y%m%d%H%M%S')) - else: - return force_str(date.strftime('%Y%m%d000000')) - - def clean(self, query_fragment): - """ - Provides a mechanism for sanitizing user input before presenting the - value to the backend. - - Whoosh 1.X differs here in that you can no longer use a backslash - to escape reserved characters. Instead, the whole word should be - quoted. - """ - words = query_fragment.split() - cleaned_words = [] - - for word in words: - if word in self.backend.RESERVED_WORDS: - word = word.replace(word, word.lower()) - - for char in self.backend.RESERVED_CHARACTERS: - if char in word: - word = "'%s'" % word - break - - cleaned_words.append(word) - - return ' '.join(cleaned_words) - - def build_query_fragment(self, field, filter_type, value): - return value.query_string - - def get_count(self): - results = self.get_results() - return len(results) if results else 0 - - def get_spelling_suggestion(self, preferred_query=None): - return self._spelling_suggestion - - def build_params(self, spelling_query=None): - kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) - return kwargs - - -class ElasticSearchModelSearchForm(ModelSearchForm): - - def search(self): - # 是否建议搜索 - self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" - sqs = super().search() - return sqs - - -class ElasticSearchEngine(BaseEngine): - backend = ElasticSearchBackend - query = ElasticSearchQuery diff --git a/src/djangoblog/feeds.py b/src/djangoblog/feeds.py deleted file mode 100644 index 8c4e851..0000000 --- a/src/djangoblog/feeds.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.syndication.views import Feed -from django.utils import timezone -from django.utils.feedgenerator import Rss201rev2Feed - -from blog.models import Article -from djangoblog.utils import CommonMarkdown - - -class DjangoBlogFeed(Feed): - feed_type = Rss201rev2Feed - - description = '大巧无工,重剑无锋.' - title = "且听风吟 大巧无工,重剑无锋. " - link = "/feed/" - - def author_name(self): - return get_user_model().objects.first().nickname - - def author_link(self): - return get_user_model().objects.first().get_absolute_url() - - def items(self): - return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] - - def item_title(self, item): - return item.title - - def item_description(self, item): - return CommonMarkdown.get_markdown(item.body) - - def feed_copyright(self): - now = timezone.now() - return "Copyright© {year} 且听风吟".format(year=now.year) - - def item_link(self, item): - return item.get_absolute_url() - - def item_guid(self, item): - return diff --git a/src/djangoblog/logentryadmin.py b/src/djangoblog/logentryadmin.py deleted file mode 100644 index 2f6a535..0000000 --- a/src/djangoblog/logentryadmin.py +++ /dev/null @@ -1,91 +0,0 @@ -from django.contrib import admin -from django.contrib.admin.models import DELETION -from django.contrib.contenttypes.models import ContentType -from django.urls import reverse, NoReverseMatch -from django.utils.encoding import force_str -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ - - -class LogEntryAdmin(admin.ModelAdmin): - list_filter = [ - 'content_type' - ] - - search_fields = [ - 'object_repr', - 'change_message' - ] - - list_display_links = [ - 'action_time', - 'get_change_message', - ] - list_display = [ - 'action_time', - 'user_link', - 'content_type', - 'object_link', - 'get_change_message', - ] - - def has_add_permission(self, request): - return False - - def has_change_permission(self, request, obj=None): - return ( - request.user.is_superuser or - request.user.has_perm('admin.change_logentry') - ) and request.method != 'POST' - - def has_delete_permission(self, request, obj=None): - return False - - def object_link(self, obj): - object_link = escape(obj.object_repr) - content_type = obj.content_type - - if obj.action_flag != DELETION and content_type is not None: - # try returning an actual link instead of object repr string - try: - url = reverse( - 'admin:{}_{}_change'.format(content_type.app_label, - content_type.model), - args=[obj.object_id] - ) - object_link = '{}'.format(url, object_link) - except NoReverseMatch: - pass - return mark_safe(object_link) - - object_link.admin_order_field = 'object_repr' - object_link.short_description = _('object') - - def user_link(self, obj): - content_type = ContentType.objects.get_for_model(type(obj.user)) - user_link = escape(force_str(obj.user)) - try: - # try returning an actual link instead of object repr string - url = reverse( - 'admin:{}_{}_change'.format(content_type.app_label, - content_type.model), - args=[obj.user.pk] - ) - user_link = '{}'.format(url, user_link) - except NoReverseMatch: - pass - return mark_safe(user_link) - - user_link.admin_order_field = 'user' - user_link.short_description = _('user') - - def get_queryset(self, request): - queryset = super(LogEntryAdmin, self).get_queryset(request) - return queryset.prefetch_related('content_type') - - def get_actions(self, request): - actions = super(LogEntryAdmin, self).get_actions(request) - if 'delete_selected' in actions: - del actions['delete_selected'] - return actions diff --git a/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-311.pyc b/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-311.pyc new file mode 100644 index 0000000..a71c305 Binary files /dev/null and b/src/djangoblog/plugin_manage/__pycache__/base_plugin.cpython-311.pyc differ diff --git a/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-311.pyc b/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-311.pyc new file mode 100644 index 0000000..d7df3d0 Binary files /dev/null and b/src/djangoblog/plugin_manage/__pycache__/hook_constants.cpython-311.pyc differ diff --git a/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-311.pyc b/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-311.pyc new file mode 100644 index 0000000..ad65b89 Binary files /dev/null and b/src/djangoblog/plugin_manage/__pycache__/hooks.cpython-311.pyc differ diff --git a/src/djangoblog/plugin_manage/__pycache__/loader.cpython-311.pyc b/src/djangoblog/plugin_manage/__pycache__/loader.cpython-311.pyc new file mode 100644 index 0000000..8a03a33 Binary files /dev/null and b/src/djangoblog/plugin_manage/__pycache__/loader.cpython-311.pyc differ diff --git a/src/djangoblog/plugin_manage/base_plugin.py b/src/djangoblog/plugin_manage/base_plugin.py index 2b4be5c..df1ce0b 100644 --- a/src/djangoblog/plugin_manage/base_plugin.py +++ b/src/djangoblog/plugin_manage/base_plugin.py @@ -1,4 +1,8 @@ import logging +from pathlib import Path + +from django.template import TemplateDoesNotExist +from django.template.loader import render_to_string logger = logging.getLogger(__name__) @@ -8,13 +12,34 @@ class BasePlugin: PLUGIN_NAME = None PLUGIN_DESCRIPTION = None PLUGIN_VERSION = None + PLUGIN_AUTHOR = None + + # 插件配置 + SUPPORTED_POSITIONS = [] # 支持的显示位置 + DEFAULT_PRIORITY = 100 # 默认优先级(数字越小优先级越高) + POSITION_PRIORITIES = {} # 各位置的优先级 {'sidebar': 50, 'article_bottom': 80} def __init__(self): if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") + + # 设置插件路径 + self.plugin_dir = self._get_plugin_directory() + self.plugin_slug = self._get_plugin_slug() + self.init_plugin() self.register_hooks() + def _get_plugin_directory(self): + """获取插件目录路径""" + import inspect + plugin_file = inspect.getfile(self.__class__) + return Path(plugin_file).parent + + def _get_plugin_slug(self): + """获取插件标识符(目录名)""" + return self.plugin_dir.name + def init_plugin(self): """ 插件初始化逻辑 @@ -29,6 +54,129 @@ class BasePlugin: """ pass + # === 位置渲染系统 === + def render_position_widget(self, position, context, **kwargs): + """ + 根据位置渲染插件组件 + + Args: + position: 位置标识 + context: 模板上下文 + **kwargs: 额外参数 + + Returns: + dict: {'html': 'HTML内容', 'priority': 优先级} 或 None + """ + if position not in self.SUPPORTED_POSITIONS: + return None + + # 检查条件显示 + if not self.should_display(position, context, **kwargs): + return None + + # 调用具体的位置渲染方法 + method_name = f'render_{position}_widget' + if hasattr(self, method_name): + html = getattr(self, method_name)(context, **kwargs) + if html: + priority = self.POSITION_PRIORITIES.get(position, self.DEFAULT_PRIORITY) + return { + 'html': html, + 'priority': priority, + 'plugin_name': self.PLUGIN_NAME + } + + return None + + def should_display(self, position, context, **kwargs): + """ + 判断插件是否应该在指定位置显示 + 子类可重写此方法实现条件显示逻辑 + + Args: + position: 位置标识 + context: 模板上下文 + **kwargs: 额外参数 + + Returns: + bool: 是否显示 + """ + return True + + # === 各位置渲染方法 - 子类重写 === + def render_sidebar_widget(self, context, **kwargs): + """渲染侧边栏组件""" + return None + + def render_article_bottom_widget(self, context, **kwargs): + """渲染文章底部组件""" + return None + + def render_article_top_widget(self, context, **kwargs): + """渲染文章顶部组件""" + return None + + def render_header_widget(self, context, **kwargs): + """渲染页头组件""" + return None + + def render_footer_widget(self, context, **kwargs): + """渲染页脚组件""" + return None + + def render_comment_before_widget(self, context, **kwargs): + """渲染评论前组件""" + return None + + def render_comment_after_widget(self, context, **kwargs): + """渲染评论后组件""" + return None + + # === 模板系统 === + def render_template(self, template_name, context=None): + """ + 渲染插件模板 + + Args: + template_name: 模板文件名 + context: 模板上下文 + + Returns: + HTML字符串 + """ + if context is None: + context = {} + + template_path = f"plugins/{self.plugin_slug}/{template_name}" + + try: + return render_to_string(template_path, context) + except TemplateDoesNotExist: + logger.warning(f"Plugin template not found: {template_path}") + return "" + + # === 静态资源系统 === + def get_static_url(self, static_file): + """获取插件静态文件URL""" + from django.templatetags.static import static + return static(f"{self.plugin_slug}/static/{self.plugin_slug}/{static_file}") + + def get_css_files(self): + """获取插件CSS文件列表""" + return [] + + def get_js_files(self): + """获取插件JavaScript文件列表""" + return [] + + def get_head_html(self, context=None): + """获取需要插入到中的HTML内容""" + return "" + + def get_body_html(self, context=None): + """获取需要插入到底部的HTML内容""" + return "" + def get_plugin_info(self): """ 获取插件信息 @@ -37,5 +185,10 @@ class BasePlugin: return { 'name': self.PLUGIN_NAME, 'description': self.PLUGIN_DESCRIPTION, - 'version': self.PLUGIN_VERSION + 'version': self.PLUGIN_VERSION, + 'author': self.PLUGIN_AUTHOR, + 'slug': self.plugin_slug, + 'directory': str(self.plugin_dir), + 'supported_positions': self.SUPPORTED_POSITIONS, + 'priorities': self.POSITION_PRIORITIES } diff --git a/src/djangoblog/plugin_manage/hook_constants.py b/src/djangoblog/plugin_manage/hook_constants.py index 6685b7c..8ed4e89 100644 --- a/src/djangoblog/plugin_manage/hook_constants.py +++ b/src/djangoblog/plugin_manage/hook_constants.py @@ -5,3 +5,18 @@ ARTICLE_DELETE = 'article_delete' ARTICLE_CONTENT_HOOK_NAME = "the_content" +# 位置钩子常量 +POSITION_HOOKS = { + 'article_top': 'article_top_widgets', + 'article_bottom': 'article_bottom_widgets', + 'sidebar': 'sidebar_widgets', + 'header': 'header_widgets', + 'footer': 'footer_widgets', + 'comment_before': 'comment_before_widgets', + 'comment_after': 'comment_after_widgets', +} + +# 资源注入钩子 +HEAD_RESOURCES_HOOK = 'head_resources' +BODY_RESOURCES_HOOK = 'body_resources' + diff --git a/src/djangoblog/plugin_manage/hooks.py b/src/djangoblog/plugin_manage/hooks.py deleted file mode 100644 index d712540..0000000 --- a/src/djangoblog/plugin_manage/hooks.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging - -logger = logging.getLogger(__name__) - -_hooks = {} - - -def register(hook_name: str, callback: callable): - """ - 注册一个钩子回调。 - """ - if hook_name not in _hooks: - _hooks[hook_name] = [] - _hooks[hook_name].append(callback) - logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") - - -def run_action(hook_name: str, *args, **kwargs): - """ - 执行一个 Action Hook。 - 它会按顺序执行所有注册到该钩子上的回调函数。 - """ - if hook_name in _hooks: - logger.debug(f"Running action hook '{hook_name}'") - for callback in _hooks[hook_name]: - try: - callback(*args, **kwargs) - except Exception as e: - logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - - -def apply_filters(hook_name: str, value, *args, **kwargs): - """ - 执行一个 Filter Hook。 - 它会把 value 依次传递给所有注册的回调函数进行处理。 - """ - if hook_name in _hooks: - logger.debug(f"Applying filter hook '{hook_name}'") - for callback in _hooks[hook_name]: - try: - value = callback(value, *args, **kwargs) - except Exception as e: - logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - return value diff --git a/src/djangoblog/plugin_manage/loader.py b/src/djangoblog/plugin_manage/loader.py index 12e824b..ee750d0 100644 --- a/src/djangoblog/plugin_manage/loader.py +++ b/src/djangoblog/plugin_manage/loader.py @@ -4,16 +4,61 @@ from django.conf import settings logger = logging.getLogger(__name__) +# 全局插件注册表 +_loaded_plugins = [] + def load_plugins(): """ Dynamically loads and initializes plugins from the 'plugins' directory. This function is intended to be called when the Django app registry is ready. """ + global _loaded_plugins + _loaded_plugins = [] + for plugin_name in settings.ACTIVE_PLUGINS: plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): try: - __import__(f'plugins.{plugin_name}.plugin') - logger.info(f"Successfully loaded plugin: {plugin_name}") + # 导入插件模块 + plugin_module = __import__(f'plugins.{plugin_name}.plugin', fromlist=['plugin']) + + # 获取插件实例 + if hasattr(plugin_module, 'plugin'): + plugin_instance = plugin_module.plugin + _loaded_plugins.append(plugin_instance) + logger.info(f"Successfully loaded plugin: {plugin_name} - {plugin_instance.PLUGIN_NAME}") + else: + logger.warning(f"Plugin {plugin_name} does not have 'plugin' instance") + except ImportError as e: - logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) \ No newline at end of file + logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) + except AttributeError as e: + logger.error(f"Failed to get plugin instance: {plugin_name}", exc_info=e) + except Exception as e: + logger.error(f"Unexpected error loading plugin: {plugin_name}", exc_info=e) + +def get_loaded_plugins(): + """获取所有已加载的插件""" + return _loaded_plugins + +def get_plugin_by_name(plugin_name): + """根据名称获取插件""" + for plugin in _loaded_plugins: + if plugin.plugin_slug == plugin_name: + return plugin + return None + +def get_plugin_by_slug(plugin_slug): + """根据slug获取插件""" + for plugin in _loaded_plugins: + if plugin.plugin_slug == plugin_slug: + return plugin + return None + +def get_plugins_info(): + """获取所有插件的信息""" + return [plugin.get_plugin_info() for plugin in _loaded_plugins] + +def get_plugins_by_position(position): + """获取支持指定位置的插件""" + return [plugin for plugin in _loaded_plugins if position in plugin.SUPPORTED_POSITIONS] \ No newline at end of file diff --git a/src/djangoblog/settings.py b/src/djangoblog/settings.py index 3269a34..9e28d5f 100644 --- a/src/djangoblog/settings.py +++ b/src/djangoblog/settings.py @@ -106,6 +106,7 @@ WSGI_APPLICATION = 'djangoblog.wsgi.application' DATABASES = { +<<<<<<< HEAD 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djangoblog', @@ -115,6 +116,17 @@ DATABASES = { 'PORT': 3306, } } +======= + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'djangoblogwhocare15', + 'USER': 'whocare15', + 'PASSWORD': 'IL2sXejLMkiEt8aU', + 'HOST': 'mysql5.sqlpub.com', + 'PORT': 3310, + } + } +>>>>>>> develop # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -174,6 +186,11 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') STATIC_URL = '/static/' STATICFILES = os.path.join(BASE_DIR, 'static') +# 添加插件静态文件目录 +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'plugins'), # 让Django能找到插件的静态文件 +] + AUTH_USER_MODEL = 'accounts.BlogUser' LOGIN_URL = '/login/' @@ -298,23 +315,76 @@ STATICFILES_FINDERS = ( 'compressor.finders.CompressorFinder', ) COMPRESS_ENABLED = True -# COMPRESS_OFFLINE = True +# 根据环境变量决定是否启用离线压缩 +COMPRESS_OFFLINE = os.environ.get('COMPRESS_OFFLINE', 'False').lower() == 'true' +# 压缩输出目录 +COMPRESS_OUTPUT_DIR = 'compressed' +# 压缩文件名模板 - 包含哈希值用于缓存破坏 +COMPRESS_CSS_HASHING_METHOD = 'mtime' +COMPRESS_JS_HASHING_METHOD = 'mtime' + +# 高级CSS压缩过滤器 COMPRESS_CSS_FILTERS = [ - # creates absolute urls from relative ones + # 创建绝对URL 'compressor.filters.css_default.CssAbsoluteFilter', - # css minimizer - 'compressor.filters.cssmin.CSSMinFilter' + # CSS压缩器 - 高压缩等级 + 'compressor.filters.cssmin.CSSCompressorFilter', ] + +# 高级JS压缩过滤器 COMPRESS_JS_FILTERS = [ - 'compressor.filters.jsmin.JSMinFilter' + # JS压缩器 - 高压缩等级 + 'compressor.filters.jsmin.SlimItFilter', ] +# 压缩缓存配置 +COMPRESS_CACHE_BACKEND = 'default' +COMPRESS_CACHE_KEY_FUNCTION = 'compressor.cache.simple_cachekey' + +# 预压缩配置 +COMPRESS_PRECOMPILERS = ( + # 支持SCSS/SASS + ('text/x-scss', 'django_libsass.SassCompiler'), + ('text/x-sass', 'django_libsass.SassCompiler'), +) + +# 压缩性能优化 +COMPRESS_MINT_DELAY = 30 # 压缩延迟(秒) +COMPRESS_MTIME_DELAY = 10 # 修改时间检查延迟 +COMPRESS_REBUILD_TIMEOUT = 2592000 # 重建超时(30天) + +# 压缩等级配置 +COMPRESS_CSS_COMPRESSOR = 'compressor.css.CssCompressor' +COMPRESS_JS_COMPRESSOR = 'compressor.js.JsCompressor' + +# 静态文件缓存配置 +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' + +# 浏览器缓存配置(通过中间件或服务器配置) +COMPRESS_URL = STATIC_URL +COMPRESS_ROOT = STATIC_ROOT + MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') MEDIA_URL = '/media/' X_FRAME_OPTIONS = 'SAMEORIGIN' +# 安全头部配置 - 防XSS和其他攻击 +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin' + +# 内容安全策略 (CSP) - 防XSS攻击 +CSP_DEFAULT_SRC = ["'self'"] +CSP_SCRIPT_SRC = ["'self'", "'unsafe-inline'", "cdn.mathjax.org", "*.googleapis.com"] +CSP_STYLE_SRC = ["'self'", "'unsafe-inline'", "*.googleapis.com", "*.gstatic.com"] +CSP_IMG_SRC = ["'self'", "data:", "*.lylinux.net", "*.gravatar.com", "*.githubusercontent.com"] +CSP_FONT_SRC = ["'self'", "*.googleapis.com", "*.gstatic.com"] +CSP_CONNECT_SRC = ["'self'"] +CSP_FRAME_SRC = ["'none'"] +CSP_OBJECT_SRC = ["'none'"] + DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): @@ -328,3 +398,19 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', }, } +<<<<<<< HEAD +======= + +# Plugin System +PLUGINS_DIR = BASE_DIR / 'plugins' +ACTIVE_PLUGINS = [ + 'article_copyright', + 'reading_time', + 'external_links', + 'view_count', + 'seo_optimizer', + 'image_lazy_loading', + 'article_recommendation', +] + +>>>>>>> develop diff --git a/src/djangoblog/sitemap.py b/src/djangoblog/sitemap.py deleted file mode 100644 index 8b7d446..0000000 --- a/src/djangoblog/sitemap.py +++ /dev/null @@ -1,59 +0,0 @@ -from django.contrib.sitemaps import Sitemap -from django.urls import reverse - -from blog.models import Article, Category, Tag - - -class StaticViewSitemap(Sitemap): - priority = 0.5 - changefreq = 'daily' - - def items(self): - return ['blog:index', ] - - def location(self, item): - return reverse(item) - - -class ArticleSiteMap(Sitemap): - changefreq = "monthly" - priority = "0.6" - - def items(self): - return Article.objects.filter(status='p') - - def lastmod(self, obj): - return obj.last_modify_time - - -class CategorySiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.6" - - def items(self): - return Category.objects.all() - - def lastmod(self, obj): - return obj.last_modify_time - - -class TagSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" - - def items(self): - return Tag.objects.all() - - def lastmod(self, obj): - return obj.last_modify_time - - -class UserSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" - - def items(self): - return list(set(map(lambda x: x.author, Article.objects.all()))) - - def lastmod(self, obj): - return obj.date_joined diff --git a/src/djangoblog/tests.py b/src/djangoblog/tests.py deleted file mode 100644 index 01237d9..0000000 --- a/src/djangoblog/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.test import TestCase - -from djangoblog.utils import * - - -class DjangoBlogTest(TestCase): - def setUp(self): - pass - - def test_utils(self): - md5 = get_sha256('test') - self.assertIsNotNone(md5) - c = CommonMarkdown.get_markdown(''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''') - self.assertIsNotNone(c) - d = { - 'd': 'key1', - 'd2': 'key2' - } - data = parse_dict_to_url(d) - self.assertIsNotNone(data) diff --git a/src/djangoblog/urls.py b/src/djangoblog/urls.py index 4aae58a..6a9e1de 100644 --- a/src/djangoblog/urls.py +++ b/src/djangoblog/urls.py @@ -20,6 +20,8 @@ from django.contrib.sitemaps.views import sitemap from django.urls import path, include from django.urls import re_path from haystack.views import search_view_factory +from django.http import JsonResponse +import time from blog.views import EsSearchView from djangoblog.admin_site import admin_site @@ -40,8 +42,20 @@ handler404 = 'blog.views.page_not_found_view' handler500 = 'blog.views.server_error_view' handle403 = 'blog.views.permission_denied_view' + +def health_check(request): + """ + 健康检查接口 + 简单返回服务健康状态 + """ + return JsonResponse({ + 'status': 'healthy', + 'timestamp': time.time() + }) + urlpatterns = [ path('i18n/', include('django.conf.urls.i18n')), + path('health/', health_check, name='health_check'), ] urlpatterns += i18n_patterns( re_path(r'^admin/', admin_site.urls), diff --git a/src/djangoblog/utils.py b/src/djangoblog/utils.py index 57f63dc..91d2b91 100644 --- a/src/djangoblog/utils.py +++ b/src/djangoblog/utils.py @@ -224,9 +224,49 @@ def get_resource_url(): ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', - 'h2', 'p'] -ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} - + 'h2', 'p', 'span', 'div'] + +# 安全的class值白名单 - 只允许代码高亮相关的class +ALLOWED_CLASSES = [ + 'codehilite', 'highlight', 'hll', 'c', 'err', 'k', 'l', 'n', 'o', 'p', 'cm', 'cp', 'c1', 'cs', + 'gd', 'ge', 'gr', 'gh', 'gi', 'go', 'gp', 'gs', 'gu', 'gt', 'kc', 'kd', 'kn', 'kp', 'kr', 'kt', + 'ld', 'm', 'mf', 'mh', 'mi', 'mo', 'na', 'nb', 'nc', 'no', 'nd', 'ni', 'ne', 'nf', 'nl', 'nn', + 'nt', 'nv', 'ow', 'w', 'mb', 'mh', 'mi', 'mo', 'sb', 'sc', 'sd', 'se', 'sh', 'si', 'sx', 's2', + 's1', 'ss', 'bp', 'vc', 'vg', 'vi', 'il' +] + +def class_filter(tag, name, value): + """自定义class属性过滤器""" + if name == 'class': + # 只允许预定义的安全class值 + allowed_classes = [cls for cls in value.split() if cls in ALLOWED_CLASSES] + return ' '.join(allowed_classes) if allowed_classes else False + return value + +# 安全的属性白名单 +ALLOWED_ATTRIBUTES = { + 'a': ['href', 'title'], + 'abbr': ['title'], + 'acronym': ['title'], + 'span': class_filter, + 'div': class_filter, + 'pre': class_filter, + 'code': class_filter +} + +# 安全的协议白名单 - 防止javascript:等危险协议 +ALLOWED_PROTOCOLS = ['http', 'https', 'mailto'] def sanitize_html(html): - return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) + """ + 安全的HTML清理函数 + 使用bleach库进行白名单过滤,防止XSS攻击 + """ + return bleach.clean( + html, + tags=ALLOWED_TAGS, + attributes=ALLOWED_ATTRIBUTES, + protocols=ALLOWED_PROTOCOLS, # 限制允许的协议 + strip=True, # 移除不允许的标签而不是转义 + strip_comments=True # 移除HTML注释 + ) diff --git a/src/djangoblog/wsgi.py b/src/djangoblog/wsgi.py deleted file mode 100644 index 2295efd..0000000 --- a/src/djangoblog/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for djangoblog project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") - -application = get_wsgi_application() diff --git a/src/docs/config-en.md b/src/docs/config-en.md deleted file mode 100644 index b877efb..0000000 --- a/src/docs/config-en.md +++ /dev/null @@ -1,64 +0,0 @@ -# Introduction to main features settings - -## Cache: -Cache using `memcache` for default. If you don't have `memcache` environment, you can remove the `default` setting in `CACHES` and change `locmemcache` to `default`. -```python -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': '127.0.0.1:11211', - 'KEY_PREFIX': 'django_test' if TESTING else 'djangoblog', - 'TIMEOUT': 60 * 60 * 10 - }, - 'locmemcache': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'TIMEOUT': 10800, - 'LOCATION': 'unique-snowflake', - } -} -``` - -## OAuth Login: -QQ, Weibo, Google, GitHub and Facebook are now supported for OAuth login. Fetch OAuth login permissions from the corresponding open platform, and save them with `appkey`, `appsecret` and callback address in **Backend->OAuth** configuration. - -### Callback address examples: -QQ: http://your-domain-name/oauth/authorize?type=qq -Weibo: http://your-domain-name/oauth/authorize?type=weibo -type is in the type field of `oauthmanager`. - -## owntracks: -owntracks is a location tracking application. It will send your locaiton to the server by timing.Simple support owntracks features. Just install owntracks app and set api address as `your-domain-name/owntracks/logtracks`. Visit `your-domain-name/owntracks/show_dates` and you will see the date with latitude and langitude, click it and see the motion track. The map is drawn by AMap. - -## Email feature: -Same as before, Configure your own error msg recvie email information with`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]` in `settings.py`. And modify: -```python -EMAIL_HOST = 'smtp.zoho.com' -EMAIL_PORT = 587 -EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') -EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') -DEFAULT_FROM_EMAIL = EMAIL_HOST_USER -SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER') -``` -with your email account information. - -## WeChat Official Account -Simple wechat official account features integrated. Set token as `your-domain-name/robot` in wechat backend. Default token is `lylinux`, you can change it to your own in `servermanager/robot.py`. Add a new command in `Backend->Servermanager->command`, in this way, you can manage the system through wechat official account. - -## Introduction to website configuration -You can add website configuration in **Backend->BLOG->WebSiteConfiguration**. Such as: keywords, description, Google Ad, website stats code, case number, etc. -OAuth user avatar path is saved in *StaticFileSavedAddress*. Please input absolute path, code directory for default. - -## Source code highlighting -If the code block in your article didn't show hightlight, please write the code blocks as following: - -![](https://resource.lylinux.net/image/codelang.png) - -That is, you should add the corresponding language name before the code block. - -## Update -If you get errors as following while executing database migrations: -```python -django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) -``` -This problem may cause by the mysql version under 5.6, a new version( >= 5.6 ) mysql is needed. - diff --git a/src/docs/config.md b/src/docs/config.md deleted file mode 100644 index 24673a3..0000000 --- a/src/docs/config.md +++ /dev/null @@ -1,58 +0,0 @@ -# 主要功能配置介绍: - -## 缓存: -缓存默认使用`localmem`缓存,如果你有`redis`环境,可以设置`DJANGO_REDIS_URL`环境变量,则会自动使用该redis来作为缓存,或者你也可以直接修改如下代码来使用。 -https://github.com/liangliangyy/DjangoBlog/blob/ffcb2c3711de805f2067dd3c1c57449cd24d84ee/djangoblog/settings.py#L185-L199 - - -## oauth登录: - -现在已经支持QQ,微博,Google,GitHub,Facebook登录,需要在其对应的开放平台申请oauth登录权限,然后在 -**后台->Oauth** 配置中新增配置,填写对应的`appkey`和`appsecret`以及回调地址。 -### 回调地址示例: -qq:http://你的域名/oauth/authorize?type=qq -微博:http://你的域名/oauth/authorize?type=weibo -type对应在`oauthmanager`中的type字段。 - -## owntracks: -owntracks是一个位置追踪软件,可以定时的将你的坐标提交到你的服务器上,现在简单的支持owntracks功能,需要安装owntracks的app,然后将api地址设置为: -`你的域名/owntracks/logtracks`就可以了。然后访问`你的域名/owntracks/show_dates`就可以看到有经纬度记录的日期,点击之后就可以看到运动轨迹了。地图是使用高德地图绘制。 - -## 邮件功能: -同样,将`settings.py`中的`ADMINS = [('liangliang', 'liangliangyy@gmail.com')]`配置为你自己的错误接收邮箱,另外修改: -```python -EMAIL_HOST = 'smtp.zoho.com' -EMAIL_PORT = 587 -EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') -EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') -DEFAULT_FROM_EMAIL = EMAIL_HOST_USER -SERVER_EMAIL = os.environ.get('DJANGO_EMAIL_USER') -``` -为你自己的邮箱配置。 - -## 微信公众号 -集成了简单的微信公众号功能,在微信后台将token地址设置为:`你的域名/robot` 即可,默认token为`lylinux`,当然你可以修改为你自己的,在`servermanager/robot.py`中。 -然后在**后台->Servermanager->命令**中新增命令,这样就可以使用微信公众号来管理了。 -## 网站配置介绍 -在**后台->BLOG->网站配置**中,可以新增网站配置,比如关键字,描述等,以及谷歌广告,网站统计代码及备案号等等。 -其中的*静态文件保存地址*是保存oauth用户登录的头像路径,填写绝对路径,默认是代码目录。 -## 代码高亮 -如果你发现你文章的代码没有高亮,请这样书写代码块: - -![](https://resource.lylinux.net/image/codelang.png) - - -也就是说,需要在代码块开始位置加入这段代码对应的语言。 - -## update -如果你发现执行数据库迁移的时候出现如下报错: -```python -django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(6) NOT NULL)' at line 1")) -``` -可能是因为你的mysql版本低于5.6,需要升级mysql版本>=5.6即可。 - - -django 4.0登录可能会报错CSRF,需要配置下`settings.py`中的`CSRF_TRUSTED_ORIGINS` - -https://github.com/liangliangyy/DjangoBlog/blob/master/djangoblog/settings.py#L39 - diff --git a/src/docs/docker-en.md b/src/docs/docker-en.md deleted file mode 100644 index 8d5d59e..0000000 --- a/src/docs/docker-en.md +++ /dev/null @@ -1,114 +0,0 @@ -# Deploying DjangoBlog with Docker - -![Docker Pulls](https://img.shields.io/docker/pulls/liangliangyy/djangoblog) -![Docker Image Version (latest by date)](https://img.shields.io/docker/v/liangliangyy/djangoblog?sort=date) -![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/liangliangyy/djangoblog) - -This project fully supports containerized deployment using Docker, providing you with a fast, consistent, and isolated runtime environment. We recommend using `docker-compose` to launch the entire blog service stack with a single command. - -## 1. Prerequisites - -Before you begin, please ensure you have the following software installed on your system: -- [Docker Engine](https://docs.docker.com/engine/install/) -- [Docker Compose](https://docs.docker.com/compose/install/) (Included with Docker Desktop for Mac and Windows) - -## 2. Recommended Method: Using `docker-compose` (One-Click Deployment) - -This is the simplest and most recommended way to deploy. It automatically creates and manages the Django application, a MySQL database, and an optional Elasticsearch service for you. - -### Step 1: Start the Basic Services - -From the project's root directory, run the following command: - -```bash -# Build and start the containers in detached mode (includes Django app and MySQL) -docker-compose up -d --build -``` - -`docker-compose` will read the `docker-compose.yml` file, pull the necessary images, build the project image, and start all services. - -- **Access Your Blog**: Once the services are up, you can access the blog by navigating to `http://127.0.0.1` in your browser. -- **Data Persistence**: MySQL data files will be stored in the `data/mysql` directory within the project root, ensuring that your data persists across container restarts. - -### Step 2: (Optional) Enable Elasticsearch for Full-Text Search - -If you want to use Elasticsearch for more powerful full-text search capabilities, you can include the `docker-compose.es.yml` configuration file: - -```bash -# Build and start all services in detached mode (Django, MySQL, Elasticsearch) -docker-compose -f docker-compose.yml -f deploy/docker-compose/docker-compose.es.yml up -d --build -``` -- **Data Persistence**: Elasticsearch data will be stored in the `data/elasticsearch` directory. - -### Step 3: First-Time Initialization - -After the containers start for the first time, you'll need to execute some initialization commands inside the application container. - -```bash -# Get a shell inside the djangoblog application container (named 'web') -docker-compose exec web bash - -# Inside the container, run the following commands: -# Create a superuser account (follow the prompts to set username, email, and password) -python manage.py createsuperuser - -# (Optional) Create some test data -python manage.py create_testdata - -# (Optional, if ES is enabled) Create the search index -python manage.py rebuild_index - -# Exit the container -exit -``` - -## 3. Alternative Method: Using the Standalone Docker Image - -If you already have an external MySQL database running, you can run the DjangoBlog application image by itself. - -```bash -# Pull the latest image from Docker Hub -docker pull liangliangyy/djangoblog:latest - -# Run the container and connect it to your external database -docker run -d \ - -p 8000:8000 \ - -e DJANGO_SECRET_KEY='your-strong-secret-key' \ - -e DJANGO_MYSQL_HOST='your-mysql-host' \ - -e DJANGO_MYSQL_USER='your-mysql-user' \ - -e DJANGO_MYSQL_PASSWORD='your-mysql-password' \ - -e DJANGO_MYSQL_DATABASE='djangoblog' \ - --name djangoblog \ - liangliangyy/djangoblog:latest -``` - -- **Access Your Blog**: After startup, visit `http://127.0.0.1:8000`. -- **Create Superuser**: `docker exec -it djangoblog python manage.py createsuperuser` - -## 4. Configuration (Environment Variables) - -Most of the project's configuration is managed through environment variables. You can modify them in the `docker-compose.yml` file or pass them using the `-e` flag with the `docker run` command. - -| Environment Variable | Default/Example Value | Notes | -|---------------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------| -| `DJANGO_SECRET_KEY` | `your-strong-secret-key` | **Must be changed to a random, complex string!** | -| `DJANGO_DEBUG` | `False` | Toggles Django's debug mode. | -| `DJANGO_MYSQL_HOST` | `mysql` | Database hostname. | -| `DJANGO_MYSQL_PORT` | `3306` | Database port. | -| `DJANGO_MYSQL_DATABASE` | `djangoblog` | Database name. | -| `DJANGO_MYSQL_USER` | `root` | Database username. | -| `DJANGO_MYSQL_PASSWORD` | `djangoblog_123` | Database password. | -| `DJANGO_REDIS_URL` | `redis:6379/0` | Redis connection URL (for caching). | -| `DJANGO_ELASTICSEARCH_HOST`| `elasticsearch:9200` | Elasticsearch host address. | -| `DJANGO_EMAIL_HOST` | `smtp.example.org` | Email server address. | -| `DJANGO_EMAIL_PORT` | `465` | Email server port. | -| `DJANGO_EMAIL_USER` | `user@example.org` | Email account username. | -| `DJANGO_EMAIL_PASSWORD` | `your-email-password` | Email account password. | -| `DJANGO_EMAIL_USE_SSL` | `True` | Whether to use SSL. | -| `DJANGO_EMAIL_USE_TLS` | `False` | Whether to use TLS. | -| `DJANGO_ADMIN_EMAIL` | `admin@example.org` | Admin email for receiving error reports. | -| `DJANGO_BAIDU_NOTIFY_URL` | `http://data.zz.baidu.com/...` | Push API from [Baidu Webmaster Tools](https://ziyuan.baidu.com/linksubmit/index). | - ---- - -After deployment, please review and adjust these environment variables according to your needs, especially `DJANGO_SECRET_KEY` and the database and email settings. \ No newline at end of file diff --git a/src/docs/es.md b/src/docs/es.md deleted file mode 100644 index 97226c5..0000000 --- a/src/docs/es.md +++ /dev/null @@ -1,28 +0,0 @@ -# 集成Elasticsearch -如果你已经有了`Elasticsearch`环境,那么可以将搜索从`Whoosh`换成`Elasticsearch`,集成方式也很简单, -首先需要注意如下几点: -1. 你的`Elasticsearch`支持`ik`中文分词 -2. 你的`Elasticsearch`版本>=7.3.0 - -接下来在`settings.py`做如下改动即可: -- 增加es链接,如下所示: -```python -ELASTICSEARCH_DSL = { - 'default': { - 'hosts': '127.0.0.1:9200' - }, -} -``` -- 修改`HAYSTACK`配置: -```python -HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', - }, -} -``` -然后终端执行: -```shell script -./manage.py build_index -``` -这将会在你的es中创建两个索引,分别是`blog`和`performance`,其中`blog`索引就是搜索所使用的,而`performance`会记录每个请求的响应时间,以供将来优化使用。 \ No newline at end of file diff --git a/src/docs/imgs/alipay.jpg b/src/docs/imgs/alipay.jpg deleted file mode 100644 index 424d70a..0000000 Binary files a/src/docs/imgs/alipay.jpg and /dev/null differ diff --git a/src/docs/imgs/pycharm_logo.png b/src/docs/imgs/pycharm_logo.png deleted file mode 100644 index 7f2a4b0..0000000 Binary files a/src/docs/imgs/pycharm_logo.png and /dev/null differ diff --git a/src/docs/imgs/wechat.jpg b/src/docs/imgs/wechat.jpg deleted file mode 100644 index 7edf525..0000000 Binary files a/src/docs/imgs/wechat.jpg and /dev/null differ diff --git a/src/docs/k8s-en.md b/src/docs/k8s-en.md deleted file mode 100644 index 20e9527..0000000 --- a/src/docs/k8s-en.md +++ /dev/null @@ -1,141 +0,0 @@ -# Deploying DjangoBlog with Kubernetes - -This document guides you through deploying the DjangoBlog application on a Kubernetes (K8s) cluster. We provide a complete set of `.yaml` configuration files in the `deploy/k8s` directory to deploy a full service stack, including the DjangoBlog application, Nginx, MySQL, Redis, and Elasticsearch. - -## Architecture Overview - -This deployment utilizes a microservices-based, cloud-native architecture: - -- **Core Components**: Each core service (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) runs as a separate `Deployment`. -- **Configuration Management**: Nginx configurations and Django application environment variables are managed via `ConfigMap`. **Note: For sensitive information like passwords, using `Secret` is highly recommended.** -- **Service Discovery**: All services are exposed internally within the cluster as `ClusterIP` type `Service`, enabling communication via service names. -- **External Access**: An `Ingress` resource is used to route external HTTP traffic to the Nginx service, which acts as the single entry point for the entire blog application. -- **Data Persistence**: A `local-storage` solution based on node-local paths is used. This requires you to manually create storage directories on a specific K8s node and statically bind them using `PersistentVolume` (PV) and `PersistentVolumeClaim` (PVC). - -## 1. Prerequisites - -Before you begin, please ensure you have the following: - -- A running Kubernetes cluster. -- The `kubectl` command-line tool configured to connect to your cluster. -- An [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) installed and configured in your cluster. -- Filesystem access to one of the nodes in your cluster (defaulted to `master` in the configs) to create local storage directories. - -## 2. Deployment Steps - -### Step 1: Create a Namespace - -We recommend deploying all DjangoBlog-related resources in a dedicated namespace for better management. - -```bash -# Create a namespace named 'djangoblog' -kubectl create namespace djangoblog -``` - -### Step 2: Configure Persistent Storage - -This setup uses Local Persistent Volumes. You need to create the data storage directories on a node within your cluster (the default is the `master` node in `pv.yaml`). - -```bash -# Log in to your master node -ssh user@master-node - -# Create the required storage directories -sudo mkdir -p /mnt/local-storage-db -sudo mkdir -p /mnt/local-storage-djangoblog -sudo mkdir -p /mnt/resource/ -sudo mkdir -p /mnt/local-storage-elasticsearch - -# Log out from the node -exit -``` -**Note**: If you wish to store data on a different node or use different paths, you must modify the `nodeAffinity` and `local.path` settings in the `deploy/k8s/pv.yaml` file. - -After creating the directories, apply the storage-related configurations: - -```bash -# Apply the StorageClass -kubectl apply -f deploy/k8s/storageclass.yaml - -# Apply the PersistentVolumes (PVs) -kubectl apply -f deploy/k8s/pv.yaml - -# Apply the PersistentVolumeClaims (PVCs) -kubectl apply -f deploy/k8s/pvc.yaml -``` - -### Step 3: Configure the Application - -Before deploying the application, you need to edit the `deploy/k8s/configmap.yaml` file to modify sensitive information and custom settings. - -**It is strongly recommended to change the following fields:** -- `DJANGO_SECRET_KEY`: Change to a random, complex string. -- `DJANGO_MYSQL_PASSWORD` and `MYSQL_ROOT_PASSWORD`: Change to your own secure database password. - -```bash -# Edit the ConfigMap file -vim deploy/k8s/configmap.yaml - -# Apply the configuration -kubectl apply -f deploy/k8s/configmap.yaml -``` - -### Step 4: Deploy the Application Stack - -Now, we can deploy all the core services. - -```bash -# Deploy the Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) -kubectl apply -f deploy/k8s/deployment.yaml - -# Deploy the Services (to create internal endpoints for the Deployments) -kubectl apply -f deploy/k8s/service.yaml -``` - -The deployment may take some time. You can run the following command to check if all Pods are running successfully (STATUS should be `Running`): - -```bash -kubectl get pods -n djangoblog -w -``` - -### Step 5: Expose the Application Externally - -Finally, expose the Nginx service to external traffic by applying the `Ingress` rule. - -```bash -# Apply the Ingress rule -kubectl apply -f deploy/k8s/gateway.yaml -``` - -Once deployed, you can access your blog via the external IP address of your Ingress Controller. Use the following command to find the address: - -```bash -kubectl get ingress -n djangoblog -``` - -### Step 6: First-Time Initialization - -Similar to the Docker deployment, you need to get a shell into the DjangoBlog application Pod to perform database initialization and create a superuser on the first run. - -```bash -# First, get the name of a djangoblog pod -kubectl get pods -n djangoblog | grep djangoblog - -# Exec into one of the Pods (replace [pod-name] with the name from the previous step) -kubectl exec -it [pod-name] -n djangoblog -- bash - -# Inside the Pod, run the following commands: -# Create a superuser account (follow the prompts) -python manage.py createsuperuser - -# (Optional) Create some test data -python manage.py create_testdata - -# (Optional, if ES is enabled) Create the search index -python manage.py rebuild_index - -# Exit the Pod -exit -``` - -Congratulations! You have successfully deployed DjangoBlog on your Kubernetes cluster. \ No newline at end of file diff --git a/src/docs/k8s.md b/src/docs/k8s.md deleted file mode 100644 index 9da3c28..0000000 --- a/src/docs/k8s.md +++ /dev/null @@ -1,141 +0,0 @@ -# 使用 Kubernetes 部署 DjangoBlog - -本文档将指导您如何在 Kubernetes (K8s) 集群上部署 DjangoBlog 应用。我们提供了一套完整的 `.yaml` 配置文件,位于 `deploy/k8s` 目录下,用于部署一个包含 DjangoBlog 应用、Nginx、MySQL、Redis 和 Elasticsearch 的完整服务栈。 - -## 架构概览 - -本次部署采用的是微服务化的云原生架构: - -- **核心组件**: 每个核心服务 (DjangoBlog, Nginx, MySQL, Redis, Elasticsearch) 都将作为独立的 `Deployment` 运行。 -- **配置管理**: Nginx 的配置文件和 Django 应用的环境变量通过 `ConfigMap` 进行管理。**注意:敏感信息(如密码)建议使用 `Secret` 进行管理。** -- **服务发现**: 所有服务都通过 `ClusterIP` 类型的 `Service` 在集群内部暴露,并通过服务名相互通信。 -- **外部访问**: 使用 `Ingress` 资源将外部的 HTTP 流量路由到 Nginx 服务,作为整个博客应用的统一入口。 -- **数据持久化**: 采用基于节点本地路径的 `local-storage` 方案。这需要您在指定的 K8s 节点上手动创建存储目录,并通过 `PersistentVolume` (PV) 和 `PersistentVolumeClaim` (PVC) 进行静态绑定。 - -## 1. 环境准备 - -在开始之前,请确保您已具备以下环境: - -- 一个正在运行的 Kubernetes 集群。 -- `kubectl` 命令行工具已配置并能够连接到您的集群。 -- 集群中已安装并配置好 [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/)。 -- 对集群中的一个节点(默认为 `master`)拥有文件系统访问权限,用于创建本地存储目录。 - -## 2. 部署步骤 - -### 步骤 1: 创建命名空间 - -我们建议将 DjangoBlog 相关的所有资源都部署在一个独立的命名空间中,便于管理。 - -```bash -# 创建一个名为 djangoblog 的命名空间 -kubectl create namespace djangoblog -``` - -### 步骤 2: 配置持久化存储 - -此方案使用本地持久卷 (Local Persistent Volume)。您需要在集群的一个节点上(在 `pv.yaml` 文件中默认为 `master` 节点)创建用于数据存储的目录。 - -```bash -# 登录到您的 master 节点 -ssh user@master-node - -# 创建所需的存储目录 -sudo mkdir -p /mnt/local-storage-db -sudo mkdir -p /mnt/local-storage-djangoblog -sudo mkdir -p /mnt/resource/ -sudo mkdir -p /mnt/local-storage-elasticsearch - -# 退出节点 -exit -``` -**注意**: 如果您希望将数据存储在其他节点或使用不同的路径,请务必修改 `deploy/k8s/pv.yaml` 文件中 `nodeAffinity` 和 `local.path` 的配置。 - -创建目录后,应用存储相关的配置文件: - -```bash -# 应用 StorageClass -kubectl apply -f deploy/k8s/storageclass.yaml - -# 应用 PersistentVolume (PV) -kubectl apply -f deploy/k8s/pv.yaml - -# 应用 PersistentVolumeClaim (PVC) -kubectl apply -f deploy/k8s/pvc.yaml -``` - -### 步骤 3: 配置应用 - -在部署应用之前,您需要编辑 `deploy/k8s/configmap.yaml` 文件,修改其中的敏感信息和个性化配置。 - -**强烈建议修改以下字段:** -- `DJANGO_SECRET_KEY`: 修改为一个随机且复杂的字符串。 -- `DJANGO_MYSQL_PASSWORD` 和 `MYSQL_ROOT_PASSWORD`: 修改为您自己的数据库密码。 - -```bash -# 编辑 ConfigMap 文件 -vim deploy/k8s/configmap.yaml - -# 应用配置 -kubectl apply -f deploy/k8s/configmap.yaml -``` - -### 步骤 4: 部署应用服务栈 - -现在,我们可以部署所有的核心服务了。 - -```bash -# 部署 Deployments (DjangoBlog, MySQL, Redis, Nginx, ES) -kubectl apply -f deploy/k8s/deployment.yaml - -# 部署 Services (为 Deployments 创建内部访问端点) -kubectl apply -f deploy/k8s/service.yaml -``` - -部署需要一些时间,您可以运行以下命令检查所有 Pod 是否都已成功运行 (STATUS 为 `Running`): - -```bash -kubectl get pods -n djangoblog -w -``` - -### 步骤 5: 暴露应用到外部 - -最后,通过应用 `Ingress` 规则来将外部流量引导至我们的 Nginx 服务。 - -```bash -# 应用 Ingress 规则 -kubectl apply -f deploy/k8s/gateway.yaml -``` - -部署完成后,您可以通过 Ingress Controller 的外部 IP 地址来访问您的博客。执行以下命令获取地址: - -```bash -kubectl get ingress -n djangoblog -``` - -### 步骤 6: 首次运行的初始化操作 - -与 Docker 部署类似,首次运行时,您需要进入 DjangoBlog 应用的 Pod 来执行数据库初始化和创建管理员账户。 - -```bash -# 首先,获取 djangoblog pod 的名称 -kubectl get pods -n djangoblog | grep djangoblog - -# 进入其中一个 Pod (将 [pod-name] 替换为上一步获取到的名称) -kubectl exec -it [pod-name] -n djangoblog -- bash - -# 在 Pod 内部执行以下命令: -# 创建超级管理员账户 (请按照提示操作) -python manage.py createsuperuser - -# (可选) 创建测试数据 -python manage.py create_testdata - -# (可选,如果启用了 ES) 创建索引 -python manage.py rebuild_index - -# 退出 Pod -exit -``` - -至此,您已成功在 Kubernetes 集群上完成了 DjangoBlog 的部署! \ No newline at end of file diff --git a/src/locale/en/LC_MESSAGES/django.mo b/src/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index f63669f..0000000 Binary files a/src/locale/en/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/locale/en/LC_MESSAGES/django.po b/src/locale/en/LC_MESSAGES/django.po deleted file mode 100644 index c80b30a..0000000 --- a/src/locale/en/LC_MESSAGES/django.po +++ /dev/null @@ -1,685 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-13 16:02+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#: .\accounts\admin.py:12 -msgid "password" -msgstr "password" - -#: .\accounts\admin.py:13 -msgid "Enter password again" -msgstr "Enter password again" - -#: .\accounts\admin.py:24 .\accounts\forms.py:89 -msgid "passwords do not match" -msgstr "passwords do not match" - -#: .\accounts\forms.py:36 -msgid "email already exists" -msgstr "email already exists" - -#: .\accounts\forms.py:46 .\accounts\forms.py:50 -msgid "New password" -msgstr "New password" - -#: .\accounts\forms.py:60 -msgid "Confirm password" -msgstr "Confirm password" - -#: .\accounts\forms.py:70 .\accounts\forms.py:116 -msgid "Email" -msgstr "Email" - -#: .\accounts\forms.py:76 .\accounts\forms.py:80 -msgid "Code" -msgstr "Code" - -#: .\accounts\forms.py:100 .\accounts\tests.py:194 -msgid "email does not exist" -msgstr "email does not exist" - -#: .\accounts\models.py:12 .\oauth\models.py:17 -msgid "nick name" -msgstr "nick name" - -#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 -#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 -#: .\oauth\models.py:53 -msgid "creation time" -msgstr "creation time" - -#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 -#: .\oauth\models.py:54 -msgid "last modify time" -msgstr "last modify time" - -#: .\accounts\models.py:15 -msgid "create source" -msgstr "create source" - -#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 -msgid "user" -msgstr "user" - -#: .\accounts\tests.py:216 .\accounts\utils.py:39 -msgid "Verification code error" -msgstr "Verification code error" - -#: .\accounts\utils.py:13 -msgid "Verify Email" -msgstr "Verify Email" - -#: .\accounts\utils.py:21 -#, python-format -msgid "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" -msgstr "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" - -#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 -#: .\oauth\models.py:12 -msgid "author" -msgstr "author" - -#: .\blog\admin.py:53 -msgid "Publish selected articles" -msgstr "Publish selected articles" - -#: .\blog\admin.py:54 -msgid "Draft selected articles" -msgstr "Draft selected articles" - -#: .\blog\admin.py:55 -msgid "Close article comments" -msgstr "Close article comments" - -#: .\blog\admin.py:56 -msgid "Open article comments" -msgstr "Open article comments" - -#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 -#: .\templates\blog\tags\sidebar.html:40 -msgid "category" -msgstr "category" - -#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 -msgid "index" -msgstr "index" - -#: .\blog\models.py:21 -msgid "list" -msgstr "list" - -#: .\blog\models.py:22 -msgid "post" -msgstr "post" - -#: .\blog\models.py:23 -msgid "all" -msgstr "all" - -#: .\blog\models.py:24 -msgid "slide" -msgstr "slide" - -#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 -msgid "modify time" -msgstr "modify time" - -#: .\blog\models.py:63 -msgid "Draft" -msgstr "Draft" - -#: .\blog\models.py:64 -msgid "Published" -msgstr "Published" - -#: .\blog\models.py:67 -msgid "Open" -msgstr "Open" - -#: .\blog\models.py:68 -msgid "Close" -msgstr "Close" - -#: .\blog\models.py:71 .\comments\admin.py:47 -msgid "Article" -msgstr "Article" - -#: .\blog\models.py:72 -msgid "Page" -msgstr "Page" - -#: .\blog\models.py:74 .\blog\models.py:280 -msgid "title" -msgstr "title" - -#: .\blog\models.py:75 -msgid "body" -msgstr "body" - -#: .\blog\models.py:77 -msgid "publish time" -msgstr "publish time" - -#: .\blog\models.py:79 -msgid "status" -msgstr "status" - -#: .\blog\models.py:84 -msgid "comment status" -msgstr "comment status" - -#: .\blog\models.py:88 .\oauth\models.py:43 -msgid "type" -msgstr "type" - -#: .\blog\models.py:89 -msgid "views" -msgstr "views" - -#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 -msgid "order" -msgstr "order" - -#: .\blog\models.py:98 -msgid "show toc" -msgstr "show toc" - -#: .\blog\models.py:105 .\blog\models.py:249 -msgid "tag" -msgstr "tag" - -#: .\blog\models.py:115 .\comments\models.py:21 -msgid "article" -msgstr "article" - -#: .\blog\models.py:171 -msgid "category name" -msgstr "category name" - -#: .\blog\models.py:174 -msgid "parent category" -msgstr "parent category" - -#: .\blog\models.py:234 -msgid "tag name" -msgstr "tag name" - -#: .\blog\models.py:256 -msgid "link name" -msgstr "link name" - -#: .\blog\models.py:257 .\blog\models.py:271 -msgid "link" -msgstr "link" - -#: .\blog\models.py:260 -msgid "is show" -msgstr "is show" - -#: .\blog\models.py:262 -msgid "show type" -msgstr "show type" - -#: .\blog\models.py:281 -msgid "content" -msgstr "content" - -#: .\blog\models.py:283 .\oauth\models.py:52 -msgid "is enable" -msgstr "is enable" - -#: .\blog\models.py:289 -msgid "sidebar" -msgstr "sidebar" - -#: .\blog\models.py:299 -msgid "site name" -msgstr "site name" - -#: .\blog\models.py:305 -msgid "site description" -msgstr "site description" - -#: .\blog\models.py:311 -msgid "site seo description" -msgstr "site seo description" - -#: .\blog\models.py:313 -msgid "site keywords" -msgstr "site keywords" - -#: .\blog\models.py:318 -msgid "article sub length" -msgstr "article sub length" - -#: .\blog\models.py:319 -msgid "sidebar article count" -msgstr "sidebar article count" - -#: .\blog\models.py:320 -msgid "sidebar comment count" -msgstr "sidebar comment count" - -#: .\blog\models.py:321 -msgid "article comment count" -msgstr "article comment count" - -#: .\blog\models.py:322 -msgid "show adsense" -msgstr "show adsense" - -#: .\blog\models.py:324 -msgid "adsense code" -msgstr "adsense code" - -#: .\blog\models.py:325 -msgid "open site comment" -msgstr "open site comment" - -#: .\blog\models.py:352 -msgid "Website configuration" -msgstr "Website configuration" - -#: .\blog\models.py:360 -msgid "There can only be one configuration" -msgstr "There can only be one configuration" - -#: .\blog\views.py:348 -msgid "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" -msgstr "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" - -#: .\blog\views.py:356 -msgid "Sorry, the server is busy, please click the home page to see other?" -msgstr "Sorry, the server is busy, please click the home page to see other?" - -#: .\blog\views.py:369 -msgid "Sorry, you do not have permission to access this page?" -msgstr "Sorry, you do not have permission to access this page?" - -#: .\comments\admin.py:15 -msgid "Disable comments" -msgstr "Disable comments" - -#: .\comments\admin.py:16 -msgid "Enable comments" -msgstr "Enable comments" - -#: .\comments\admin.py:46 -msgid "User" -msgstr "User" - -#: .\comments\models.py:25 -msgid "parent comment" -msgstr "parent comment" - -#: .\comments\models.py:29 -msgid "enable" -msgstr "enable" - -#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 -msgid "comment" -msgstr "comment" - -#: .\comments\utils.py:13 -msgid "Thanks for your comment" -msgstr "Thanks for your comment" - -#: .\comments\utils.py:15 -#, python-format -msgid "" -"

Thank you very much for your comments on this site

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

Thank you very much for your comments on this site

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

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

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

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

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

Please click the link below to bind your email

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

Please click the link below to bind your email

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

Thank you very much for your comments on this site

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

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

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

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

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

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

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

Please click the link below to bind your email

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

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

\n" -"\n" -" %(url)s\n" -"\n" -"再次感谢您!\n" -"
\n" -"如果上面的链接打不开,请复制此链接到您的浏览器。\n" -"%(url)s\n" -" " - -#: .\oauth\views.py:228 .\oauth\views.py:240 -msgid "Bind your email" -msgstr "绑定邮箱" - -#: .\oauth\views.py:242 -msgid "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." -msgstr "恭喜您,还差一步就绑定成功了,请登录您的邮箱查看邮件完成绑定,谢谢。" - -#: .\oauth\views.py:245 -msgid "Binding successful" -msgstr "绑定成功" - -#: .\oauth\views.py:247 -#, python-format -msgid "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." -msgstr "" -"恭喜您绑定成功,您以后可以使用%(oauthuser_type)s来直接免密码登录本站啦,感谢" -"您对本站对关注。" - -#: .\templates\account\forget_password.html:7 -msgid "forget the password" -msgstr "忘记密码" - -#: .\templates\account\forget_password.html:18 -msgid "get verification code" -msgstr "获取验证码" - -#: .\templates\account\forget_password.html:19 -msgid "submit" -msgstr "提交" - -#: .\templates\account\login.html:36 -msgid "Create Account" -msgstr "创建账号" - -#: .\templates\account\login.html:42 -#| msgid "forget the password" -msgid "Forget Password" -msgstr "忘记密码" - -#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 -msgid "login" -msgstr "登录" - -#: .\templates\account\result.html:22 -msgid "back to the homepage" -msgstr "返回首页吧" - -#: .\templates\blog\article_archives.html:7 -#: .\templates\blog\article_archives.html:24 -msgid "article archive" -msgstr "文章归档" - -#: .\templates\blog\article_archives.html:32 -msgid "year" -msgstr "年" - -#: .\templates\blog\article_archives.html:36 -msgid "month" -msgstr "月" - -#: .\templates\blog\tags\article_info.html:12 -msgid "pin to top" -msgstr "置顶" - -#: .\templates\blog\tags\article_info.html:28 -msgid "comments" -msgstr "评论" - -#: .\templates\blog\tags\article_info.html:58 -msgid "toc" -msgstr "目录" - -#: .\templates\blog\tags\article_meta_info.html:6 -msgid "posted in" -msgstr "发布于" - -#: .\templates\blog\tags\article_meta_info.html:14 -msgid "and tagged" -msgstr "并标记为" - -#: .\templates\blog\tags\article_meta_info.html:25 -msgid "by " -msgstr "由" - -#: .\templates\blog\tags\article_meta_info.html:29 -#, python-format -msgid "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " -msgstr "" -"\n" -" title=\"查看所有由 %(article.author.username)s\"发布的文章\n" -" " - -#: .\templates\blog\tags\article_meta_info.html:44 -msgid "on" -msgstr "在" - -#: .\templates\blog\tags\article_meta_info.html:54 -msgid "edit" -msgstr "编辑" - -#: .\templates\blog\tags\article_pagination.html:4 -msgid "article navigation" -msgstr "文章导航" - -#: .\templates\blog\tags\article_pagination.html:9 -msgid "earlier articles" -msgstr "早期文章" - -#: .\templates\blog\tags\article_pagination.html:12 -msgid "newer articles" -msgstr "较新文章" - -#: .\templates\blog\tags\article_tag_list.html:5 -msgid "tags" -msgstr "标签" - -#: .\templates\blog\tags\sidebar.html:7 -msgid "search" -msgstr "搜索" - -#: .\templates\blog\tags\sidebar.html:50 -msgid "recent comments" -msgstr "近期评论" - -#: .\templates\blog\tags\sidebar.html:57 -msgid "published on" -msgstr "发表于" - -#: .\templates\blog\tags\sidebar.html:65 -msgid "recent articles" -msgstr "近期文章" - -#: .\templates\blog\tags\sidebar.html:77 -msgid "bookmark" -msgstr "书签" - -#: .\templates\blog\tags\sidebar.html:96 -msgid "Tag Cloud" -msgstr "标签云" - -#: .\templates\blog\tags\sidebar.html:107 -msgid "Welcome to star or fork the source code of this site" -msgstr "欢迎您STAR或者FORK本站源代码" - -#: .\templates\blog\tags\sidebar.html:118 -msgid "Function" -msgstr "功能" - -#: .\templates\blog\tags\sidebar.html:120 -msgid "management site" -msgstr "管理站点" - -#: .\templates\blog\tags\sidebar.html:122 -msgid "logout" -msgstr "登出" - -#: .\templates\blog\tags\sidebar.html:129 -msgid "Track record" -msgstr "运动轨迹记录" - -#: .\templates\blog\tags\sidebar.html:135 -msgid "Click me to return to the top" -msgstr "点我返回顶部" - -#: .\templates\oauth\oauth_applications.html:5 -#| msgid "login" -msgid "quick login" -msgstr "快捷登录" - -#: .\templates\share_layout\nav.html:26 -msgid "Article archive" -msgstr "文章归档" diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.mo b/src/locale/zh_Hant/LC_MESSAGES/django.mo deleted file mode 100644 index fe2ea17..0000000 Binary files a/src/locale/zh_Hant/LC_MESSAGES/django.mo and /dev/null differ diff --git a/src/locale/zh_Hant/LC_MESSAGES/django.po b/src/locale/zh_Hant/LC_MESSAGES/django.po deleted file mode 100644 index a2920ce..0000000 --- a/src/locale/zh_Hant/LC_MESSAGES/django.po +++ /dev/null @@ -1,668 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-13 16:02+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#: .\accounts\admin.py:12 -msgid "password" -msgstr "密碼" - -#: .\accounts\admin.py:13 -msgid "Enter password again" -msgstr "再次輸入密碼" - -#: .\accounts\admin.py:24 .\accounts\forms.py:89 -msgid "passwords do not match" -msgstr "密碼不匹配" - -#: .\accounts\forms.py:36 -msgid "email already exists" -msgstr "郵箱已存在" - -#: .\accounts\forms.py:46 .\accounts\forms.py:50 -msgid "New password" -msgstr "新密碼" - -#: .\accounts\forms.py:60 -msgid "Confirm password" -msgstr "確認密碼" - -#: .\accounts\forms.py:70 .\accounts\forms.py:116 -msgid "Email" -msgstr "郵箱" - -#: .\accounts\forms.py:76 .\accounts\forms.py:80 -msgid "Code" -msgstr "驗證碼" - -#: .\accounts\forms.py:100 .\accounts\tests.py:194 -msgid "email does not exist" -msgstr "郵箱不存在" - -#: .\accounts\models.py:12 .\oauth\models.py:17 -msgid "nick name" -msgstr "昵稱" - -#: .\accounts\models.py:13 .\blog\models.py:29 .\blog\models.py:266 -#: .\blog\models.py:284 .\comments\models.py:13 .\oauth\models.py:23 -#: .\oauth\models.py:53 -msgid "creation time" -msgstr "創建時間" - -#: .\accounts\models.py:14 .\comments\models.py:14 .\oauth\models.py:24 -#: .\oauth\models.py:54 -msgid "last modify time" -msgstr "最後修改時間" - -#: .\accounts\models.py:15 -msgid "create source" -msgstr "來源" - -#: .\accounts\models.py:33 .\djangoblog\logentryadmin.py:81 -msgid "user" -msgstr "用戶" - -#: .\accounts\tests.py:216 .\accounts\utils.py:39 -msgid "Verification code error" -msgstr "驗證碼錯誤" - -#: .\accounts\utils.py:13 -msgid "Verify Email" -msgstr "驗證郵箱" - -#: .\accounts\utils.py:21 -#, python-format -msgid "" -"You are resetting the password, the verification code is:%(code)s, valid " -"within 5 minutes, please keep it properly" -msgstr "您正在重置密碼,驗證碼為:%(code)s,5分鐘內有效 請妥善保管." - -#: .\blog\admin.py:13 .\blog\models.py:92 .\comments\models.py:17 -#: .\oauth\models.py:12 -msgid "author" -msgstr "作者" - -#: .\blog\admin.py:53 -msgid "Publish selected articles" -msgstr "發布選中的文章" - -#: .\blog\admin.py:54 -msgid "Draft selected articles" -msgstr "選中文章設為草稿" - -#: .\blog\admin.py:55 -msgid "Close article comments" -msgstr "關閉文章評論" - -#: .\blog\admin.py:56 -msgid "Open article comments" -msgstr "打開文章評論" - -#: .\blog\admin.py:89 .\blog\models.py:101 .\blog\models.py:183 -#: .\templates\blog\tags\sidebar.html:40 -msgid "category" -msgstr "分類目錄" - -#: .\blog\models.py:20 .\blog\models.py:179 .\templates\share_layout\nav.html:8 -msgid "index" -msgstr "首頁" - -#: .\blog\models.py:21 -msgid "list" -msgstr "列表" - -#: .\blog\models.py:22 -msgid "post" -msgstr "文章" - -#: .\blog\models.py:23 -msgid "all" -msgstr "所有" - -#: .\blog\models.py:24 -msgid "slide" -msgstr "側邊欄" - -#: .\blog\models.py:30 .\blog\models.py:267 .\blog\models.py:285 -msgid "modify time" -msgstr "修改時間" - -#: .\blog\models.py:63 -msgid "Draft" -msgstr "草稿" - -#: .\blog\models.py:64 -msgid "Published" -msgstr "發布" - -#: .\blog\models.py:67 -msgid "Open" -msgstr "打開" - -#: .\blog\models.py:68 -msgid "Close" -msgstr "關閉" - -#: .\blog\models.py:71 .\comments\admin.py:47 -msgid "Article" -msgstr "文章" - -#: .\blog\models.py:72 -msgid "Page" -msgstr "頁面" - -#: .\blog\models.py:74 .\blog\models.py:280 -msgid "title" -msgstr "標題" - -#: .\blog\models.py:75 -msgid "body" -msgstr "內容" - -#: .\blog\models.py:77 -msgid "publish time" -msgstr "發布時間" - -#: .\blog\models.py:79 -msgid "status" -msgstr "狀態" - -#: .\blog\models.py:84 -msgid "comment status" -msgstr "評論狀態" - -#: .\blog\models.py:88 .\oauth\models.py:43 -msgid "type" -msgstr "類型" - -#: .\blog\models.py:89 -msgid "views" -msgstr "閱讀量" - -#: .\blog\models.py:97 .\blog\models.py:258 .\blog\models.py:282 -msgid "order" -msgstr "排序" - -#: .\blog\models.py:98 -msgid "show toc" -msgstr "顯示目錄" - -#: .\blog\models.py:105 .\blog\models.py:249 -msgid "tag" -msgstr "標簽" - -#: .\blog\models.py:115 .\comments\models.py:21 -msgid "article" -msgstr "文章" - -#: .\blog\models.py:171 -msgid "category name" -msgstr "分類名" - -#: .\blog\models.py:174 -msgid "parent category" -msgstr "上級分類" - -#: .\blog\models.py:234 -msgid "tag name" -msgstr "標簽名" - -#: .\blog\models.py:256 -msgid "link name" -msgstr "鏈接名" - -#: .\blog\models.py:257 .\blog\models.py:271 -msgid "link" -msgstr "鏈接" - -#: .\blog\models.py:260 -msgid "is show" -msgstr "是否顯示" - -#: .\blog\models.py:262 -msgid "show type" -msgstr "顯示類型" - -#: .\blog\models.py:281 -msgid "content" -msgstr "內容" - -#: .\blog\models.py:283 .\oauth\models.py:52 -msgid "is enable" -msgstr "是否啟用" - -#: .\blog\models.py:289 -msgid "sidebar" -msgstr "側邊欄" - -#: .\blog\models.py:299 -msgid "site name" -msgstr "站點名稱" - -#: .\blog\models.py:305 -msgid "site description" -msgstr "站點描述" - -#: .\blog\models.py:311 -msgid "site seo description" -msgstr "站點SEO描述" - -#: .\blog\models.py:313 -msgid "site keywords" -msgstr "關鍵字" - -#: .\blog\models.py:318 -msgid "article sub length" -msgstr "文章摘要長度" - -#: .\blog\models.py:319 -msgid "sidebar article count" -msgstr "側邊欄文章數目" - -#: .\blog\models.py:320 -msgid "sidebar comment count" -msgstr "側邊欄評論數目" - -#: .\blog\models.py:321 -msgid "article comment count" -msgstr "文章頁面默認顯示評論數目" - -#: .\blog\models.py:322 -msgid "show adsense" -msgstr "是否顯示廣告" - -#: .\blog\models.py:324 -msgid "adsense code" -msgstr "廣告內容" - -#: .\blog\models.py:325 -msgid "open site comment" -msgstr "公共頭部" - -#: .\blog\models.py:352 -msgid "Website configuration" -msgstr "網站配置" - -#: .\blog\models.py:360 -msgid "There can only be one configuration" -msgstr "只能有一個配置" - -#: .\blog\views.py:348 -msgid "" -"Sorry, the page you requested is not found, please click the home page to " -"see other?" -msgstr "抱歉,你所訪問的頁面找不到,請點擊首頁看看別的?" - -#: .\blog\views.py:356 -msgid "Sorry, the server is busy, please click the home page to see other?" -msgstr "抱歉,服務出錯了,請點擊首頁看看別的?" - -#: .\blog\views.py:369 -msgid "Sorry, you do not have permission to access this page?" -msgstr "抱歉,你沒用權限訪問此頁面。" - -#: .\comments\admin.py:15 -msgid "Disable comments" -msgstr "禁用評論" - -#: .\comments\admin.py:16 -msgid "Enable comments" -msgstr "啟用評論" - -#: .\comments\admin.py:46 -msgid "User" -msgstr "用戶" - -#: .\comments\models.py:25 -msgid "parent comment" -msgstr "上級評論" - -#: .\comments\models.py:29 -msgid "enable" -msgstr "啟用" - -#: .\comments\models.py:34 .\templates\blog\tags\article_info.html:30 -msgid "comment" -msgstr "評論" - -#: .\comments\utils.py:13 -msgid "Thanks for your comment" -msgstr "感謝你的評論" - -#: .\comments\utils.py:15 -#, python-format -msgid "" -"

Thank you very much for your comments on this site

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

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

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

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

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

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

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

Please click the link below to bind your email

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

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

\n" -"\n" -" %(url)s\n" -"\n" -"再次感謝您!\n" -"
\n" -"如果上面的鏈接打不開,請復製此鏈接到您的瀏覽器。\n" -"%(url)s\n" -" " - -#: .\oauth\views.py:228 .\oauth\views.py:240 -msgid "Bind your email" -msgstr "綁定郵箱" - -#: .\oauth\views.py:242 -msgid "" -"Congratulations, the binding is just one step away. Please log in to your " -"email to check the email to complete the binding. Thank you." -msgstr "恭喜您,還差一步就綁定成功了,請登錄您的郵箱查看郵件完成綁定,謝謝。" - -#: .\oauth\views.py:245 -msgid "Binding successful" -msgstr "綁定成功" - -#: .\oauth\views.py:247 -#, python-format -msgid "" -"Congratulations, you have successfully bound your email address. You can use " -"%(oauthuser_type)s to directly log in to this website without a password. " -"You are welcome to continue to follow this site." -msgstr "" -"恭喜您綁定成功,您以後可以使用%(oauthuser_type)s來直接免密碼登錄本站啦,感謝" -"您對本站對關註。" - -#: .\templates\account\forget_password.html:7 -msgid "forget the password" -msgstr "忘記密碼" - -#: .\templates\account\forget_password.html:18 -msgid "get verification code" -msgstr "獲取驗證碼" - -#: .\templates\account\forget_password.html:19 -msgid "submit" -msgstr "提交" - -#: .\templates\account\login.html:36 -msgid "Create Account" -msgstr "創建賬號" - -#: .\templates\account\login.html:42 -#, fuzzy -#| msgid "forget the password" -msgid "Forget Password" -msgstr "忘記密碼" - -#: .\templates\account\result.html:18 .\templates\blog\tags\sidebar.html:126 -msgid "login" -msgstr "登錄" - -#: .\templates\account\result.html:22 -msgid "back to the homepage" -msgstr "返回首頁吧" - -#: .\templates\blog\article_archives.html:7 -#: .\templates\blog\article_archives.html:24 -msgid "article archive" -msgstr "文章歸檔" - -#: .\templates\blog\article_archives.html:32 -msgid "year" -msgstr "年" - -#: .\templates\blog\article_archives.html:36 -msgid "month" -msgstr "月" - -#: .\templates\blog\tags\article_info.html:12 -msgid "pin to top" -msgstr "置頂" - -#: .\templates\blog\tags\article_info.html:28 -msgid "comments" -msgstr "評論" - -#: .\templates\blog\tags\article_info.html:58 -msgid "toc" -msgstr "目錄" - -#: .\templates\blog\tags\article_meta_info.html:6 -msgid "posted in" -msgstr "發布於" - -#: .\templates\blog\tags\article_meta_info.html:14 -msgid "and tagged" -msgstr "並標記為" - -#: .\templates\blog\tags\article_meta_info.html:25 -msgid "by " -msgstr "由" - -#: .\templates\blog\tags\article_meta_info.html:29 -#, python-format -msgid "" -"\n" -" title=\"View all articles published by " -"%(article.author.username)s\"\n" -" " -msgstr "" -"\n" -" title=\"查看所有由 %(article.author.username)s\"發布的文章\n" -" " - -#: .\templates\blog\tags\article_meta_info.html:44 -msgid "on" -msgstr "在" - -#: .\templates\blog\tags\article_meta_info.html:54 -msgid "edit" -msgstr "編輯" - -#: .\templates\blog\tags\article_pagination.html:4 -msgid "article navigation" -msgstr "文章導航" - -#: .\templates\blog\tags\article_pagination.html:9 -msgid "earlier articles" -msgstr "早期文章" - -#: .\templates\blog\tags\article_pagination.html:12 -msgid "newer articles" -msgstr "較新文章" - -#: .\templates\blog\tags\article_tag_list.html:5 -msgid "tags" -msgstr "標簽" - -#: .\templates\blog\tags\sidebar.html:7 -msgid "search" -msgstr "搜索" - -#: .\templates\blog\tags\sidebar.html:50 -msgid "recent comments" -msgstr "近期評論" - -#: .\templates\blog\tags\sidebar.html:57 -msgid "published on" -msgstr "發表於" - -#: .\templates\blog\tags\sidebar.html:65 -msgid "recent articles" -msgstr "近期文章" - -#: .\templates\blog\tags\sidebar.html:77 -msgid "bookmark" -msgstr "書簽" - -#: .\templates\blog\tags\sidebar.html:96 -msgid "Tag Cloud" -msgstr "標簽雲" - -#: .\templates\blog\tags\sidebar.html:107 -msgid "Welcome to star or fork the source code of this site" -msgstr "歡迎您STAR或者FORK本站源代碼" - -#: .\templates\blog\tags\sidebar.html:118 -msgid "Function" -msgstr "功能" - -#: .\templates\blog\tags\sidebar.html:120 -msgid "management site" -msgstr "管理站點" - -#: .\templates\blog\tags\sidebar.html:122 -msgid "logout" -msgstr "登出" - -#: .\templates\blog\tags\sidebar.html:129 -msgid "Track record" -msgstr "運動軌跡記錄" - -#: .\templates\blog\tags\sidebar.html:135 -msgid "Click me to return to the top" -msgstr "點我返回頂部" - -#: .\templates\oauth\oauth_applications.html:5 -#| msgid "login" -msgid "quick login" -msgstr "快捷登錄" - -#: .\templates\share_layout\nav.html:26 -msgid "Article archive" -msgstr "文章歸檔" diff --git a/src/logs/djangoblog.log b/src/logs/djangoblog.log new file mode 100644 index 0000000..493da88 --- /dev/null +++ b/src/logs/djangoblog.log @@ -0,0 +1,939 @@ +[2025-10-14 20:36:01,516] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-14 20:36:01,516] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-14 20:36:01,518] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-14 20:36:01,518] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-14 20:36:01,519] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-14 20:36:01,519] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-14 20:36:01,520] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-14 20:36:01,520] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-14 20:36:01,521] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-14 20:36:01,521] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-14 20:36:01,521] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-14 20:36:01,521] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-14 20:36:01,523] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-14 20:36:01,523] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-14 20:36:01,523] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-14 20:36:01,523] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-14 20:36:01,525] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-14 20:36:01,525] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-14 20:36:01,525] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-14 20:36:01,525] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-14 20:36:01,527] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-14 20:36:01,527] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-14 20:36:01,527] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-14 20:36:01,527] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-14 20:36:01,528] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-14 20:36:01,528] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-14 20:36:01,529] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-14 20:36:01,529] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-14 20:36:02,586] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-14 20:36:02,586] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-14 20:36:02,587] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-14 20:36:02,587] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-14 20:36:02,588] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-14 20:36:02,588] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-14 20:36:02,588] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-14 20:36:02,588] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-14 20:36:02,590] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-14 20:36:02,590] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-14 20:36:02,590] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-14 20:36:02,590] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-14 20:36:02,591] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-14 20:36:02,591] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-14 20:36:02,591] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-14 20:36:02,591] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-14 20:36:02,593] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-14 20:36:02,593] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-14 20:36:02,593] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-14 20:36:02,593] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-14 20:36:02,594] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-14 20:36:02,594] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-14 20:36:02,594] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-14 20:36:02,594] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-14 20:36:02,596] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-14 20:36:02,596] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-14 20:36:02,596] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-14 20:36:02,596] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-14 20:36:02,607] INFO [django.utils.autoreload.run_with_reloader:668 autoreload] Watching for file changes with StatReloader +[2025-10-14 20:36:12,997] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:36:13,031] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:36:13,184] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:36:13,184] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:36:13,667] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:36:13,828] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:37:37,355] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:37:43,208] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:37:58,772] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:38:57,032] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/c.html"]} +[2025-10-14 20:38:57,032] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/c.html"]} +[2025-10-14 20:38:57,310] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:38:57,345] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:38:57,345] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:00,721] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/c.html"]} +[2025-10-14 20:39:00,721] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/c.html"]} +[2025-10-14 20:39:00,977] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:39:01,013] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:01,013] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:04,783] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/java.html"]} +[2025-10-14 20:39:04,783] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/java.html"]} +[2025-10-14 20:39:05,045] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:39:05,086] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:05,086] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:07,299] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/python.html"]} +[2025-10-14 20:39:07,299] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/python.html"]} +[2025-10-14 20:39:07,544] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:39:07,577] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:07,577] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:14,925] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/ji-zhu-fen-xiang.html"]} +[2025-10-14 20:39:14,925] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/ji-zhu-fen-xiang.html"]} +[2025-10-14 20:39:15,174] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:39:15,208] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:15,208] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:22,570] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/ji-zhu-qiu-zhu.html"]} +[2025-10-14 20:39:22,570] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/tag/ji-zhu-qiu-zhu.html"]} +[2025-10-14 20:39:22,855] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:39:22,894] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:39:22,894] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:40:35,655] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:40:35,655] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:40:36,018] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:40:36,051] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:40:36,051] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:42:18,929] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/14/1.html"]} +[2025-10-14 20:42:18,929] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/14/1.html"]} +[2025-10-14 20:42:19,272] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:42:19,305] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:42:19,305] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:43:00,493] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:43:00,493] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:43:00,821] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:43:00,862] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:43:00,862] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:43:32,291] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/14/1.html"]} +[2025-10-14 20:43:32,291] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/14/1.html"]} +[2025-10-14 20:43:32,592] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:43:32,626] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:43:32,626] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:43:37,686] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:43:38,551] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:43:38,779] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:44:13,528] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 20:44:18,070] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:44:18,070] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:44:18,071] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:18,071] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:18,072] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:18,072] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:18,073] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:18,073] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:18,074] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:18,074] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:18,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:18,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:18,158] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:18,158] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:18,159] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:25,308] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:25,308] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:25,309] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:25,309] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:25,309] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:25,309] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:25,310] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:25,310] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:25,310] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:25,310] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:25,310] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 20:44:25,492] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:44:25,492] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:44:25,494] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:44:25,494] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:44:25,495] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:25,495] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:25,495] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:25,495] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:25,496] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:25,705] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:44:25,707] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:44:25,740] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:44:25,740] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:44:26,025] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:44:26,259] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:44:39,270] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-14 20:44:39,386] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:44:39,762] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:44:39,803] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:44:40,092] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-14 20:44:49,669] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:49,669] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:44:49,670] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:49,670] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:49,671] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:44:49,835] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-14 20:44:49,922] ERROR [djangoblog.blog_signals.send_email_signal_handler:49 blog_signals] 失败邮箱号: ['test@test.com'], (500, b'Error: bad syntax', 'None') +[2025-10-14 20:44:49,922] ERROR [djangoblog.blog_signals.send_email_signal_handler:49 blog_signals] 失败邮箱号: ['test@test.com'], (500, b'Error: bad syntax', 'None') +[2025-10-14 20:44:49,967] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:44:49,987] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:44:50,452] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:44:50,517] INFO [blog.templatetags.blog_tags.gravatar_url:396 blog_tags] Using default avatar for test@test.com +[2025-10-14 20:44:50,666] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:44:50,803] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-14 20:45:40,205] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:45:40,205] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:45:40,206] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:45:40,206] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:45:40,208] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:45:40,208] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:45:40,209] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:45:40,209] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:45:40,211] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:45:40,211] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:45:40,212] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:45:40,212] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:45:40,212] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:45:40,212] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:45:40,483] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:45:40,526] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:45:40,526] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:47:32,528] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:47:32,528] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:47:32,859] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:47:32,897] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:47:32,897] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:47:42,168] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:47:42,168] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:47:42,519] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:47:42,552] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:47:42,552] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:48:28,419] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:48:28,419] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:48:28,759] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:48:28,865] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:48:28,865] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:49:18,735] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:49:19,036] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:49:19,296] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:49:23,302] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:49:23,302] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:49:23,303] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:23,303] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:23,303] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:23,303] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:23,304] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:23,304] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:23,304] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:23,304] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:23,306] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:23,306] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:23,389] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:23,389] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:23,389] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:23,389] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:23,391] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:23,391] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:23,391] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:23,391] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:23,392] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:23,392] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:30,946] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:30,946] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:30,947] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:30,947] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:30,947] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:30,947] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:30,948] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:30,948] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:30,948] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:30,948] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:30,949] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 20:49:31,411] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:49:31,411] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:49:31,412] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:49:31,412] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:49:31,422] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:31,422] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:49:31,422] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:31,422] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:49:31,423] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:31,423] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:49:31,425] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:31,425] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:49:31,425] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:31,425] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:49:31,663] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:49:31,664] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:49:31,697] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:49:31,697] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:49:32,003] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:49:32,239] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:49:45,839] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-14 20:49:45,956] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:49:46,334] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 20:49:46,417] INFO [blog.templatetags.blog_tags.gravatar_url:396 blog_tags] Using default avatar for test@test.com +[2025-10-14 20:49:46,439] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:49:46,571] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-14 20:50:01,693] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_Java_1 +[2025-10-14 20:50:01,850] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:50:01,992] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarl +[2025-10-14 20:50:03,227] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_Python_1 +[2025-10-14 20:50:05,550] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:category_list_C_1 +[2025-10-14 20:50:06,429] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:archives +[2025-10-14 20:50:08,712] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 20:50:39,655] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:50:39,655] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:50:39,656] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:50:39,656] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:50:39,656] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:50:39,656] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:50:39,656] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:50:39,656] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:50:39,658] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:50:39,885] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:50:39,919] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:50:39,919] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:51:09,293] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:51:09,293] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:51:09,603] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:51:09,713] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:51:09,713] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:51:43,081] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:51:43,081] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:51:43,421] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:51:43,457] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:51:43,457] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:52:10,181] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:52:10,497] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:52:10,766] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:52:14,279] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:52:14,279] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:52:14,281] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:14,281] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:14,281] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:14,281] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:14,282] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:14,282] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:14,283] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:14,283] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:14,283] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:14,283] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:14,380] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:14,380] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:14,381] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:14,381] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:14,382] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:14,382] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:14,382] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:14,382] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:14,383] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:14,383] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:21,293] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:21,293] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:21,294] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:21,294] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:21,295] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:21,296] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 20:52:21,964] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:52:21,964] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:52:21,965] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:52:21,965] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:21,965] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:21,966] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:21,966] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:22,169] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:52:22,170] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:52:22,203] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:52:22,203] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:52:22,562] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:52:22,819] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:52:47,474] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:52:47,474] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:52:47,476] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:52:47,476] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:52:47,476] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:47,476] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:47,477] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:52:47,478] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:47,478] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:52:47,772] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:52:47,810] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:52:47,810] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:53:23,185] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:53:23,185] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:53:23,549] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:53:23,587] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:53:23,587] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:53:58,012] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:53:58,300] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:53:58,541] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:54:04,534] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:54:04,534] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:54:04,535] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:04,535] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:04,537] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:04,537] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:04,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:04,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:04,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:04,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:04,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:04,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:04,635] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:04,635] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:04,636] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:04,636] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:04,636] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:04,636] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:04,637] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:04,637] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:04,637] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:04,637] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:11,538] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:11,538] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:11,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:11,541] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:11,541] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:11,542] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 20:54:11,992] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:54:11,992] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 20:54:11,997] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:54:11,997] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 20:54:11,997] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:11,997] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:11,997] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:11,997] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:11,998] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:11,998] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:11,998] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:11,998] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:11,999] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:11,999] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:12,207] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:54:12,211] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:54:12,330] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:54:12,330] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:54:12,640] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:54:12,872] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 20:54:46,370] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:54:46,370] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 20:54:46,372] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:54:46,372] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 20:54:46,372] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:46,372] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 20:54:46,373] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:46,373] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 20:54:46,374] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:46,374] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 20:54:46,375] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:46,375] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 20:54:46,379] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:46,379] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 20:54:46,657] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 20:54:46,694] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:54:46,694] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 20:55:22,218] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 20:55:22,516] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 20:55:22,776] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:00:59,799] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 21:01:04,366] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:01:04,366] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:04,367] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:04,368] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:04,368] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:04,368] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:04,368] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:04,455] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:04,455] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:04,455] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:04,455] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:04,456] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:04,456] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:04,457] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:04,457] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:04,459] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:04,459] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:20,204] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:20,204] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:20,205] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:20,205] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:20,205] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:20,205] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:20,206] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:20,206] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:20,206] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:20,206] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:20,207] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 21:01:20,674] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:01:20,674] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:01:20,676] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:01:20,676] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:01:20,676] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:20,676] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:20,677] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:20,677] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:20,677] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:20,677] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:20,678] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:20,678] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:20,678] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:20,678] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:20,885] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 21:01:20,886] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:01:20,921] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:01:20,921] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:01:21,242] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:01:21,272] WARNING [django.security.csrf.log_response:241 log] Forbidden (CSRF token from POST incorrect.): /login/ +[2025-10-14 21:01:21,497] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:01:31,631] WARNING [django.security.csrf.log_response:241 log] Forbidden (CSRF token from POST incorrect.): /login/ +[2025-10-14 21:01:37,036] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 21:01:40,679] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:01:40,679] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:01:40,680] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:40,680] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:40,680] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:40,680] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:40,681] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:40,773] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:40,773] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:01:40,774] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:40,774] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:01:40,775] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:40,775] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:01:40,776] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:40,776] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:01:40,777] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:40,777] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:01:43,125] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 21:01:43,491] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:01:43,636] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:02:00,786] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:02:00,786] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:02:00,790] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:02:00,791] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:02:00,791] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:02:00,794] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 21:02:01,271] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:02:01,271] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:02:01,273] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:02:01,273] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:02:01,274] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:02:01,274] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:02:01,275] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:02:01,275] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:02:01,276] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:02:01,276] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:02:01,277] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:02:01,277] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:02:01,277] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:02:01,277] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:02:01,498] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 21:02:01,499] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:02:01,589] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:02:01,589] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:02:01,927] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:02:02,199] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:02:33,526] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-14 21:02:33,665] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:02:34,078] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:02:34,164] INFO [blog.templatetags.blog_tags.gravatar_url:396 blog_tags] Using default avatar for test@test.com +[2025-10-14 21:02:34,184] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:02:34,323] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-14 21:02:36,040] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:author_admin_1 +[2025-10-14 21:02:36,350] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:02:36,495] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarl +[2025-10-14 21:02:55,147] INFO [blog.models.comment_list:146 models] get article comments:1 +[2025-10-14 21:02:55,262] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:02:55,650] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:03:06,539] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:03:06,539] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:03:06,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:03:06,539] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:03:06,540] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:03:06,623] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:03:06,623] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:03:06,624] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:03:06,624] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:03:06,625] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:03:06,625] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:03:06,626] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:03:06,626] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:03:06,626] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:03:06,626] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:04:24,608] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test1.html"]} +[2025-10-14 21:04:24,608] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test1.html"]} +[2025-10-14 21:04:24,776] ERROR [djangoblog.blog_signals.send_email_signal_handler:49 blog_signals] 失败邮箱号: ['test1@test1.com'], (500, b'Error: bad syntax', 'None') +[2025-10-14 21:04:24,776] ERROR [djangoblog.blog_signals.send_email_signal_handler:49 blog_signals] 失败邮箱号: ['test1@test1.com'], (500, b'Error: bad syntax', 'None') +[2025-10-14 21:04:24,939] INFO [accounts.views.account_result:150 views] register +[2025-10-14 21:04:24,945] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:04:24,978] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:04:24,978] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:06:30,066] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:06:30,066] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:06:30,068] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:06:30,068] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:06:30,069] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:06:30,070] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 21:06:30,529] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 21:06:30,529] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-14 21:06:30,531] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:06:30,531] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:06:30,531] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:06:30,531] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:06:30,533] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:06:30,533] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:06:30,534] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:06:30,748] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 21:06:30,754] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:06:30,791] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:06:30,791] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:06:31,125] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:06:31,391] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:10:00,425] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test1.html"]} +[2025-10-14 21:10:00,425] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test1.html"]} +[2025-10-14 21:10:00,746] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:10:00,780] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:10:00,780] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:10:13,725] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 21:10:14,017] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:10:14,279] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:10:17,053] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:10:17,053] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-14 21:10:17,056] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:17,056] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:17,057] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:17,057] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:17,058] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:17,058] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:17,059] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:17,059] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:17,061] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:17,061] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:17,148] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:17,148] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:17,149] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:17,149] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:17,150] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:17,150] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:17,150] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:17,150] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:17,151] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:17,151] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:50,705] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:50,705] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:50,706] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:50,707] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:50,707] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:50,707] INFO [accounts.views.form_valid:123 views] next +[2025-10-14 21:10:50,891] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:10:50,891] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/test.html"]} +[2025-10-14 21:10:50,894] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:10:50,894] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] test@test.com +[2025-10-14 21:10:50,895] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:50,895] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-14 21:10:50,895] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:50,895] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:50,896] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-14 21:10:51,103] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-14 21:10:51,104] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-14 21:10:51,141] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:10:51,141] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-14 21:10:51,498] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:10:51,790] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-14 21:13:21,061] INFO [blog.views.get_queryset_from_cache:70 views] get view cache.key:index_1 +[2025-10-14 21:14:06,379] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-14 21:14:06,507] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:14:06,921] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-14 21:14:07,017] INFO [blog.templatetags.blog_tags.gravatar_url:396 blog_tags] Using default avatar for test@test.com +[2025-10-14 21:14:07,044] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-14 21:14:07,190] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-15 19:06:42,893] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:42,893] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:42,894] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:42,894] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:42,896] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:42,896] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:42,897] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:42,897] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:42,898] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:42,898] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:42,898] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:42,898] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:42,900] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:42,900] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:42,900] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:42,900] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:42,903] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:42,903] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:42,903] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:42,903] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:42,904] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:42,904] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:42,905] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:42,905] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:42,907] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:42,907] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:42,907] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:42,907] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:51,100] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:51,100] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:51,100] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:51,100] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:51,101] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:51,101] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:51,103] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:51,103] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:51,104] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:51,104] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:51,104] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:51,104] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:51,105] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:51,105] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:51,106] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:51,106] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:51,108] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:51,108] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:51,108] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:51,108] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:51,109] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:51,109] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:51,109] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:51,109] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:51,111] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:51,111] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:51,111] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:51,111] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:52,148] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:52,148] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 19:06:52,149] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:52,149] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 19:06:52,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:52,150] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 19:06:52,151] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:52,151] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 19:06:52,153] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:52,153] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 19:06:52,153] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:52,153] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 19:06:52,155] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:52,155] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 19:06:52,156] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:52,156] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 19:06:52,157] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:52,157] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 19:06:52,158] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:52,158] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 19:06:52,160] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:52,160] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 19:06:52,160] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:52,160] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 19:06:52,162] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:52,162] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 19:06:52,162] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:52,162] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 19:06:52,179] INFO [django.utils.autoreload.run_with_reloader:668 autoreload] Watching for file changes with StatReloader +[2025-10-15 19:07:03,571] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-15 19:07:03,600] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-15 19:07:04,004] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 19:07:04,004] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 19:07:07,984] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 19:07:10,022] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-15 19:07:25,823] INFO [blog.models.comment_list:151 models] set article comments:1 +[2025-10-15 19:07:26,719] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-15 19:07:30,321] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 0, 有效推荐数量: 0 +[2025-10-15 19:07:31,108] INFO [blog.templatetags.blog_tags.gravatar_url:396 blog_tags] Using default avatar for test@test.com +[2025-10-15 19:07:31,567] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 19:07:32,878] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp +[2025-10-15 20:19:20,626] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 20:19:20,626] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 20:19:20,626] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 20:19:20,626] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 20:19:20,629] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 20:19:20,629] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 20:19:20,630] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 20:19:20,630] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 20:19:20,632] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 20:19:20,632] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 20:19:20,633] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 20:19:20,633] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 20:19:20,635] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 20:19:20,635] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 20:19:20,635] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 20:19:20,635] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 20:19:20,637] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 20:19:20,637] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 20:19:20,639] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 20:19:20,639] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 20:19:20,640] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 20:19:20,640] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 20:19:20,641] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 20:19:20,641] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 20:19:20,643] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 20:19:20,643] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 20:19:20,644] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 20:19:20,644] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 20:19:21,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 20:19:21,775] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章结尾版权声明 initialized. +[2025-10-15 20:19:21,775] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 20:19:21,775] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_copyright - 文章结尾版权声明 +[2025-10-15 20:19:21,777] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 20:19:21,777] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 阅读时间预测 initialized. +[2025-10-15 20:19:21,778] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 20:19:21,778] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: reading_time - 阅读时间预测 +[2025-10-15 20:19:21,779] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 20:19:21,779] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 外部链接处理器 initialized. +[2025-10-15 20:19:21,780] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 20:19:21,780] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: external_links - 外部链接处理器 +[2025-10-15 20:19:21,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 20:19:21,781] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章浏览次数统计 initialized. +[2025-10-15 20:19:21,781] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 20:19:21,781] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: view_count - 文章浏览次数统计 +[2025-10-15 20:19:21,783] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 20:19:21,783] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] SEO 优化器 initialized. +[2025-10-15 20:19:21,783] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 20:19:21,783] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: seo_optimizer - SEO 优化器 +[2025-10-15 20:19:21,784] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 20:19:21,784] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 图片性能优化插件 initialized. +[2025-10-15 20:19:21,784] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 20:19:21,784] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: image_lazy_loading - 图片性能优化插件 +[2025-10-15 20:19:21,785] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 20:19:21,785] INFO [djangoblog.plugin_manage.base_plugin.init_plugin:48 base_plugin] 文章推荐 initialized. +[2025-10-15 20:19:21,786] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 20:19:21,786] INFO [djangoblog.plugin_manage.loader.load_plugins:29 loader] Successfully loaded plugin: article_recommendation - 文章推荐 +[2025-10-15 20:19:21,800] INFO [django.utils.autoreload.run_with_reloader:668 autoreload] Watching for file changes with StatReloader +[2025-10-15 20:20:24,225] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-15 20:20:24,279] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-15 20:20:24,359] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:20:24,359] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:20:26,465] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 20:20:27,034] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-15 20:20:41,073] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-15 20:20:41,073] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-15 20:20:41,074] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-15 20:20:41,074] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-15 20:20:41,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-15 20:20:41,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-15 20:20:41,076] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-15 20:20:41,076] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-15 20:20:41,076] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-15 20:20:41,076] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-15 20:20:41,076] INFO [accounts.views.form_valid:123 views] next +[2025-10-15 20:20:42,074] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-15 20:20:42,074] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/author/admin.html"]} +[2025-10-15 20:20:42,075] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-15 20:20:42,075] INFO [djangoblog.blog_signals.user_auth_callback:120 blog_signals] admin@admin123.com +[2025-10-15 20:20:42,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-15 20:20:42,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebari +[2025-10-15 20:20:42,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-15 20:20:42,075] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarl +[2025-10-15 20:20:42,077] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-15 20:20:42,077] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebarp +[2025-10-15 20:20:42,077] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-15 20:20:42,077] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebara +[2025-10-15 20:20:42,078] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-15 20:20:42,078] INFO [djangoblog.utils.delete_sidebar_cache:208 utils] delete sidebar key:sidebars +[2025-10-15 20:20:42,514] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-15 20:20:42,516] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-15 20:20:42,575] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:20:42,575] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:20:43,118] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 20:20:43,638] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-15 20:23:08,795] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/15/2.html"]} +[2025-10-15 20:23:08,795] INFO [djangoblog.spider_notify.baidu_notify:15 spider_notify] {"remain":10,"success":0,"not_same_site":["https://example.com/article/2025/10/15/2.html"]} +[2025-10-15 20:23:09,719] INFO [blog.context_processors.seo_processor:17 context_processors] set processor cache. +[2025-10-15 20:23:09,870] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:23:09,870] INFO [djangoblog.utils.get_blog_setting:171 utils] set cache get_blog_setting +[2025-10-15 20:23:16,292] INFO [blog.views.get_queryset_from_cache:75 views] set view cache.key:index_1 +[2025-10-15 20:23:17,191] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 20:23:17,887] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebari +[2025-10-15 20:23:23,601] INFO [blog.models.comment_list:151 models] set article comments:2 +[2025-10-15 20:23:23,776] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 1, 有效推荐数量: 1 +[2025-10-15 20:23:23,776] INFO [plugins.article_recommendation.plugin.get_recommendations:193 plugin] 推荐 1: ID=1, 标题='震惊!代码出现重大bug!!!', 长度=15 +[2025-10-15 20:23:24,628] INFO [plugins.article_recommendation.plugin.get_recommendations:191 plugin] 原始推荐数量: 1, 有效推荐数量: 1 +[2025-10-15 20:23:24,628] INFO [plugins.article_recommendation.plugin.get_recommendations:193 plugin] 推荐 1: ID=1, 标题='震惊!代码出现重大bug!!!', 长度=15 +[2025-10-15 20:23:24,753] INFO [blog.templatetags.blog_tags.load_sidebar:213 blog_tags] load sidebar +[2025-10-15 20:23:24,964] INFO [blog.templatetags.blog_tags.load_sidebar:257 blog_tags] set sidebar cache.key:sidebarp diff --git a/src/manage.py b/src/manage.py deleted file mode 100644 index 919ba74..0000000 --- a/src/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise - execute_from_command_line(sys.argv) diff --git a/src/oauth/__init__.py b/src/oauth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oauth/__pycache__/__init__.cpython-311.pyc b/src/oauth/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..ea150ab Binary files /dev/null and b/src/oauth/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/admin.cpython-311.pyc b/src/oauth/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..944b692 Binary files /dev/null and b/src/oauth/__pycache__/admin.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/apps.cpython-311.pyc b/src/oauth/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..7544da2 Binary files /dev/null and b/src/oauth/__pycache__/apps.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/forms.cpython-311.pyc b/src/oauth/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..2765c9a Binary files /dev/null and b/src/oauth/__pycache__/forms.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/models.cpython-311.pyc b/src/oauth/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..14be4c6 Binary files /dev/null and b/src/oauth/__pycache__/models.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/oauthmanager.cpython-311.pyc b/src/oauth/__pycache__/oauthmanager.cpython-311.pyc new file mode 100644 index 0000000..e122471 Binary files /dev/null and b/src/oauth/__pycache__/oauthmanager.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/urls.cpython-311.pyc b/src/oauth/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..93f5f7b Binary files /dev/null and b/src/oauth/__pycache__/urls.cpython-311.pyc differ diff --git a/src/oauth/__pycache__/views.cpython-311.pyc b/src/oauth/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..1503f3c Binary files /dev/null and b/src/oauth/__pycache__/views.cpython-311.pyc differ diff --git a/src/oauth/admin.py b/src/oauth/admin.py deleted file mode 100644 index 57eab5f..0000000 --- a/src/oauth/admin.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging - -from django.contrib import admin -# Register your models here. -from django.urls import reverse -from django.utils.html import format_html - -logger = logging.getLogger(__name__) - - -class OAuthUserAdmin(admin.ModelAdmin): - search_fields = ('nickname', 'email') - list_per_page = 20 - list_display = ( - 'id', - 'nickname', - 'link_to_usermodel', - 'show_user_image', - 'type', - 'email', - ) - list_display_links = ('id', 'nickname') - list_filter = ('author', 'type',) - readonly_fields = [] - - def get_readonly_fields(self, request, obj=None): - return list(self.readonly_fields) + \ - [field.name for field in obj._meta.fields] + \ - [field.name for field in obj._meta.many_to_many] - - def has_add_permission(self, request): - return False - - def link_to_usermodel(self, obj): - if obj.author: - info = (obj.author._meta.app_label, obj.author._meta.model_name) - link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) - return format_html( - u'%s' % - (link, obj.author.nickname if obj.author.nickname else obj.author.email)) - - def show_user_image(self, obj): - img = obj.picture - return format_html( - u'' % - (img)) - - link_to_usermodel.short_description = '用户' - show_user_image.short_description = '用户头像' - - -class OAuthConfigAdmin(admin.ModelAdmin): - list_display = ('type', 'appkey', 'appsecret', 'is_enable') - list_filter = ('type',) diff --git a/src/oauth/apps.py b/src/oauth/apps.py deleted file mode 100644 index 17fcea2..0000000 --- a/src/oauth/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OauthConfig(AppConfig): - name = 'oauth' diff --git a/src/oauth/forms.py b/src/oauth/forms.py deleted file mode 100644 index 0e4ede3..0000000 --- a/src/oauth/forms.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.contrib.auth.forms import forms -from django.forms import widgets - - -class RequireEmailForm(forms.Form): - email = forms.EmailField(label='电子邮箱', required=True) - oauthid = forms.IntegerField(widget=forms.HiddenInput, required=False) - - def __init__(self, *args, **kwargs): - super(RequireEmailForm, self).__init__(*args, **kwargs) - self.fields['email'].widget = widgets.EmailInput( - attrs={'placeholder': "email", "class": "form-control"}) diff --git a/src/oauth/migrations/0001_initial.py b/src/oauth/migrations/0001_initial.py deleted file mode 100644 index 3aa3e03..0000000 --- a/src/oauth/migrations/0001_initial.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-07 09:53 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='OAuthConfig', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('type', models.CharField(choices=[('weibo', '微博'), ('google', '谷歌'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='类型')), - ('appkey', models.CharField(max_length=200, verbose_name='AppKey')), - ('appsecret', models.CharField(max_length=200, verbose_name='AppSecret')), - ('callback_url', models.CharField(default='http://www.baidu.com', max_length=200, verbose_name='回调地址')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ], - options={ - 'verbose_name': 'oauth配置', - 'verbose_name_plural': 'oauth配置', - 'ordering': ['-created_time'], - }, - ), - migrations.CreateModel( - name='OAuthUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('openid', models.CharField(max_length=50)), - ('nickname', models.CharField(max_length=50, verbose_name='昵称')), - ('token', models.CharField(blank=True, max_length=150, null=True)), - ('picture', models.CharField(blank=True, max_length=350, null=True)), - ('type', models.CharField(max_length=50)), - ('email', models.CharField(blank=True, max_length=50, null=True)), - ('metadata', models.TextField(blank=True, null=True)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), - ], - options={ - 'verbose_name': 'oauth用户', - 'verbose_name_plural': 'oauth用户', - 'ordering': ['-created_time'], - }, - ), - ] diff --git a/src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py deleted file mode 100644 index d5cc70e..0000000 --- a/src/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py +++ /dev/null @@ -1,86 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('oauth', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='oauthconfig', - options={'ordering': ['-creation_time'], 'verbose_name': 'oauth配置', 'verbose_name_plural': 'oauth配置'}, - ), - migrations.AlterModelOptions( - name='oauthuser', - options={'ordering': ['-creation_time'], 'verbose_name': 'oauth user', 'verbose_name_plural': 'oauth user'}, - ), - migrations.RemoveField( - model_name='oauthconfig', - name='created_time', - ), - migrations.RemoveField( - model_name='oauthconfig', - name='last_mod_time', - ), - migrations.RemoveField( - model_name='oauthuser', - name='created_time', - ), - migrations.RemoveField( - model_name='oauthuser', - name='last_mod_time', - ), - migrations.AddField( - model_name='oauthconfig', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='oauthconfig', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AddField( - model_name='oauthuser', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - migrations.AddField( - model_name='oauthuser', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='callback_url', - field=models.CharField(default='', max_length=200, verbose_name='callback url'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='is_enable', - field=models.BooleanField(default=True, verbose_name='is enable'), - ), - migrations.AlterField( - model_name='oauthconfig', - name='type', - field=models.CharField(choices=[('weibo', 'weibo'), ('google', 'google'), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ')], default='a', max_length=10, verbose_name='type'), - ), - migrations.AlterField( - model_name='oauthuser', - name='author', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - migrations.AlterField( - model_name='oauthuser', - name='nickname', - field=models.CharField(max_length=50, verbose_name='nickname'), - ), - ] diff --git a/src/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/oauth/migrations/0003_alter_oauthuser_nickname.py deleted file mode 100644 index 6af08eb..0000000 --- a/src/oauth/migrations/0003_alter_oauthuser_nickname.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-26 02:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('oauth', '0002_alter_oauthconfig_options_alter_oauthuser_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='oauthuser', - name='nickname', - field=models.CharField(max_length=50, verbose_name='nick name'), - ), - ] diff --git a/src/oauth/migrations/__init__.py b/src/oauth/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oauth/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/oauth/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..13fca2f Binary files /dev/null and b/src/oauth/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-311.pyc b/src/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-311.pyc new file mode 100644 index 0000000..114bdd2 Binary files /dev/null and b/src/oauth/migrations/__pycache__/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.cpython-311.pyc differ diff --git a/src/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-311.pyc b/src/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-311.pyc new file mode 100644 index 0000000..d1b7c08 Binary files /dev/null and b/src/oauth/migrations/__pycache__/0003_alter_oauthuser_nickname.cpython-311.pyc differ diff --git a/src/oauth/migrations/__pycache__/__init__.cpython-311.pyc b/src/oauth/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7f5dc49 Binary files /dev/null and b/src/oauth/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/oauth/models.py b/src/oauth/models.py deleted file mode 100644 index be838ed..0000000 --- a/src/oauth/models.py +++ /dev/null @@ -1,67 +0,0 @@ -# Create your models here. -from django.conf import settings -from django.core.exceptions import ValidationError -from django.db import models -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ - - -class OAuthUser(models.Model): - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - blank=True, - null=True, - on_delete=models.CASCADE) - openid = models.CharField(max_length=50) - nickname = models.CharField(max_length=50, verbose_name=_('nick name')) - token = models.CharField(max_length=150, null=True, blank=True) - picture = models.CharField(max_length=350, blank=True, null=True) - type = models.CharField(blank=False, null=False, max_length=50) - email = models.CharField(max_length=50, null=True, blank=True) - metadata = models.TextField(null=True, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - - def __str__(self): - return self.nickname - - class Meta: - verbose_name = _('oauth user') - verbose_name_plural = verbose_name - ordering = ['-creation_time'] - - -class OAuthConfig(models.Model): - TYPE = ( - ('weibo', _('weibo')), - ('google', _('google')), - ('github', 'GitHub'), - ('facebook', 'FaceBook'), - ('qq', 'QQ'), - ) - type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a') - appkey = models.CharField(max_length=200, verbose_name='AppKey') - appsecret = models.CharField(max_length=200, verbose_name='AppSecret') - callback_url = models.CharField( - max_length=200, - verbose_name=_('callback url'), - blank=False, - default='') - is_enable = models.BooleanField( - _('is enable'), default=True, blank=False, null=False) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - - def clean(self): - if OAuthConfig.objects.filter( - type=self.type).exclude(id=self.id).count(): - raise ValidationError(_(self.type + _('already exists'))) - - def __str__(self): - return self.type - - class Meta: - verbose_name = 'oauth配置' - verbose_name_plural = verbose_name - ordering = ['-creation_time'] diff --git a/src/oauth/oauthmanager.py b/src/oauth/oauthmanager.py deleted file mode 100644 index 2e7ceef..0000000 --- a/src/oauth/oauthmanager.py +++ /dev/null @@ -1,504 +0,0 @@ -import json -import logging -import os -import urllib.parse -from abc import ABCMeta, abstractmethod - -import requests - -from djangoblog.utils import cache_decorator -from oauth.models import OAuthUser, OAuthConfig - -logger = logging.getLogger(__name__) - - -class OAuthAccessTokenException(Exception): - ''' - oauth授权失败异常 - ''' - - -class BaseOauthManager(metaclass=ABCMeta): - """获取用户授权""" - AUTH_URL = None - """获取token""" - TOKEN_URL = None - """获取用户信息""" - API_URL = None - '''icon图标名''' - ICON_NAME = None - - def __init__(self, access_token=None, openid=None): - self.access_token = access_token - self.openid = openid - - @property - def is_access_token_set(self): - return self.access_token is not None - - @property - def is_authorized(self): - return self.is_access_token_set and self.access_token is not None and self.openid is not None - - @abstractmethod - def get_authorization_url(self, nexturl='/'): - pass - - @abstractmethod - def get_access_token_by_code(self, code): - pass - - @abstractmethod - def get_oauth_userinfo(self): - pass - - @abstractmethod - def get_picture(self, metadata): - pass - - def do_get(self, url, params, headers=None): - rsp = requests.get(url=url, params=params, headers=headers) - logger.info(rsp.text) - return rsp.text - - def do_post(self, url, params, headers=None): - rsp = requests.post(url, params, headers=headers) - logger.info(rsp.text) - return rsp.text - - def get_config(self): - value = OAuthConfig.objects.filter(type=self.ICON_NAME) - return value[0] if value else None - - -class WBOauthManager(BaseOauthManager): - AUTH_URL = 'https://api.weibo.com/oauth2/authorize' - TOKEN_URL = 'https://api.weibo.com/oauth2/access_token' - API_URL = 'https://api.weibo.com/2/users/show.json' - ICON_NAME = 'weibo' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - WBOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, nexturl='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url + '&next_url=' + nexturl - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - if 'access_token' in obj: - self.access_token = str(obj['access_token']) - self.openid = str(obj['uid']) - return self.get_oauth_userinfo() - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - if not self.is_authorized: - return None - params = { - 'uid': self.openid, - 'access_token': self.access_token - } - rsp = self.do_get(self.API_URL, params) - try: - datas = json.loads(rsp) - user = OAuthUser() - user.metadata = rsp - user.picture = datas['avatar_large'] - user.nickname = datas['screen_name'] - user.openid = datas['id'] - user.type = 'weibo' - user.token = self.access_token - if 'email' in datas and datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('weibo oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['avatar_large'] - - -class ProxyManagerMixin: - def __init__(self, *args, **kwargs): - if os.environ.get("HTTP_PROXY"): - self.proxies = { - "http": os.environ.get("HTTP_PROXY"), - "https": os.environ.get("HTTP_PROXY") - } - else: - self.proxies = None - - def do_get(self, url, params, headers=None): - rsp = requests.get(url=url, params=params, headers=headers, proxies=self.proxies) - logger.info(rsp.text) - return rsp.text - - def do_post(self, url, params, headers=None): - rsp = requests.post(url, params, headers=headers, proxies=self.proxies) - logger.info(rsp.text) - return rsp.text - - -class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth' - TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token' - API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo' - ICON_NAME = 'google' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - GoogleOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, nexturl='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url, - 'scope': 'openid email', - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - - if 'access_token' in obj: - self.access_token = str(obj['access_token']) - self.openid = str(obj['id_token']) - logger.info(self.ICON_NAME + ' oauth ' + rsp) - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - if not self.is_authorized: - return None - params = { - 'access_token': self.access_token - } - rsp = self.do_get(self.API_URL, params) - try: - - datas = json.loads(rsp) - user = OAuthUser() - user.metadata = rsp - user.picture = datas['picture'] - user.nickname = datas['name'] - user.openid = datas['sub'] - user.token = self.access_token - user.type = 'google' - if datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('google oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['picture'] - - -class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://github.com/login/oauth/authorize' - TOKEN_URL = 'https://github.com/login/oauth/access_token' - API_URL = 'https://api.github.com/user' - ICON_NAME = 'github' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - GitHubOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': f'{self.callback_url}&next_url={next_url}', - 'scope': 'user' - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - from urllib import parse - r = parse.parse_qs(rsp) - if 'access_token' in r: - self.access_token = (r['access_token'][0]) - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - - rsp = self.do_get(self.API_URL, params={}, headers={ - "Authorization": "token " + self.access_token - }) - try: - datas = json.loads(rsp) - user = OAuthUser() - user.picture = datas['avatar_url'] - user.nickname = datas['name'] - user.openid = datas['id'] - user.type = 'github' - user.token = self.access_token - user.metadata = rsp - if 'email' in datas and datas['email']: - user.email = datas['email'] - return user - except Exception as e: - logger.error(e) - logger.error('github oauth error.rsp:' + rsp) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return datas['avatar_url'] - - -class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager): - AUTH_URL = 'https://www.facebook.com/v16.0/dialog/oauth' - TOKEN_URL = 'https://graph.facebook.com/v16.0/oauth/access_token' - API_URL = 'https://graph.facebook.com/me' - ICON_NAME = 'facebook' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - FaceBookOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'client_id': self.client_id, - 'response_type': 'code', - 'redirect_uri': self.callback_url, - 'scope': 'email,public_profile' - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'client_id': self.client_id, - 'client_secret': self.client_secret, - # 'grant_type': 'authorization_code', - 'code': code, - - 'redirect_uri': self.callback_url - } - rsp = self.do_post(self.TOKEN_URL, params) - - obj = json.loads(rsp) - if 'access_token' in obj: - token = str(obj['access_token']) - self.access_token = token - return self.access_token - else: - raise OAuthAccessTokenException(rsp) - - def get_oauth_userinfo(self): - params = { - 'access_token': self.access_token, - 'fields': 'id,name,picture,email' - } - try: - rsp = self.do_get(self.API_URL, params) - datas = json.loads(rsp) - user = OAuthUser() - user.nickname = datas['name'] - user.openid = datas['id'] - user.type = 'facebook' - user.token = self.access_token - user.metadata = rsp - if 'email' in datas and datas['email']: - user.email = datas['email'] - if 'picture' in datas and datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']: - user.picture = str(datas['picture']['data']['url']) - return user - except Exception as e: - logger.error(e) - return None - - def get_picture(self, metadata): - datas = json.loads(metadata) - return str(datas['picture']['data']['url']) - - -class QQOauthManager(BaseOauthManager): - AUTH_URL = 'https://graph.qq.com/oauth2.0/authorize' - TOKEN_URL = 'https://graph.qq.com/oauth2.0/token' - API_URL = 'https://graph.qq.com/user/get_user_info' - OPEN_ID_URL = 'https://graph.qq.com/oauth2.0/me' - ICON_NAME = 'qq' - - def __init__(self, access_token=None, openid=None): - config = self.get_config() - self.client_id = config.appkey if config else '' - self.client_secret = config.appsecret if config else '' - self.callback_url = config.callback_url if config else '' - super( - QQOauthManager, - self).__init__( - access_token=access_token, - openid=openid) - - def get_authorization_url(self, next_url='/'): - params = { - 'response_type': 'code', - 'client_id': self.client_id, - 'redirect_uri': self.callback_url + '&next_url=' + next_url, - } - url = self.AUTH_URL + "?" + urllib.parse.urlencode(params) - return url - - def get_access_token_by_code(self, code): - params = { - 'grant_type': 'authorization_code', - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'code': code, - 'redirect_uri': self.callback_url - } - rsp = self.do_get(self.TOKEN_URL, params) - if rsp: - d = urllib.parse.parse_qs(rsp) - if 'access_token' in d: - token = d['access_token'] - self.access_token = token[0] - return token - else: - raise OAuthAccessTokenException(rsp) - - def get_open_id(self): - if self.is_access_token_set: - params = { - 'access_token': self.access_token - } - rsp = self.do_get(self.OPEN_ID_URL, params) - if rsp: - rsp = rsp.replace( - 'callback(', '').replace( - ')', '').replace( - ';', '') - obj = json.loads(rsp) - openid = str(obj['openid']) - self.openid = openid - return openid - - def get_oauth_userinfo(self): - openid = self.get_open_id() - if openid: - params = { - 'access_token': self.access_token, - 'oauth_consumer_key': self.client_id, - 'openid': self.openid - } - rsp = self.do_get(self.API_URL, params) - logger.info(rsp) - obj = json.loads(rsp) - user = OAuthUser() - user.nickname = obj['nickname'] - user.openid = openid - user.type = 'qq' - user.token = self.access_token - user.metadata = rsp - if 'email' in obj: - user.email = obj['email'] - if 'figureurl' in obj: - user.picture = str(obj['figureurl']) - return user - - def get_picture(self, metadata): - datas = json.loads(metadata) - return str(datas['figureurl']) - - -@cache_decorator(expiration=100 * 60) -def get_oauth_apps(): - configs = OAuthConfig.objects.filter(is_enable=True).all() - if not configs: - return [] - configtypes = [x.type for x in configs] - applications = BaseOauthManager.__subclasses__() - apps = [x() for x in applications if x().ICON_NAME.lower() in configtypes] - return apps - - -def get_manager_by_type(type): - applications = get_oauth_apps() - if applications: - finds = list( - filter( - lambda x: x.ICON_NAME.lower() == type.lower(), - applications)) - if finds: - return finds[0] - return None diff --git a/src/oauth/templatetags/__init__.py b/src/oauth/templatetags/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/src/oauth/templatetags/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/oauth/templatetags/__pycache__/__init__.cpython-311.pyc b/src/oauth/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..6d11ed2 Binary files /dev/null and b/src/oauth/templatetags/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/oauth/templatetags/__pycache__/oauth_tags.cpython-311.pyc b/src/oauth/templatetags/__pycache__/oauth_tags.cpython-311.pyc new file mode 100644 index 0000000..7f2bb07 Binary files /dev/null and b/src/oauth/templatetags/__pycache__/oauth_tags.cpython-311.pyc differ diff --git a/src/oauth/templatetags/oauth_tags.py b/src/oauth/templatetags/oauth_tags.py deleted file mode 100644 index 7b687d5..0000000 --- a/src/oauth/templatetags/oauth_tags.py +++ /dev/null @@ -1,22 +0,0 @@ -from django import template -from django.urls import reverse - -from oauth.oauthmanager import get_oauth_apps - -register = template.Library() - - -@register.inclusion_tag('oauth/oauth_applications.html') -def load_oauth_applications(request): - applications = get_oauth_apps() - if applications: - baseurl = reverse('oauth:oauthlogin') - path = request.get_full_path() - - apps = list(map(lambda x: (x.ICON_NAME, '{baseurl}?type={type}&next_url={next}'.format( - baseurl=baseurl, type=x.ICON_NAME, next=path)), applications)) - else: - apps = [] - return { - 'apps': apps - } diff --git a/src/oauth/tests.py b/src/oauth/tests.py deleted file mode 100644 index bb23b9b..0000000 --- a/src/oauth/tests.py +++ /dev/null @@ -1,249 +0,0 @@ -import json -from unittest.mock import patch - -from django.conf import settings -from django.contrib import auth -from django.test import Client, RequestFactory, TestCase -from django.urls import reverse - -from djangoblog.utils import get_sha256 -from oauth.models import OAuthConfig -from oauth.oauthmanager import BaseOauthManager - - -# Create your tests here. -class OAuthConfigTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_oauth_login_test(self): - c = OAuthConfig() - c.type = 'weibo' - c.appkey = 'appkey' - c.appsecret = 'appsecret' - c.save() - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - -class OauthLoginTest(TestCase): - def setUp(self) -> None: - self.client = Client() - self.factory = RequestFactory() - self.apps = self.init_apps() - - def init_apps(self): - applications = [p() for p in BaseOauthManager.__subclasses__()] - for application in applications: - c = OAuthConfig() - c.type = application.ICON_NAME.lower() - c.appkey = 'appkey' - c.appsecret = 'appsecret' - c.save() - return applications - - def get_app_by_type(self, type): - for app in self.apps: - if app.ICON_NAME.lower() == type: - return app - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_login(self, mock_do_get, mock_do_post): - weibo_app = self.get_app_by_type('weibo') - assert weibo_app - url = weibo_app.get_authorization_url() - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_do_get.return_value = json.dumps({ - "avatar_large": "avatar_large", - "screen_name": "screen_name", - "id": "id", - "email": "email", - }) - userinfo = weibo_app.get_access_token_by_code('code') - self.assertEqual(userinfo.token, 'access_token') - self.assertEqual(userinfo.openid, 'id') - - @patch("oauth.oauthmanager.GoogleOauthManager.do_post") - @patch("oauth.oauthmanager.GoogleOauthManager.do_get") - def test_google_login(self, mock_do_get, mock_do_post): - google_app = self.get_app_by_type('google') - assert google_app - url = google_app.get_authorization_url() - mock_do_post.return_value = json.dumps({ - "access_token": "access_token", - "id_token": "id_token", - }) - mock_do_get.return_value = json.dumps({ - "picture": "picture", - "name": "name", - "sub": "sub", - "email": "email", - }) - token = google_app.get_access_token_by_code('code') - userinfo = google_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - self.assertEqual(userinfo.openid, 'sub') - - @patch("oauth.oauthmanager.GitHubOauthManager.do_post") - @patch("oauth.oauthmanager.GitHubOauthManager.do_get") - def test_github_login(self, mock_do_get, mock_do_post): - github_app = self.get_app_by_type('github') - assert github_app - url = github_app.get_authorization_url() - self.assertTrue("github.com" in url) - self.assertTrue("client_id" in url) - mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer" - mock_do_get.return_value = json.dumps({ - "avatar_url": "avatar_url", - "name": "name", - "id": "id", - "email": "email", - }) - token = github_app.get_access_token_by_code('code') - userinfo = github_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a') - self.assertEqual(userinfo.openid, 'id') - - @patch("oauth.oauthmanager.FaceBookOauthManager.do_post") - @patch("oauth.oauthmanager.FaceBookOauthManager.do_get") - def test_facebook_login(self, mock_do_get, mock_do_post): - facebook_app = self.get_app_by_type('facebook') - assert facebook_app - url = facebook_app.get_authorization_url() - self.assertTrue("facebook.com" in url) - mock_do_post.return_value = json.dumps({ - "access_token": "access_token", - }) - mock_do_get.return_value = json.dumps({ - "name": "name", - "id": "id", - "email": "email", - "picture": { - "data": { - "url": "url" - } - } - }) - token = facebook_app.get_access_token_by_code('code') - userinfo = facebook_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - - @patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[ - 'access_token=access_token&expires_in=3600', - 'callback({"client_id":"appid","openid":"openid"} );', - json.dumps({ - "nickname": "nickname", - "email": "email", - "figureurl": "figureurl", - "openid": "openid", - }) - ]) - def test_qq_login(self, mock_do_get): - qq_app = self.get_app_by_type('qq') - assert qq_app - url = qq_app.get_authorization_url() - self.assertTrue("qq.com" in url) - token = qq_app.get_access_token_by_code('code') - userinfo = qq_app.get_oauth_userinfo() - self.assertEqual(userinfo.token, 'access_token') - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post): - - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_user_info = { - "avatar_large": "avatar_large", - "screen_name": "screen_name1", - "id": "id", - "email": "email", - } - mock_do_get.return_value = json.dumps(mock_user_info) - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - user = auth.get_user(self.client) - assert user.is_authenticated - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, mock_user_info['email']) - self.client.logout() - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, '/') - - user = auth.get_user(self.client) - assert user.is_authenticated - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, mock_user_info['email']) - - @patch("oauth.oauthmanager.WBOauthManager.do_post") - @patch("oauth.oauthmanager.WBOauthManager.do_get") - def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post): - - mock_do_post.return_value = json.dumps({"access_token": "access_token", - "uid": "uid" - }) - mock_user_info = { - "avatar_large": "avatar_large", - "screen_name": "screen_name1", - "id": "id", - } - mock_do_get.return_value = json.dumps(mock_user_info) - - response = self.client.get('/oauth/oauthlogin?type=weibo') - self.assertEqual(response.status_code, 302) - self.assertTrue("api.weibo.com" in response.url) - - response = self.client.get('/oauth/authorize?type=weibo&code=code') - - self.assertEqual(response.status_code, 302) - - oauth_user_id = int(response.url.split('/')[-1].split('.')[0]) - self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html') - - response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id}) - - self.assertEqual(response.status_code, 302) - sign = get_sha256(settings.SECRET_KEY + - str(oauth_user_id) + settings.SECRET_KEY) - - url = reverse('oauth:bindsuccess', kwargs={ - 'oauthid': oauth_user_id, - }) - self.assertEqual(response.url, f'{url}?type=email') - - path = reverse('oauth:email_confirm', kwargs={ - 'id': oauth_user_id, - 'sign': sign - }) - response = self.client.get(path) - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success') - user = auth.get_user(self.client) - from oauth.models import OAuthUser - oauth_user = OAuthUser.objects.get(author=user) - self.assertTrue(user.is_authenticated) - self.assertEqual(user.username, mock_user_info['screen_name']) - self.assertEqual(user.email, 'test@gmail.com') - self.assertEqual(oauth_user.pk, oauth_user_id) diff --git a/src/oauth/urls.py b/src/oauth/urls.py deleted file mode 100644 index c4a12a0..0000000 --- a/src/oauth/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "oauth" -urlpatterns = [ - path( - r'oauth/authorize', - views.authorize), - path( - r'oauth/requireemail/.html', - views.RequireEmailView.as_view(), - name='require_email'), - path( - r'oauth/emailconfirm//.html', - views.emailconfirm, - name='email_confirm'), - path( - r'oauth/bindsuccess/.html', - views.bindsuccess, - name='bindsuccess'), - path( - r'oauth/oauthlogin', - views.oauthlogin, - name='oauthlogin')] diff --git a/src/oauth/views.py b/src/oauth/views.py deleted file mode 100644 index 12e3a6e..0000000 --- a/src/oauth/views.py +++ /dev/null @@ -1,253 +0,0 @@ -import logging -# Create your views here. -from urllib.parse import urlparse - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth import login -from django.core.exceptions import ObjectDoesNotExist -from django.db import transaction -from django.http import HttpResponseForbidden -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -from django.urls import reverse -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ -from django.views.generic import FormView - -from djangoblog.blog_signals import oauth_user_login_signal -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email, get_sha256 -from oauth.forms import RequireEmailForm -from .models import OAuthUser -from .oauthmanager import get_manager_by_type, OAuthAccessTokenException - -logger = logging.getLogger(__name__) - - -def get_redirecturl(request): - nexturl = request.GET.get('next_url', None) - if not nexturl or nexturl == '/login/' or nexturl == '/login': - nexturl = '/' - return nexturl - p = urlparse(nexturl) - if p.netloc: - site = get_current_site().domain - if not p.netloc.replace('www.', '') == site.replace('www.', ''): - logger.info('非法url:' + nexturl) - return "/" - return nexturl - - -def oauthlogin(request): - type = request.GET.get('type', None) - if not type: - return HttpResponseRedirect('/') - manager = get_manager_by_type(type) - if not manager: - return HttpResponseRedirect('/') - nexturl = get_redirecturl(request) - authorizeurl = manager.get_authorization_url(nexturl) - return HttpResponseRedirect(authorizeurl) - - -def authorize(request): - type = request.GET.get('type', None) - if not type: - return HttpResponseRedirect('/') - manager = get_manager_by_type(type) - if not manager: - return HttpResponseRedirect('/') - code = request.GET.get('code', None) - try: - rsp = manager.get_access_token_by_code(code) - except OAuthAccessTokenException as e: - logger.warning("OAuthAccessTokenException:" + str(e)) - return HttpResponseRedirect('/') - except Exception as e: - logger.error(e) - rsp = None - nexturl = get_redirecturl(request) - if not rsp: - return HttpResponseRedirect(manager.get_authorization_url(nexturl)) - user = manager.get_oauth_userinfo() - if user: - if not user.nickname or not user.nickname.strip(): - user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') - try: - temp = OAuthUser.objects.get(type=type, openid=user.openid) - temp.picture = user.picture - temp.metadata = user.metadata - temp.nickname = user.nickname - user = temp - except ObjectDoesNotExist: - pass - # facebook的token过长 - if type == 'facebook': - user.token = '' - if user.email: - with transaction.atomic(): - author = None - try: - author = get_user_model().objects.get(id=user.author_id) - except ObjectDoesNotExist: - pass - if not author: - result = get_user_model().objects.get_or_create(email=user.email) - author = result[0] - if result[1]: - try: - get_user_model().objects.get(username=user.nickname) - except ObjectDoesNotExist: - author.username = user.nickname - else: - author.username = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') - author.source = 'authorize' - author.save() - - user.author = author - user.save() - - oauth_user_login_signal.send( - sender=authorize.__class__, id=user.id) - login(request, author) - return HttpResponseRedirect(nexturl) - else: - user.save() - url = reverse('oauth:require_email', kwargs={ - 'oauthid': user.id - }) - - return HttpResponseRedirect(url) - else: - return HttpResponseRedirect(nexturl) - - -def emailconfirm(request, id, sign): - if not sign: - return HttpResponseForbidden() - if not get_sha256(settings.SECRET_KEY + - str(id) + - settings.SECRET_KEY).upper() == sign.upper(): - return HttpResponseForbidden() - oauthuser = get_object_or_404(OAuthUser, pk=id) - with transaction.atomic(): - if oauthuser.author: - author = get_user_model().objects.get(pk=oauthuser.author_id) - else: - result = get_user_model().objects.get_or_create(email=oauthuser.email) - author = result[0] - if result[1]: - author.source = 'emailconfirm' - author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip( - ) else "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S') - author.save() - oauthuser.author = author - oauthuser.save() - oauth_user_login_signal.send( - sender=emailconfirm.__class__, - id=oauthuser.id) - login(request, author) - - site = 'http://' + get_current_site().domain - content = _(''' -

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

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

Please click the link below to bind your email

- - %(url)s - - Thank you again! -
- If the link above cannot be opened, please copy this link to your browser. -
- %(url)s - """) % {'url': url} - send_email(emailto=[email, ], title=_('Bind your email'), content=content) - url = reverse('oauth:bindsuccess', kwargs={ - 'oauthid': oauthid - }) - url = url + '?type=email' - return HttpResponseRedirect(url) - - -def bindsuccess(request, oauthid): - type = request.GET.get('type', None) - oauthuser = get_object_or_404(OAuthUser, pk=oauthid) - if type == 'email': - title = _('Bind your email') - content = _( - 'Congratulations, the binding is just one step away. ' - 'Please log in to your email to check the email to complete the binding. Thank you.') - else: - title = _('Binding successful') - content = _( - "Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s" - " to directly log in to this website without a password. You are welcome to continue to follow this site." % { - 'oauthuser_type': oauthuser.type}) - return render(request, 'oauth/bindsuccess.html', { - 'title': title, - 'content': content - }) diff --git a/src/owntracks/__init__.py b/src/owntracks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/owntracks/__pycache__/__init__.cpython-311.pyc b/src/owntracks/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..fe145ca Binary files /dev/null and b/src/owntracks/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/owntracks/__pycache__/admin.cpython-311.pyc b/src/owntracks/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..a166500 Binary files /dev/null and b/src/owntracks/__pycache__/admin.cpython-311.pyc differ diff --git a/src/owntracks/__pycache__/apps.cpython-311.pyc b/src/owntracks/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..d489fa2 Binary files /dev/null and b/src/owntracks/__pycache__/apps.cpython-311.pyc differ diff --git a/src/owntracks/__pycache__/models.cpython-311.pyc b/src/owntracks/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..eb4a923 Binary files /dev/null and b/src/owntracks/__pycache__/models.cpython-311.pyc differ diff --git a/src/owntracks/__pycache__/urls.cpython-311.pyc b/src/owntracks/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..3e667fe Binary files /dev/null and b/src/owntracks/__pycache__/urls.cpython-311.pyc differ diff --git a/src/owntracks/__pycache__/views.cpython-311.pyc b/src/owntracks/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..c1aeb6b Binary files /dev/null and b/src/owntracks/__pycache__/views.cpython-311.pyc differ diff --git a/src/owntracks/admin.py b/src/owntracks/admin.py deleted file mode 100644 index 655b535..0000000 --- a/src/owntracks/admin.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin - -# Register your models here. - - -class OwnTrackLogsAdmin(admin.ModelAdmin): - pass diff --git a/src/owntracks/apps.py b/src/owntracks/apps.py deleted file mode 100644 index 1bc5f12..0000000 --- a/src/owntracks/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class OwntracksConfig(AppConfig): - name = 'owntracks' diff --git a/src/owntracks/migrations/0001_initial.py b/src/owntracks/migrations/0001_initial.py deleted file mode 100644 index 9eee55c..0000000 --- a/src/owntracks/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='OwnTrackLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tid', models.CharField(max_length=100, verbose_name='用户')), - ('lat', models.FloatField(verbose_name='纬度')), - ('lon', models.FloatField(verbose_name='经度')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ], - options={ - 'verbose_name': 'OwnTrackLogs', - 'verbose_name_plural': 'OwnTrackLogs', - 'ordering': ['created_time'], - 'get_latest_by': 'created_time', - }, - ), - ] diff --git a/src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py deleted file mode 100644 index b4f8dec..0000000 --- a/src/owntracks/migrations/0002_alter_owntracklog_options_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('owntracks', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='owntracklog', - options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'}, - ), - migrations.RenameField( - model_name='owntracklog', - old_name='created_time', - new_name='creation_time', - ), - ] diff --git a/src/owntracks/migrations/__init__.py b/src/owntracks/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/owntracks/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/owntracks/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..57892bb Binary files /dev/null and b/src/owntracks/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-311.pyc b/src/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-311.pyc new file mode 100644 index 0000000..1fed633 Binary files /dev/null and b/src/owntracks/migrations/__pycache__/0002_alter_owntracklog_options_and_more.cpython-311.pyc differ diff --git a/src/owntracks/migrations/__pycache__/__init__.cpython-311.pyc b/src/owntracks/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a01f9cb Binary files /dev/null and b/src/owntracks/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/owntracks/models.py b/src/owntracks/models.py deleted file mode 100644 index 760942c..0000000 --- a/src/owntracks/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import models -from django.utils.timezone import now - - -# Create your models here. - -class OwnTrackLog(models.Model): - tid = models.CharField(max_length=100, null=False, verbose_name='用户') - lat = models.FloatField(verbose_name='纬度') - lon = models.FloatField(verbose_name='经度') - creation_time = models.DateTimeField('创建时间', default=now) - - def __str__(self): - return self.tid - - class Meta: - ordering = ['creation_time'] - verbose_name = "OwnTrackLogs" - verbose_name_plural = verbose_name - get_latest_by = 'creation_time' diff --git a/src/owntracks/tests.py b/src/owntracks/tests.py deleted file mode 100644 index 3b4b9d8..0000000 --- a/src/owntracks/tests.py +++ /dev/null @@ -1,64 +0,0 @@ -import json - -from django.test import Client, RequestFactory, TestCase - -from accounts.models import BlogUser -from .models import OwnTrackLog - - -# Create your tests here. - -class OwnTrackLogTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_own_track_log(self): - o = { - 'tid': 12, - 'lat': 123.123, - 'lon': 134.341 - } - - self.client.post( - '/owntracks/logtracks', - json.dumps(o), - content_type='application/json') - length = len(OwnTrackLog.objects.all()) - self.assertEqual(length, 1) - - o = { - 'tid': 12, - 'lat': 123.123 - } - - self.client.post( - '/owntracks/logtracks', - json.dumps(o), - content_type='application/json') - length = len(OwnTrackLog.objects.all()) - self.assertEqual(length, 1) - - rsp = self.client.get('/owntracks/show_maps') - self.assertEqual(rsp.status_code, 302) - - user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - self.client.login(username='liangliangyy1', password='liangliangyy1') - s = OwnTrackLog() - s.tid = 12 - s.lon = 123.234 - s.lat = 34.234 - s.save() - - rsp = self.client.get('/owntracks/show_dates') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/show_maps') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/get_datas') - self.assertEqual(rsp.status_code, 200) - rsp = self.client.get('/owntracks/get_datas?date=2018-02-26') - self.assertEqual(rsp.status_code, 200) diff --git a/src/owntracks/urls.py b/src/owntracks/urls.py deleted file mode 100644 index c19ada8..0000000 --- a/src/owntracks/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "owntracks" - -urlpatterns = [ - path('owntracks/logtracks', views.manage_owntrack_log, name='logtracks'), - path('owntracks/show_maps', views.show_maps, name='show_maps'), - path('owntracks/get_datas', views.get_datas, name='get_datas'), - path('owntracks/show_dates', views.show_log_dates, name='show_dates') -] diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/plugins/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/plugins/__pycache__/__init__.cpython-311.pyc b/src/plugins/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..9c02a78 Binary files /dev/null and b/src/plugins/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/article_copyright/__init__.py b/src/plugins/article_copyright/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/plugins/article_copyright/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/plugins/article_copyright/__pycache__/__init__.cpython-311.pyc b/src/plugins/article_copyright/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..84122cb Binary files /dev/null and b/src/plugins/article_copyright/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/article_copyright/__pycache__/plugin.cpython-311.pyc b/src/plugins/article_copyright/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..61a1311 Binary files /dev/null and b/src/plugins/article_copyright/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/article_copyright/plugin.py b/src/plugins/article_copyright/plugin.py index 317fed2..5dba3b3 100644 --- a/src/plugins/article_copyright/plugin.py +++ b/src/plugins/article_copyright/plugin.py @@ -22,6 +22,11 @@ class ArticleCopyrightPlugin(BasePlugin): article = kwargs.get('article') if not article: return content + + # 如果是摘要模式(首页),不添加版权声明 + is_summary = kwargs.get('is_summary', False) + if is_summary: + return content copyright_info = f"\n

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

" return content + copyright_info diff --git a/src/plugins/article_recommendation/__init__.py b/src/plugins/article_recommendation/__init__.py new file mode 100644 index 0000000..951f2ff --- /dev/null +++ b/src/plugins/article_recommendation/__init__.py @@ -0,0 +1 @@ +# 文章推荐插件 diff --git a/src/plugins/article_recommendation/__pycache__/__init__.cpython-311.pyc b/src/plugins/article_recommendation/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..f9a74fe Binary files /dev/null and b/src/plugins/article_recommendation/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/article_recommendation/__pycache__/plugin.cpython-311.pyc b/src/plugins/article_recommendation/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..c6a8c65 Binary files /dev/null and b/src/plugins/article_recommendation/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/article_recommendation/plugin.py b/src/plugins/article_recommendation/plugin.py new file mode 100644 index 0000000..6656a07 --- /dev/null +++ b/src/plugins/article_recommendation/plugin.py @@ -0,0 +1,205 @@ +import logging +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD +from blog.models import Article + +logger = logging.getLogger(__name__) + + +class ArticleRecommendationPlugin(BasePlugin): + PLUGIN_NAME = '文章推荐' + PLUGIN_DESCRIPTION = '智能文章推荐系统,支持多位置展示' + PLUGIN_VERSION = '1.0.0' + PLUGIN_AUTHOR = 'liangliangyy' + + # 支持的位置 + SUPPORTED_POSITIONS = ['article_bottom'] + + # 各位置优先级 + POSITION_PRIORITIES = { + 'article_bottom': 80, # 文章底部优先级 + } + + # 插件配置 + CONFIG = { + 'article_bottom_count': 8, # 文章底部推荐数量 + 'sidebar_count': 5, # 侧边栏推荐数量 + 'enable_category_fallback': True, # 启用分类回退 + 'enable_popular_fallback': True, # 启用热门文章回退 + } + + def register_hooks(self): + """注册钩子""" + hooks.register(ARTICLE_DETAIL_LOAD, self.on_article_detail_load) + + def on_article_detail_load(self, article, context, request, *args, **kwargs): + """文章详情页加载时的处理""" + # 可以在这里预加载推荐数据到context中 + recommendations = self.get_recommendations(article) + context['article_recommendations'] = recommendations + + def should_display(self, position, context, **kwargs): + """条件显示逻辑""" + # 只在文章详情页底部显示 + if position == 'article_bottom': + article = kwargs.get('article') or context.get('article') + # 检查是否有文章对象,以及是否不是索引页面 + is_index = context.get('isindex', False) if hasattr(context, 'get') else False + return article is not None and not is_index + + return False + + def render_article_bottom_widget(self, context, **kwargs): + """渲染文章底部推荐""" + article = kwargs.get('article') or context.get('article') + if not article: + return None + + # 使用配置的数量,也可以通过kwargs覆盖 + count = kwargs.get('count', self.CONFIG['article_bottom_count']) + recommendations = self.get_recommendations(article, count=count) + if not recommendations: + return None + + # 将RequestContext转换为普通字典 + context_dict = {} + if hasattr(context, 'flatten'): + context_dict = context.flatten() + elif hasattr(context, 'dicts'): + # 合并所有上下文字典 + for d in context.dicts: + context_dict.update(d) + + template_context = { + 'recommendations': recommendations, + 'article': article, + 'title': '相关推荐', + **context_dict + } + + return self.render_template('bottom_widget.html', template_context) + + def render_sidebar_widget(self, context, **kwargs): + """渲染侧边栏推荐""" + article = context.get('article') + + # 使用配置的数量,也可以通过kwargs覆盖 + count = kwargs.get('count', self.CONFIG['sidebar_count']) + + if article: + # 文章页面,显示相关文章 + recommendations = self.get_recommendations(article, count=count) + title = '相关文章' + else: + # 其他页面,显示热门文章 + recommendations = self.get_popular_articles(count=count) + title = '热门推荐' + + if not recommendations: + return None + + # 将RequestContext转换为普通字典 + context_dict = {} + if hasattr(context, 'flatten'): + context_dict = context.flatten() + elif hasattr(context, 'dicts'): + # 合并所有上下文字典 + for d in context.dicts: + context_dict.update(d) + + template_context = { + 'recommendations': recommendations, + 'title': title, + **context_dict + } + + return self.render_template('sidebar_widget.html', template_context) + + def get_css_files(self): + """返回CSS文件""" + return ['css/recommendation.css'] + + def get_js_files(self): + """返回JS文件""" + return ['js/recommendation.js'] + + def get_recommendations(self, article, count=5): + """获取推荐文章""" + if not article: + return [] + + recommendations = [] + + # 1. 基于标签的推荐 + if article.tags.exists(): + tag_ids = list(article.tags.values_list('id', flat=True)) + tag_based = list(Article.objects.filter( + status='p', + tags__id__in=tag_ids + ).exclude( + id=article.id + ).exclude( + title__isnull=True + ).exclude( + title__exact='' + ).distinct().order_by('-views')[:count]) + recommendations.extend(tag_based) + + # 2. 如果数量不够,基于分类推荐 + if len(recommendations) < count and self.CONFIG['enable_category_fallback']: + needed = count - len(recommendations) + existing_ids = [r.id for r in recommendations] + [article.id] + + category_based = list(Article.objects.filter( + status='p', + category=article.category + ).exclude( + id__in=existing_ids + ).exclude( + title__isnull=True + ).exclude( + title__exact='' + ).order_by('-views')[:needed]) + recommendations.extend(category_based) + + # 3. 如果还是不够,推荐热门文章 + if len(recommendations) < count and self.CONFIG['enable_popular_fallback']: + needed = count - len(recommendations) + existing_ids = [r.id for r in recommendations] + [article.id] + + popular_articles = list(Article.objects.filter( + status='p' + ).exclude( + id__in=existing_ids + ).exclude( + title__isnull=True + ).exclude( + title__exact='' + ).order_by('-views')[:needed]) + recommendations.extend(popular_articles) + + # 过滤掉无效的推荐 + valid_recommendations = [] + for rec in recommendations: + if rec.title and len(rec.title.strip()) > 0: + valid_recommendations.append(rec) + else: + logger.warning(f"过滤掉空标题文章: ID={rec.id}, 标题='{rec.title}'") + + # 调试:记录推荐结果 + logger.info(f"原始推荐数量: {len(recommendations)}, 有效推荐数量: {len(valid_recommendations)}") + for i, rec in enumerate(valid_recommendations): + logger.info(f"推荐 {i+1}: ID={rec.id}, 标题='{rec.title}', 长度={len(rec.title)}") + + return valid_recommendations[:count] + + def get_popular_articles(self, count=3): + """获取热门文章""" + return list(Article.objects.filter( + status='p' + ).order_by('-views')[:count]) + + +# 实例化插件 +plugin = ArticleRecommendationPlugin() diff --git a/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css b/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css new file mode 100644 index 0000000..b223f41 --- /dev/null +++ b/src/plugins/article_recommendation/static/article_recommendation/css/recommendation.css @@ -0,0 +1,166 @@ +/* 文章推荐插件样式 - 与网站风格保持一致 */ + +/* 文章底部推荐样式 */ +.article-recommendations { + margin: 30px 0; + padding: 20px; + background: #fff; + border: 1px solid #e1e1e1; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.recommendations-title { + margin: 0 0 15px 0; + font-size: 18px; + color: #444; + font-weight: bold; + padding-bottom: 8px; + border-bottom: 2px solid #21759b; + display: inline-block; +} + +.recommendations-icon { + margin-right: 5px; + font-size: 16px; +} + +.recommendations-grid { + display: grid; + gap: 15px; + grid-template-columns: 1fr; + margin-top: 15px; +} + +.recommendation-card { + background: #fff; + border: 1px solid #e1e1e1; + border-radius: 3px; + transition: all 0.2s ease; + overflow: hidden; +} + +.recommendation-card:hover { + border-color: #21759b; + box-shadow: 0 2px 5px rgba(33, 117, 155, 0.1); +} + +.recommendation-link { + display: block; + padding: 15px; + text-decoration: none; + color: inherit; +} + +.recommendation-title { + margin: 0 0 8px 0; + font-size: 15px; + font-weight: normal; + color: #444; + line-height: 1.4; + transition: color 0.2s ease; +} + +.recommendation-card:hover .recommendation-title { + color: #21759b; +} + +.recommendation-meta { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: #757575; +} + +.recommendation-category { + background: #ebebeb; + color: #5e5e5e; + padding: 2px 6px; + border-radius: 2px; + font-size: 11px; + font-weight: normal; +} + +.recommendation-date { + font-weight: normal; + color: #757575; +} + +/* 侧边栏推荐样式 */ +.widget_recommendations { + margin-bottom: 20px; +} + +.widget_recommendations .widget-title { + font-size: 16px; + font-weight: bold; + margin-bottom: 15px; + color: #333; + border-bottom: 2px solid #007cba; + padding-bottom: 5px; +} + +.recommendations-list { + list-style: none; + padding: 0; + margin: 0; +} + +.recommendations-list .recommendation-item { + padding: 8px 0; + border-bottom: 1px solid #eee; + background: none; + border: none; + border-radius: 0; +} + +.recommendations-list .recommendation-item:last-child { + border-bottom: none; +} + +.recommendations-list .recommendation-item a { + color: #333; + text-decoration: none; + font-size: 14px; + line-height: 1.4; + display: block; + margin-bottom: 4px; + transition: color 0.3s ease; +} + +.recommendations-list .recommendation-item a:hover { + color: #007cba; +} + +.recommendations-list .recommendation-meta { + font-size: 11px; + color: #999; + margin: 0; +} + +.recommendations-list .recommendation-meta span { + margin-right: 10px; +} + +/* 响应式设计 - 分栏显示 */ +@media (min-width: 768px) { + .recommendations-grid { + grid-template-columns: repeat(2, 1fr); + gap: 15px; + } +} + +@media (min-width: 1024px) { + .recommendations-grid { + grid-template-columns: repeat(3, 1fr); + gap: 15px; + } +} + +@media (min-width: 1200px) { + .recommendations-grid { + grid-template-columns: repeat(4, 1fr); + gap: 15px; + } +} diff --git a/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js b/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js new file mode 100644 index 0000000..eb19211 --- /dev/null +++ b/src/plugins/article_recommendation/static/article_recommendation/js/recommendation.js @@ -0,0 +1,93 @@ +/** + * 文章推荐插件JavaScript + */ + +(function() { + 'use strict'; + + // 等待DOM加载完成 + document.addEventListener('DOMContentLoaded', function() { + initRecommendations(); + }); + + function initRecommendations() { + // 添加点击统计 + trackRecommendationClicks(); + + // 懒加载优化(如果需要) + lazyLoadRecommendations(); + } + + function trackRecommendationClicks() { + const recommendationLinks = document.querySelectorAll('.recommendation-item a'); + + recommendationLinks.forEach(function(link) { + link.addEventListener('click', function(e) { + // 可以在这里添加点击统计逻辑 + const articleTitle = this.textContent.trim(); + const articleUrl = this.href; + + // 发送统计数据到后端(可选) + if (typeof gtag !== 'undefined') { + gtag('event', 'click', { + 'event_category': 'recommendation', + 'event_label': articleTitle, + 'value': 1 + }); + } + + console.log('Recommendation clicked:', articleTitle, articleUrl); + }); + }); + } + + function lazyLoadRecommendations() { + // 如果推荐内容很多,可以实现懒加载 + const recommendationContainer = document.querySelector('.article-recommendations'); + + if (!recommendationContainer) { + return; + } + + // 检查是否在视窗中 + const observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + entry.target.classList.add('loaded'); + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.1 + }); + + const recommendationItems = document.querySelectorAll('.recommendation-item'); + recommendationItems.forEach(function(item) { + observer.observe(item); + }); + } + + // 添加一些动画效果 + function addAnimations() { + const recommendationItems = document.querySelectorAll('.recommendation-item'); + + recommendationItems.forEach(function(item, index) { + item.style.opacity = '0'; + item.style.transform = 'translateY(20px)'; + item.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + + setTimeout(function() { + item.style.opacity = '1'; + item.style.transform = 'translateY(0)'; + }, index * 100); + }); + } + + // 如果需要,可以在这里添加更多功能 + window.ArticleRecommendation = { + init: initRecommendations, + track: trackRecommendationClicks, + animate: addAnimations + }; + +})(); diff --git a/src/plugins/external_links/__init__.py b/src/plugins/external_links/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/plugins/external_links/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/plugins/external_links/__pycache__/__init__.cpython-311.pyc b/src/plugins/external_links/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..e3769bc Binary files /dev/null and b/src/plugins/external_links/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/external_links/__pycache__/plugin.cpython-311.pyc b/src/plugins/external_links/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..02b4df3 Binary files /dev/null and b/src/plugins/external_links/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/external_links/plugin.py b/src/plugins/external_links/plugin.py deleted file mode 100644 index 5b2ef14..0000000 --- a/src/plugins/external_links/plugin.py +++ /dev/null @@ -1,48 +0,0 @@ -import re -from urllib.parse import urlparse -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks -from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - - -class ExternalLinksPlugin(BasePlugin): - PLUGIN_NAME = '外部链接处理器' - PLUGIN_DESCRIPTION = '自动为文章中的外部链接添加 target="_blank" 和 rel="noopener noreferrer" 属性。' - PLUGIN_VERSION = '0.1.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def register_hooks(self): - hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.process_external_links) - - def process_external_links(self, content, *args, **kwargs): - from djangoblog.utils import get_current_site - site_domain = get_current_site().domain - - # 正则表达式查找所有 标签 - link_pattern = re.compile(r'(]*?\s+)?href=")([^"]*)(".*?/a>)', re.IGNORECASE) - - def replacer(match): - # match.group(1) 是 ... - href = match.group(2) - - # 如果链接已经有 target 属性,则不处理 - if 'target=' in match.group(0).lower(): - return match.group(0) - - # 解析链接 - parsed_url = urlparse(href) - - # 如果链接是外部的 (有域名且域名不等于当前网站域名) - if parsed_url.netloc and parsed_url.netloc != site_domain: - # 添加 target 和 rel 属性 - return f'{match.group(1)}{href}" target="_blank" rel="noopener noreferrer"{match.group(3)}' - - # 否则返回原样 - return match.group(0) - - return link_pattern.sub(replacer, content) - - -plugin = ExternalLinksPlugin() diff --git a/src/plugins/image_lazy_loading/__init__.py b/src/plugins/image_lazy_loading/__init__.py new file mode 100644 index 0000000..2d27de0 --- /dev/null +++ b/src/plugins/image_lazy_loading/__init__.py @@ -0,0 +1 @@ +# Image Lazy Loading Plugin diff --git a/src/plugins/image_lazy_loading/__pycache__/__init__.cpython-311.pyc b/src/plugins/image_lazy_loading/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..d64914b Binary files /dev/null and b/src/plugins/image_lazy_loading/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/image_lazy_loading/__pycache__/plugin.cpython-311.pyc b/src/plugins/image_lazy_loading/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..86ecbe0 Binary files /dev/null and b/src/plugins/image_lazy_loading/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/image_lazy_loading/plugin.py b/src/plugins/image_lazy_loading/plugin.py new file mode 100644 index 0000000..b4b9e0a --- /dev/null +++ b/src/plugins/image_lazy_loading/plugin.py @@ -0,0 +1,182 @@ +import re +import hashlib +from urllib.parse import urlparse +from djangoblog.plugin_manage.base_plugin import BasePlugin +from djangoblog.plugin_manage import hooks +from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME + + +class ImageOptimizationPlugin(BasePlugin): + PLUGIN_NAME = '图片性能优化插件' + PLUGIN_DESCRIPTION = '自动为文章中的图片添加懒加载、异步解码等性能优化属性,显著提升页面加载速度。' + PLUGIN_VERSION = '1.0.0' + PLUGIN_AUTHOR = 'liangliangyy' + + def __init__(self): + # 插件配置 + self.config = { + 'enable_lazy_loading': True, # 启用懒加载 + 'enable_async_decoding': True, # 启用异步解码 + 'add_loading_placeholder': True, # 添加加载占位符 + 'optimize_external_images': True, # 优化外部图片 + 'add_responsive_attributes': True, # 添加响应式属性 + 'skip_first_image': True, # 跳过第一张图片(LCP优化) + } + super().__init__() + + def register_hooks(self): + hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.optimize_images) + + def optimize_images(self, content, *args, **kwargs): + """ + 优化文章中的图片标签 + """ + if not content: + return content + + # 正则表达式匹配 img 标签 + img_pattern = re.compile( + r']*?)(?:\s*/)?>', + re.IGNORECASE | re.DOTALL + ) + + image_count = 0 + + def replace_img_tag(match): + nonlocal image_count + image_count += 1 + + # 获取原始属性 + original_attrs = match.group(1) + + # 解析现有属性 + attrs = self._parse_img_attributes(original_attrs) + + # 应用优化 + optimized_attrs = self._apply_optimizations(attrs, image_count) + + # 重构 img 标签 + return self._build_img_tag(optimized_attrs) + + # 替换所有 img 标签 + optimized_content = img_pattern.sub(replace_img_tag, content) + + return optimized_content + + def _parse_img_attributes(self, attr_string): + """ + 解析 img 标签的属性 + """ + attrs = {} + + # 正则表达式匹配属性 + attr_pattern = re.compile(r'(\w+)=(["\'])(.*?)\2') + + for match in attr_pattern.finditer(attr_string): + attr_name = match.group(1).lower() + attr_value = match.group(3) + attrs[attr_name] = attr_value + + return attrs + + def _apply_optimizations(self, attrs, image_index): + """ + 应用各种图片优化 + """ + # 1. 懒加载优化(跳过第一张图片以优化LCP) + if self.config['enable_lazy_loading']: + if not (self.config['skip_first_image'] and image_index == 1): + if 'loading' not in attrs: + attrs['loading'] = 'lazy' + + # 2. 异步解码 + if self.config['enable_async_decoding']: + if 'decoding' not in attrs: + attrs['decoding'] = 'async' + + # 3. 添加样式优化 + current_style = attrs.get('style', '') + + # 确保图片不会超出容器 + if 'max-width' not in current_style: + if current_style and not current_style.endswith(';'): + current_style += ';' + current_style += 'max-width:100%;height:auto;' + attrs['style'] = current_style + + # 4. 添加 alt 属性(SEO和可访问性) + if 'alt' not in attrs: + # 尝试从图片URL生成有意义的alt文本 + src = attrs.get('src', '') + if src: + # 从文件名生成alt文本 + filename = src.split('/')[-1].split('.')[0] + # 移除常见的无意义字符 + clean_name = re.sub(r'[0-9a-f]{8,}', '', filename) # 移除长hash + clean_name = re.sub(r'[_-]+', ' ', clean_name).strip() + attrs['alt'] = clean_name if clean_name else '文章图片' + else: + attrs['alt'] = '文章图片' + + # 5. 外部图片优化 + if self.config['optimize_external_images'] and 'src' in attrs: + src = attrs['src'] + parsed_url = urlparse(src) + + # 如果是外部图片,添加 referrerpolicy + if parsed_url.netloc and parsed_url.netloc != self._get_current_domain(): + attrs['referrerpolicy'] = 'no-referrer-when-downgrade' + # 为外部图片添加crossorigin属性以支持性能监控 + if 'crossorigin' not in attrs: + attrs['crossorigin'] = 'anonymous' + + # 6. 响应式图片属性(如果配置启用) + if self.config['add_responsive_attributes']: + # 添加 sizes 属性(如果没有的话) + if 'sizes' not in attrs and 'srcset' not in attrs: + attrs['sizes'] = '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw' + + # 7. 添加图片唯一标识符用于性能追踪 + if 'data-img-id' not in attrs and 'src' in attrs: + img_hash = hashlib.md5(attrs['src'].encode()).hexdigest()[:8] + attrs['data-img-id'] = f'img-{img_hash}' + + # 8. 为第一张图片添加高优先级提示(LCP优化) + if image_index == 1 and self.config['skip_first_image']: + attrs['fetchpriority'] = 'high' + # 移除懒加载以确保快速加载 + if 'loading' in attrs: + del attrs['loading'] + + return attrs + + def _build_img_tag(self, attrs): + """ + 重新构建 img 标签 + """ + attr_strings = [] + + # 确保 src 属性在最前面 + if 'src' in attrs: + attr_strings.append(f'src="{attrs["src"]}"') + + # 添加其他属性 + for key, value in attrs.items(): + if key != 'src': # src 已经添加过了 + attr_strings.append(f'{key}="{value}"') + + return f'' + + def _get_current_domain(self): + """ + 获取当前网站域名 + """ + try: + from djangoblog.utils import get_current_site + return get_current_site().domain + except: + return '' + + +# 实例化插件 +plugin = ImageOptimizationPlugin() diff --git a/src/plugins/reading_time/__init__.py b/src/plugins/reading_time/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/plugins/reading_time/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/plugins/reading_time/__pycache__/__init__.cpython-311.pyc b/src/plugins/reading_time/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..2e1851f Binary files /dev/null and b/src/plugins/reading_time/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/reading_time/__pycache__/plugin.cpython-311.pyc b/src/plugins/reading_time/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..fcbba00 Binary files /dev/null and b/src/plugins/reading_time/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/reading_time/plugin.py b/src/plugins/reading_time/plugin.py index 35f9db1..4b929d8 100644 --- a/src/plugins/reading_time/plugin.py +++ b/src/plugins/reading_time/plugin.py @@ -17,7 +17,15 @@ class ReadingTimePlugin(BasePlugin): def add_reading_time(self, content, *args, **kwargs): """ 计算阅读时间并添加到内容开头。 + 只在文章详情页显示,首页(文章列表页)不显示。 """ + # 检查是否为摘要模式(首页/文章列表页) + # 通过kwargs中的is_summary参数判断 + is_summary = kwargs.get('is_summary', False) + if is_summary: + # 如果是摘要模式(首页),直接返回原内容,不添加阅读时间 + return content + # 移除HTML标签和空白字符,以获得纯文本 clean_content = re.sub(r'<[^>]*>', '', content) clean_content = clean_content.strip() diff --git a/src/plugins/seo_optimizer/__init__.py b/src/plugins/seo_optimizer/__init__.py deleted file mode 100644 index e88afca..0000000 --- a/src/plugins/seo_optimizer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package diff --git a/src/plugins/seo_optimizer/__pycache__/__init__.cpython-311.pyc b/src/plugins/seo_optimizer/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0473a47 Binary files /dev/null and b/src/plugins/seo_optimizer/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/seo_optimizer/__pycache__/plugin.cpython-311.pyc b/src/plugins/seo_optimizer/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..f793500 Binary files /dev/null and b/src/plugins/seo_optimizer/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/seo_optimizer/plugin.py b/src/plugins/seo_optimizer/plugin.py index b5b19a3..de12c15 100644 --- a/src/plugins/seo_optimizer/plugin.py +++ b/src/plugins/seo_optimizer/plugin.py @@ -97,6 +97,8 @@ class SeoOptimizerPlugin(BasePlugin): structured_data = { "@context": "https://schema.org", "@type": "WebSite", + "name": blog_setting.site_name, + "description": blog_setting.site_description, "url": request.build_absolute_uri('/'), "potentialAction": { "@type": "SearchAction", @@ -131,12 +133,15 @@ class SeoOptimizerPlugin(BasePlugin): json_ld_script = f'' - return f""" + seo_html = f""" {seo_data.get("title", "")} {seo_data.get("meta_tags", "")} {json_ld_script} """ + + # 将SEO内容追加到现有的metas内容上 + return metas + seo_html plugin = SeoOptimizerPlugin() diff --git a/src/plugins/view_count/__init__.py b/src/plugins/view_count/__init__.py deleted file mode 100644 index 8804fdf..0000000 --- a/src/plugins/view_count/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file makes this a Python package \ No newline at end of file diff --git a/src/plugins/view_count/__pycache__/__init__.cpython-311.pyc b/src/plugins/view_count/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..287c667 Binary files /dev/null and b/src/plugins/view_count/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/plugins/view_count/__pycache__/plugin.cpython-311.pyc b/src/plugins/view_count/__pycache__/plugin.cpython-311.pyc new file mode 100644 index 0000000..0a7b862 Binary files /dev/null and b/src/plugins/view_count/__pycache__/plugin.cpython-311.pyc differ diff --git a/src/plugins/view_count/plugin.py b/src/plugins/view_count/plugin.py deleted file mode 100644 index 15e9d94..0000000 --- a/src/plugins/view_count/plugin.py +++ /dev/null @@ -1,18 +0,0 @@ -from djangoblog.plugin_manage.base_plugin import BasePlugin -from djangoblog.plugin_manage import hooks - - -class ViewCountPlugin(BasePlugin): - PLUGIN_NAME = '文章浏览次数统计' - PLUGIN_DESCRIPTION = '统计文章的浏览次数' - PLUGIN_VERSION = '0.1.0' - PLUGIN_AUTHOR = 'liangliangyy' - - def register_hooks(self): - hooks.register('after_article_body_get', self.record_view) - - def record_view(self, article, *args, **kwargs): - article.viewed() - - -plugin = ViewCountPlugin() \ No newline at end of file diff --git a/src/servermanager/MemcacheStorage.py b/src/servermanager/MemcacheStorage.py deleted file mode 100644 index 38a7990..0000000 --- a/src/servermanager/MemcacheStorage.py +++ /dev/null @@ -1,32 +0,0 @@ -from werobot.session import SessionStorage -from werobot.utils import json_loads, json_dumps - -from djangoblog.utils import cache - - -class MemcacheStorage(SessionStorage): - def __init__(self, prefix='ws_'): - self.prefix = prefix - self.cache = cache - - @property - def is_available(self): - value = "1" - self.set('checkavaliable', value=value) - return value == self.get('checkavaliable') - - def key_name(self, s): - return '{prefix}{s}'.format(prefix=self.prefix, s=s) - - def get(self, id): - id = self.key_name(id) - session_json = self.cache.get(id) or '{}' - return json_loads(session_json) - - def set(self, id, value): - id = self.key_name(id) - self.cache.set(id, json_dumps(value)) - - def delete(self, id): - id = self.key_name(id) - self.cache.delete(id) diff --git a/src/servermanager/__init__.py b/src/servermanager/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/servermanager/__pycache__/MemcacheStorage.cpython-311.pyc b/src/servermanager/__pycache__/MemcacheStorage.cpython-311.pyc new file mode 100644 index 0000000..a673789 Binary files /dev/null and b/src/servermanager/__pycache__/MemcacheStorage.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/__init__.cpython-311.pyc b/src/servermanager/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..01321cd Binary files /dev/null and b/src/servermanager/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/admin.cpython-311.pyc b/src/servermanager/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..2a2c95b Binary files /dev/null and b/src/servermanager/__pycache__/admin.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/apps.cpython-311.pyc b/src/servermanager/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000..800626a Binary files /dev/null and b/src/servermanager/__pycache__/apps.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/models.cpython-311.pyc b/src/servermanager/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..3d3ace9 Binary files /dev/null and b/src/servermanager/__pycache__/models.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/robot.cpython-311.pyc b/src/servermanager/__pycache__/robot.cpython-311.pyc new file mode 100644 index 0000000..74c54e9 Binary files /dev/null and b/src/servermanager/__pycache__/robot.cpython-311.pyc differ diff --git a/src/servermanager/__pycache__/urls.cpython-311.pyc b/src/servermanager/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..52b2842 Binary files /dev/null and b/src/servermanager/__pycache__/urls.cpython-311.pyc differ diff --git a/src/servermanager/admin.py b/src/servermanager/admin.py deleted file mode 100644 index f26f4f6..0000000 --- a/src/servermanager/admin.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.contrib import admin -# Register your models here. - - -class CommandsAdmin(admin.ModelAdmin): - list_display = ('title', 'command', 'describe') - - -class EmailSendLogAdmin(admin.ModelAdmin): - list_display = ('title', 'emailto', 'send_result', 'creation_time') - readonly_fields = ( - 'title', - 'emailto', - 'send_result', - 'creation_time', - 'content') - - def has_add_permission(self, request): - return False diff --git a/src/servermanager/api/__init__.py b/src/servermanager/api/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/src/servermanager/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/servermanager/api/__pycache__/__init__.cpython-311.pyc b/src/servermanager/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7626632 Binary files /dev/null and b/src/servermanager/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/servermanager/api/__pycache__/blogapi.cpython-311.pyc b/src/servermanager/api/__pycache__/blogapi.cpython-311.pyc new file mode 100644 index 0000000..99e76df Binary files /dev/null and b/src/servermanager/api/__pycache__/blogapi.cpython-311.pyc differ diff --git a/src/servermanager/api/__pycache__/commonapi.cpython-311.pyc b/src/servermanager/api/__pycache__/commonapi.cpython-311.pyc new file mode 100644 index 0000000..5129c92 Binary files /dev/null and b/src/servermanager/api/__pycache__/commonapi.cpython-311.pyc differ diff --git a/src/servermanager/api/blogapi.py b/src/servermanager/api/blogapi.py deleted file mode 100644 index 8a4d6ac..0000000 --- a/src/servermanager/api/blogapi.py +++ /dev/null @@ -1,27 +0,0 @@ -from haystack.query import SearchQuerySet - -from blog.models import Article, Category - - -class BlogApi: - def __init__(self): - self.searchqueryset = SearchQuerySet() - self.searchqueryset.auto_query('') - self.__max_takecount__ = 8 - - def search_articles(self, query): - sqs = self.searchqueryset.auto_query(query) - sqs = sqs.load_all() - return sqs[:self.__max_takecount__] - - def get_category_lists(self): - return Category.objects.all() - - def get_category_articles(self, categoryname): - articles = Article.objects.filter(category__name=categoryname) - if articles: - return articles[:self.__max_takecount__] - return None - - def get_recent_articles(self): - return Article.objects.all()[:self.__max_takecount__] diff --git a/src/servermanager/api/commonapi.py b/src/servermanager/api/commonapi.py deleted file mode 100644 index 83ad9ff..0000000 --- a/src/servermanager/api/commonapi.py +++ /dev/null @@ -1,64 +0,0 @@ -import logging -import os - -import openai - -from servermanager.models import commands - -logger = logging.getLogger(__name__) - -openai.api_key = os.environ.get('OPENAI_API_KEY') -if os.environ.get('HTTP_PROXY'): - openai.proxy = os.environ.get('HTTP_PROXY') - - -class ChatGPT: - - @staticmethod - def chat(prompt): - try: - completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", - messages=[{"role": "user", "content": prompt}]) - return completion.choices[0].message.content - except Exception as e: - logger.error(e) - return "服务器出错了" - - -class CommandHandler: - def __init__(self): - self.commands = commands.objects.all() - - def run(self, title): - """ - 运行命令 - :param title: 命令 - :return: 返回命令执行结果 - """ - cmd = list( - filter( - lambda x: x.title.upper() == title.upper(), - self.commands)) - if cmd: - return self.__run_command__(cmd[0].command) - else: - return "未找到相关命令,请输入hepme获得帮助。" - - def __run_command__(self, cmd): - try: - res = os.popen(cmd).read() - return res - except BaseException: - return '命令执行出错!' - - def get_help(self): - rsp = '' - for cmd in self.commands: - rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe) - return rsp - - -if __name__ == '__main__': - chatbot = ChatGPT() - prompt = "写一篇1000字关于AI的论文" - print(chatbot.chat(prompt)) diff --git a/src/servermanager/apps.py b/src/servermanager/apps.py deleted file mode 100644 index 03cc38d..0000000 --- a/src/servermanager/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ServermanagerConfig(AppConfig): - name = 'servermanager' diff --git a/src/servermanager/migrations/0001_initial.py b/src/servermanager/migrations/0001_initial.py deleted file mode 100644 index bbdbf77..0000000 --- a/src/servermanager/migrations/0001_initial.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='commands', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=300, verbose_name='命令标题')), - ('command', models.CharField(max_length=2000, verbose_name='命令')), - ('describe', models.CharField(max_length=300, verbose_name='命令描述')), - ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), - ], - options={ - 'verbose_name': '命令', - 'verbose_name_plural': '命令', - }, - ), - migrations.CreateModel( - name='EmailSendLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('emailto', models.CharField(max_length=300, verbose_name='收件人')), - ('title', models.CharField(max_length=2000, verbose_name='邮件标题')), - ('content', models.TextField(verbose_name='邮件内容')), - ('send_result', models.BooleanField(default=False, verbose_name='结果')), - ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ], - options={ - 'verbose_name': '邮件发送log', - 'verbose_name_plural': '邮件发送log', - 'ordering': ['-created_time'], - }, - ), - ] diff --git a/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py deleted file mode 100644 index 4858857..0000000 --- a/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('servermanager', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='emailsendlog', - options={'ordering': ['-creation_time'], 'verbose_name': '邮件发送log', 'verbose_name_plural': '邮件发送log'}, - ), - migrations.RenameField( - model_name='commands', - old_name='created_time', - new_name='creation_time', - ), - migrations.RenameField( - model_name='commands', - old_name='last_mod_time', - new_name='last_modify_time', - ), - migrations.RenameField( - model_name='emailsendlog', - old_name='created_time', - new_name='creation_time', - ), - ] diff --git a/src/servermanager/migrations/__init__.py b/src/servermanager/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/servermanager/migrations/__pycache__/0001_initial.cpython-311.pyc b/src/servermanager/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..82cbef5 Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-311.pyc b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-311.pyc new file mode 100644 index 0000000..90d91e4 Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-311.pyc differ diff --git a/src/servermanager/migrations/__pycache__/__init__.cpython-311.pyc b/src/servermanager/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..c9deb10 Binary files /dev/null and b/src/servermanager/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/src/servermanager/models.py b/src/servermanager/models.py deleted file mode 100644 index 4326c65..0000000 --- a/src/servermanager/models.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.db import models - - -# Create your models here. -class commands(models.Model): - title = models.CharField('命令标题', max_length=300) - command = models.CharField('命令', max_length=2000) - describe = models.CharField('命令描述', max_length=300) - creation_time = models.DateTimeField('创建时间', auto_now_add=True) - last_modify_time = models.DateTimeField('修改时间', auto_now=True) - - def __str__(self): - return self.title - - class Meta: - verbose_name = '命令' - verbose_name_plural = verbose_name - - -class EmailSendLog(models.Model): - emailto = models.CharField('收件人', max_length=300) - title = models.CharField('邮件标题', max_length=2000) - content = models.TextField('邮件内容') - send_result = models.BooleanField('结果', default=False) - creation_time = models.DateTimeField('创建时间', auto_now_add=True) - - def __str__(self): - return self.title - - class Meta: - verbose_name = '邮件发送log' - verbose_name_plural = verbose_name - ordering = ['-creation_time'] diff --git a/src/servermanager/robot.py b/src/servermanager/robot.py deleted file mode 100644 index 7b45736..0000000 --- a/src/servermanager/robot.py +++ /dev/null @@ -1,187 +0,0 @@ -import logging -import os -import re - -import jsonpickle -from django.conf import settings -from werobot import WeRoBot -from werobot.replies import ArticlesReply, Article -from werobot.session.filestorage import FileStorage - -from djangoblog.utils import get_sha256 -from servermanager.api.blogapi import BlogApi -from servermanager.api.commonapi import ChatGPT, CommandHandler -from .MemcacheStorage import MemcacheStorage - -robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') - or 'lylinux', enable_session=True) -memstorage = MemcacheStorage() -if memstorage.is_available: - robot.config['SESSION_STORAGE'] = memstorage -else: - if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')): - os.remove(os.path.join(settings.BASE_DIR, 'werobot_session')) - robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') - -blogapi = BlogApi() -cmd_handler = CommandHandler() -logger = logging.getLogger(__name__) - - -def convert_to_article_reply(articles, message): - reply = ArticlesReply(message=message) - from blog.templatetags.blog_tags import truncatechars_content - for post in articles: - imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body) - imgurl = '' - if imgs: - imgurl = imgs[0] - article = Article( - title=post.title, - description=truncatechars_content(post.body), - img=imgurl, - url=post.get_full_url() - ) - reply.add_article(article) - return reply - - -@robot.filter(re.compile(r"^\?.*")) -def search(message, session): - s = message.content - searchstr = str(s).replace('?', '') - result = blogapi.search_articles(searchstr) - if result: - articles = list(map(lambda x: x.object, result)) - reply = convert_to_article_reply(articles, message) - return reply - else: - return '没有找到相关文章。' - - -@robot.filter(re.compile(r'^category\s*$', re.I)) -def category(message, session): - categorys = blogapi.get_category_lists() - content = ','.join(map(lambda x: x.name, categorys)) - return '所有文章分类目录:' + content - - -@robot.filter(re.compile(r'^recent\s*$', re.I)) -def recents(message, session): - articles = blogapi.get_recent_articles() - if articles: - reply = convert_to_article_reply(articles, message) - return reply - else: - return "暂时还没有文章" - - -@robot.filter(re.compile('^help$', re.I)) -def help(message, session): - return '''欢迎关注! - 默认会与图灵机器人聊天~~ - 你可以通过下面这些命令来获得信息 - ?关键字搜索文章. - 如?python. - category获得文章分类目录及文章数. - category-***获得该分类目录文章 - 如category-python - recent获得最新文章 - help获得帮助. - weather:获得天气 - 如weather:西安 - idcard:获得身份证信息 - 如idcard:61048119xxxxxxxxxx - music:音乐搜索 - 如music:阴天快乐 - PS:以上标点符号都不支持中文标点~~ - ''' - - -@robot.filter(re.compile(r'^weather\:.*$', re.I)) -def weather(message, session): - return "建设中..." - - -@robot.filter(re.compile(r'^idcard\:.*$', re.I)) -def idcard(message, session): - return "建设中..." - - -@robot.handler -def echo(message, session): - handler = MessageHandler(message, session) - return handler.handler() - - -class MessageHandler: - def __init__(self, message, session): - userid = message.source - self.message = message - self.session = session - self.userid = userid - try: - info = session[userid] - self.userinfo = jsonpickle.decode(info) - except Exception as e: - userinfo = WxUserInfo() - self.userinfo = userinfo - - @property - def is_admin(self): - return self.userinfo.isAdmin - - @property - def is_password_set(self): - return self.userinfo.isPasswordSet - - def save_session(self): - info = jsonpickle.encode(self.userinfo) - self.session[self.userid] = info - - def handler(self): - info = self.message.content - - if self.userinfo.isAdmin and info.upper() == 'EXIT': - self.userinfo = WxUserInfo() - self.save_session() - return "退出成功" - if info.upper() == 'ADMIN': - self.userinfo.isAdmin = True - self.save_session() - return "输入管理员密码" - if self.userinfo.isAdmin and not self.userinfo.isPasswordSet: - passwd = settings.WXADMIN - if settings.TESTING: - passwd = '123' - if passwd.upper() == get_sha256(get_sha256(info)).upper(): - self.userinfo.isPasswordSet = True - self.save_session() - return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助" - else: - if self.userinfo.Count >= 3: - self.userinfo = WxUserInfo() - self.save_session() - return "超过验证次数" - self.userinfo.Count += 1 - self.save_session() - return "验证失败,请重新输入管理员密码:" - if self.userinfo.isAdmin and self.userinfo.isPasswordSet: - if self.userinfo.Command != '' and info.upper() == 'Y': - return cmd_handler.run(self.userinfo.Command) - else: - if info.upper() == 'HELPME': - return cmd_handler.get_help() - self.userinfo.Command = info - self.save_session() - return "确认执行: " + info + " 命令?" - - return ChatGPT.chat(info) - - -class WxUserInfo(): - def __init__(self): - self.isAdmin = False - self.isPasswordSet = False - self.Count = 0 - self.Command = '' diff --git a/src/servermanager/tests.py b/src/servermanager/tests.py deleted file mode 100644 index 22a6689..0000000 --- a/src/servermanager/tests.py +++ /dev/null @@ -1,79 +0,0 @@ -from django.test import Client, RequestFactory, TestCase -from django.utils import timezone -from werobot.messages.messages import TextMessage - -from accounts.models import BlogUser -from blog.models import Category, Article -from servermanager.api.commonapi import ChatGPT -from .models import commands -from .robot import MessageHandler, CommandHandler -from .robot import search, category, recents - - -# Create your tests here. -class ServerManagerTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_chat_gpt(self): - content = ChatGPT.chat("你好") - self.assertIsNotNone(content) - - def test_validate_comment(self): - user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - self.client.login(username='liangliangyy1', password='liangliangyy1') - - c = Category() - c.name = "categoryccc" - c.save() - - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = user - article.category = c - article.type = 'a' - article.status = 'p' - article.save() - s = TextMessage([]) - s.content = "nice" - rsp = search(s, None) - rsp = category(None, None) - self.assertIsNotNone(rsp) - rsp = recents(None, None) - self.assertTrue(rsp != '暂时还没有文章') - - cmd = commands() - cmd.title = "test" - cmd.command = "ls" - cmd.describe = "test" - cmd.save() - - cmdhandler = CommandHandler() - rsp = cmdhandler.run('test') - self.assertIsNotNone(rsp) - s.source = 'u' - s.content = 'test' - msghandler = MessageHandler(s, {}) - - # msghandler.userinfo.isPasswordSet = True - # msghandler.userinfo.isAdmin = True - msghandler.handler() - s.content = 'y' - msghandler.handler() - s.content = 'idcard:12321233' - msghandler.handler() - s.content = 'weather:上海' - msghandler.handler() - s.content = 'admin' - msghandler.handler() - s.content = '123' - msghandler.handler() - - s.content = 'exit' - msghandler.handler() diff --git a/src/servermanager/urls.py b/src/servermanager/urls.py deleted file mode 100644 index 8d134d2..0000000 --- a/src/servermanager/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from werobot.contrib.django import make_view - -from .robot import robot - -app_name = "servermanager" -urlpatterns = [ - path(r'robot', make_view(robot)), - -] diff --git a/src/servermanager/views.py b/src/servermanager/views.py deleted file mode 100644 index 60f00ef..0000000 --- a/src/servermanager/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/src/templates/account/forget_password.html b/src/templates/account/forget_password.html deleted file mode 100644 index 3384531..0000000 --- a/src/templates/account/forget_password.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'share_layout/base_account.html' %} -{% load i18n %} -{% load static %} -{% block content %} -
- - - - - -

- Home Page - | - login page -

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

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

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

- Sign In -

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

{% trans 'article archive' %}

-
- -
- - {% regroup article_list by pub_time.year as year_post_group %} -
    - {% for year in year_post_group %} -
  • {{ year.grouper }} {% trans 'year' %} - {% regroup year.list by pub_time.month as month_post_group %} -
      - {% for month in month_post_group %} -
    • {{ month.grouper }} {% trans 'month' %} - -
    • - {% endfor %} -
    -
  • - {% endfor %} -
-
-
-
- -{% endblock %} - - -{% block sidebar %} - {% load_sidebar user 'i' %} -{% endblock %} - - diff --git a/src/templates/blog/article_index.html b/src/templates/blog/article_index.html deleted file mode 100644 index 0ee6150..0000000 --- a/src/templates/blog/article_index.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% load cache %} -{% block header %} - {% if tag_name %} - {{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }} - {% comment %}{% endcomment %} - {% else %} - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - {% endif %} - - - - - - - -{% endblock %} -{% block content %} -
-
- {% if page_type and tag_name %} -
- -

{{ page_type }}:{{ tag_name }}

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

{{ message }}

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

友情链接

-
- -
- -
-
-
- -{% endblock %} - - -{% block sidebar %} - {% load_sidebar user 'i' %} -{% endblock %} - - diff --git a/src/templates/blog/tags/article_info.html b/src/templates/blog/tags/article_info.html index 3deec44..65b45fa 100644 --- a/src/templates/blog/tags/article_info.html +++ b/src/templates/blog/tags/article_info.html @@ -48,7 +48,7 @@
{% if isindex %} - {{ article.body|custom_markdown|escape|truncatechars_content }} + {% render_article_content article True %}

Read more

{% else %} @@ -62,7 +62,7 @@ {% endif %}
- {{ article.body|custom_markdown|escape }} + {% render_article_content article False %}
{% endif %} @@ -71,4 +71,9 @@ {% load_article_metas article user %} - \ No newline at end of file + + + +{% if not isindex %} + {% render_plugin_widgets 'article_bottom' article=article %} +{% endif %} \ No newline at end of file diff --git a/src/templates/blog/tags/article_meta_info.html b/src/templates/blog/tags/article_meta_info.html index cb6111c..ec8a0f9 100644 --- a/src/templates/blog/tags/article_meta_info.html +++ b/src/templates/blog/tags/article_meta_info.html @@ -5,9 +5,6 @@
{% trans 'posted in' %} {{ article.category.name }} - - - {% if article.type == 'a' %} {% if article.tags.all %} @@ -46,13 +43,14 @@ title="{% datetimeformat article.pub_time %}" itemprop="datePublished" content="{% datetimeformat article.pub_time %}" rel="bookmark"> - - - {% if user.is_superuser %} - {% trans 'edit' %} - {% endif %} + + + {% if user.is_superuser %} + {% trans 'edit' %} + {% endif %}
diff --git a/src/templates/blog/tags/article_pagination.html b/src/templates/blog/tags/article_pagination.html deleted file mode 100644 index 95514ff..0000000 --- a/src/templates/blog/tags/article_pagination.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} - \ No newline at end of file diff --git a/src/templates/blog/tags/article_tag_list.html b/src/templates/blog/tags/article_tag_list.html deleted file mode 100644 index c8ba474..0000000 --- a/src/templates/blog/tags/article_tag_list.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load i18n %} -{% if article_tags_list %} -
-
- {% trans 'tags' %} -
-
- - {% for url,count,tag,color in article_tags_list %} - - {{ tag.name }} - {{ count }} - - {% endfor %} - -
-
-{% endif %} diff --git a/src/templates/blog/tags/breadcrumb.html b/src/templates/blog/tags/breadcrumb.html deleted file mode 100644 index 67087d5..0000000 --- a/src/templates/blog/tags/breadcrumb.html +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/src/templates/blog/tags/sidebar.html b/src/templates/blog/tags/sidebar.html index f70544c..ecb6d20 100644 --- a/src/templates/blog/tags/sidebar.html +++ b/src/templates/blog/tags/sidebar.html @@ -16,7 +16,7 @@ {% endfor %} diff --git a/src/templates/comments/tags/comment_item.html b/src/templates/comments/tags/comment_item.html index ebb0388..0693649 100644 --- a/src/templates/comments/tags/comment_item.html +++ b/src/templates/comments/tags/comment_item.html @@ -2,10 +2,13 @@
  • - + class="avatar avatar-96 photo" + loading="lazy" + decoding="async" + style="max-width:100%;height:auto;">
    - + class="avatar avatar-96 photo" + loading="lazy" + decoding="async" + style="max-width:100%;height:auto;"> -
    - {% load blog_tags %} - {% load comments_tags %} - {% load cache %} - - - {% if article_comments %} -
    -
      - {# {% query article_comments parent_comment=None as parent_comments %}#} - {% for comment_item in p_comments %} - - {% with 0 as depth %} - {% include "comments/tags/comment_item_tree.html" %} - {% endwith %} - {% endfor %} - -
    - -
    -
    - {% endif %} -
    - - \ No newline at end of file diff --git a/src/templates/comments/tags/post_comment.html b/src/templates/comments/tags/post_comment.html deleted file mode 100644 index 3ae5a27..0000000 --- a/src/templates/comments/tags/post_comment.html +++ /dev/null @@ -1,33 +0,0 @@ -
    - -
    -

    发表评论 - -

    -
    {% csrf_token %} -

    - {{ form.body.label_tag }} - - {{ form.body }} - {{ form.body.errors }} -

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

    {{ content }}

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

    - 登录 -

    - -
    -{% endblock %} \ No newline at end of file diff --git a/src/templates/owntracks/show_log_dates.html b/src/templates/owntracks/show_log_dates.html deleted file mode 100644 index 7dbba21..0000000 --- a/src/templates/owntracks/show_log_dates.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - 记录日期 - - - -
      - {% for date in results %} -
    • - {{ date }} -
    • - {% endfor %} -
    - - \ No newline at end of file diff --git a/src/templates/owntracks/show_maps.html b/src/templates/owntracks/show_maps.html deleted file mode 100644 index 3aeda36..0000000 --- a/src/templates/owntracks/show_maps.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - 运动轨迹 - - - -
    - - - - - - - - \ No newline at end of file diff --git a/src/templates/plugins/article_recommendation/__init__.py b/src/templates/plugins/article_recommendation/__init__.py new file mode 100644 index 0000000..7d86a99 --- /dev/null +++ b/src/templates/plugins/article_recommendation/__init__.py @@ -0,0 +1 @@ +# 插件模板目录 diff --git a/src/templates/plugins/article_recommendation/bottom_widget.html b/src/templates/plugins/article_recommendation/bottom_widget.html new file mode 100644 index 0000000..829b7b4 --- /dev/null +++ b/src/templates/plugins/article_recommendation/bottom_widget.html @@ -0,0 +1,23 @@ +{% load i18n %} +
    +

    + 📖{{ title }} +

    +
    + {% for article in recommendations %} + {% if article.title and article.title|length > 0 %} + + {% endif %} + {% endfor %} +
    +
    diff --git a/src/templates/plugins/article_recommendation/sidebar_widget.html b/src/templates/plugins/article_recommendation/sidebar_widget.html new file mode 100644 index 0000000..5f1afbf --- /dev/null +++ b/src/templates/plugins/article_recommendation/sidebar_widget.html @@ -0,0 +1,17 @@ +{% load i18n %} + diff --git a/src/templates/plugins/css_includes.html b/src/templates/plugins/css_includes.html new file mode 100644 index 0000000..37029ae --- /dev/null +++ b/src/templates/plugins/css_includes.html @@ -0,0 +1,4 @@ +{% comment %}插件CSS文件包含模板 - 用于压缩{% endcomment %} +{% for css_file in css_files %} + +{% endfor %} diff --git a/src/templates/plugins/js_includes.html b/src/templates/plugins/js_includes.html new file mode 100644 index 0000000..2a315e3 --- /dev/null +++ b/src/templates/plugins/js_includes.html @@ -0,0 +1,4 @@ +{% comment %}插件JS文件包含模板 - 用于压缩{% endcomment %} +{% for js_file in js_files %} + +{% endfor %} diff --git a/src/templates/search/indexes/blog/article_text.txt b/src/templates/search/indexes/blog/article_text.txt deleted file mode 100644 index 4f9ca76..0000000 --- a/src/templates/search/indexes/blog/article_text.txt +++ /dev/null @@ -1,3 +0,0 @@ -{{ object.title }} -{{ object.author.username }} -{{ object.body }} \ No newline at end of file diff --git a/src/templates/search/search.html b/src/templates/search/search.html deleted file mode 100644 index 1404c60..0000000 --- a/src/templates/search/search.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends 'share_layout/base.html' %} -{% load blog_tags %} -{% block header %} - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - - - - - - - -{% endblock %} -{% block content %} -
    -
    - {% if query %} -
    - {% if suggestion %} -

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

    - {% else %} -

    - 搜索:{{ query }}    -

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

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

    -
    - {% endif %} -
    -
    -{% endblock %} - - -{% block sidebar %} - {% load_sidebar request.user 'i' %} -{% endblock %} - - diff --git a/src/templates/share_layout/adsense.html b/src/templates/share_layout/adsense.html deleted file mode 100644 index 8f99c55..0000000 --- a/src/templates/share_layout/adsense.html +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/src/templates/share_layout/base.html b/src/templates/share_layout/base.html index d3e0e42..2687993 100644 --- a/src/templates/share_layout/base.html +++ b/src/templates/share_layout/base.html @@ -16,15 +16,32 @@ +<<<<<<< HEAD {% block header %} +======= + + + + {% load blog_tags %} + {% head_meta %} + {% block header %} + +>>>>>>> develop {% endblock %} + + + + + + +<<<<<<< HEAD {% compress css %} - +======= + + + + + +>>>>>>> develop + + + + {% compress css %} @@ -44,10 +71,16 @@ {% block compress_css %} {% endblock %} + + {% plugin_compressed_css %} {% endcompress %} + {% if GLOBAL_HEADER %} {{ GLOBAL_HEADER|safe }} {% endif %} + + + {% plugin_head_resources %} @@ -59,6 +92,7 @@

    {{ SITE_DESCRIPTION }}

    {% load i18n %} +<<<<<<< HEAD {#
    #} @@ -79,6 +113,8 @@ {#
    #} +======= +>>>>>>> develop {% include 'share_layout/nav.html' %} @@ -97,19 +133,25 @@ {% include 'share_layout/footer.html' %}
    - -
    - - {% compress js %} - - - - - - {% block compress_js %} - {% endblock %} - {% endcompress %} - {% block footer %} + +{% compress js %} + + + + + {% block compress_js %} {% endblock %} -
    + + {% plugin_compressed_js %} +{% endcompress %} + + + + +{% block footer %} +{% endblock %} + + +{% plugin_body_resources %} + diff --git a/src/templates/share_layout/base_account.html b/src/templates/share_layout/base_account.html deleted file mode 100644 index c00d842..0000000 --- a/src/templates/share_layout/base_account.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - {% load static %} - - - - - - - - - {{ SITE_NAME }} | {{ SITE_DESCRIPTION }} - - {% load compress %} - {% compress css %} - - - - - - - - - - {% endcompress %} - {% compress js %} - - - {% endcompress %} - - - - - -{% block content %} -{% endblock %} - - - - - - - \ No newline at end of file diff --git a/src/templates/share_layout/footer.html b/src/templates/share_layout/footer.html deleted file mode 100644 index cd86a29..0000000 --- a/src/templates/share_layout/footer.html +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/src/templates/share_layout/nav.html b/src/templates/share_layout/nav.html deleted file mode 100644 index 24d4da6..0000000 --- a/src/templates/share_layout/nav.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load i18n %} - - \ No newline at end of file diff --git a/src/templates/share_layout/nav_node.html b/src/templates/share_layout/nav_node.html deleted file mode 100644 index c266880..0000000 --- a/src/templates/share_layout/nav_node.html +++ /dev/null @@ -1,19 +0,0 @@ -
  • - -