Compare commits
3 Commits
master
...
ZXX-master
| Author | SHA1 | Date |
|---|---|---|
|
|
91a1f33395 | 5 months ago |
|
|
2c28b552d0 | 5 months ago |
|
|
7c45634123 | 5 months ago |
@ -1,8 +0,0 @@
|
||||
.DS_Store
|
||||
*.DS_Store
|
||||
src/DjangoBlog/.venv/
|
||||
src/DjangoBlog/venv/
|
||||
src/DjangoBlog/.idea/
|
||||
src/DjangoBlog/logs/
|
||||
src/DjangoBlog/collectedstatic/
|
||||
src/DjangoBlog/uploads/
|
||||
@ -1,3 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.11 (AI实验)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
||||
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11 (AI实验)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AI实验)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/git-test.iml" filepath="$PROJECT_DIR$/.idea/git-test.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/src/DjangoBlog" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,16 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
const double pi=3.14;
|
||||
double r,c,s;
|
||||
cin>>r;
|
||||
c=2.0*pi*r ;
|
||||
s=pi*r*r;
|
||||
cout<<c<<endl;
|
||||
cout<<s<<endl;
|
||||
|
||||
|
||||
return 0 ;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
#评论应用的配置类
|
||||
class CommentsConfig(AppConfig):
|
||||
name = 'comments'# 指定应用名称为'comments',对应安装的应用名
|
||||
@ -1,85 +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
|
||||
|
||||
|
||||
# 评论功能测试类(使用数据库事务隔离)
|
||||
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()
|
||||
|
||||
# 获取评论提交的URL
|
||||
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
|
||||
@ -1,43 +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()}" # 构建文章完整URL
|
||||
|
||||
# 1. 给评论作者发送感谢邮件
|
||||
html_content = _("""<p>Thank you very much for your comments on this site</p>
|
||||
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
|
||||
to review your comments,
|
||||
Thank you again!
|
||||
<br />
|
||||
If the link above cannot be opened, please copy this link to your browser.
|
||||
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
|
||||
tomail = comment.author.email# 收件人邮箱
|
||||
send_email([tomail], subject, html_content)
|
||||
|
||||
# 2. 如果是回复评论,给被回复者发送通知邮件
|
||||
try:
|
||||
if comment.parent_comment:
|
||||
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
|
||||
received a reply. <br/> %(comment_body)s
|
||||
<br/>
|
||||
go check it out!
|
||||
<br/>
|
||||
If the link above cannot be opened, please copy this link to your browser.
|
||||
%(article_url)s
|
||||
""") % {'article_url': article_url, 'article_title': comment.article.title,
|
||||
'comment_body': comment.parent_comment.body}
|
||||
tomail = comment.parent_comment.author.email # 被回复者邮箱
|
||||
send_email([tomail], subject, html_content)
|
||||
except Exception as e:
|
||||
logger.error(e)#记录邮件发送异常
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,10 +0,0 @@
|
||||
[run]
|
||||
source = .
|
||||
include = *.py
|
||||
omit =
|
||||
*migrations*
|
||||
*tests*
|
||||
*.html
|
||||
*whoosh_cn_backend*
|
||||
*settings.py*
|
||||
*venv*
|
||||
@ -1,11 +0,0 @@
|
||||
bin/data/
|
||||
# virtualenv
|
||||
venv/
|
||||
collectedstatic/
|
||||
djangoblog/whoosh_index/
|
||||
uploads/
|
||||
settings_production.py
|
||||
*.md
|
||||
docs/
|
||||
logs/
|
||||
static/
|
||||
@ -1,6 +0,0 @@
|
||||
blog/static/* linguist-vendored
|
||||
*.js linguist-vendored
|
||||
*.css linguist-vendored
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.conf text eol=lf
|
||||
@ -1,18 +0,0 @@
|
||||
<!--
|
||||
如果你不认真勾选下面的内容,我可能会直接关闭你的 Issue。
|
||||
提问之前,建议先阅读 https://github.com/ruby-china/How-To-Ask-Questions-The-Smart-Way
|
||||
-->
|
||||
|
||||
**我确定我已经查看了** (标注`[ ]`为`[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 反馈
|
||||
- [ ] 添加新的特性或者功能
|
||||
- [ ] 请求技术支持
|
||||
@ -1,47 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
- '**/*.yml'
|
||||
- '**/*.txt'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
- '**/*.yml'
|
||||
- '**/*.txt'
|
||||
schedule:
|
||||
- cron: '30 1 * * 0'
|
||||
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@ -1,136 +0,0 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.css'
|
||||
- '**/*.js'
|
||||
|
||||
jobs:
|
||||
build-normal:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ["3.10","3.11" ]
|
||||
|
||||
steps:
|
||||
- name: Start MySQL
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
run: |
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py test
|
||||
|
||||
build-with-es:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: ["3.10","3.11" ]
|
||||
|
||||
steps:
|
||||
- name: Start MySQL
|
||||
uses: samin/mysql-action@v1.3
|
||||
with:
|
||||
host port: 3306
|
||||
container port: 3306
|
||||
character set server: utf8mb4
|
||||
collation server: utf8mb4_general_ci
|
||||
mysql version: latest
|
||||
mysql root password: root
|
||||
mysql database: djangoblog
|
||||
mysql user: root
|
||||
mysql password: root
|
||||
|
||||
- name: Configure sysctl limits
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo sysctl -w vm.swappiness=1
|
||||
sudo sysctl -w fs.file-max=262144
|
||||
sudo sysctl -w vm.max_map_count=262144
|
||||
|
||||
- uses: miyataka/elasticsearch-github-actions@1
|
||||
|
||||
with:
|
||||
stack-version: '7.12.1'
|
||||
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
||||
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
env:
|
||||
DJANGO_MYSQL_PASSWORD: root
|
||||
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||
DJANGO_ELASTICSEARCH_HOST: 127.0.0.1:9200
|
||||
run: |
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
coverage run manage.py test
|
||||
coverage xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: djangoblog/djangoblog:dev
|
||||
@ -1,43 +0,0 @@
|
||||
name: docker
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.yml'
|
||||
branches:
|
||||
- 'master'
|
||||
- 'dev'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set env to docker dev tag
|
||||
if: endsWith(github.ref, '/dev')
|
||||
run: |
|
||||
echo "DOCKER_TAG=test" >> $GITHUB_ENV
|
||||
- name: Set env to docker latest tag
|
||||
if: endsWith(github.ref, '/master')
|
||||
run: |
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{env.DOCKER_TAG}}
|
||||
|
||||
|
||||
@ -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 }}
|
||||
@ -1,80 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
|
||||
# PyCharm
|
||||
# http://www.jetbrains.com/pycharm/webhelp/project.html
|
||||
.idea
|
||||
.iml
|
||||
static/
|
||||
# virtualenv
|
||||
venv/
|
||||
|
||||
collectedstatic/
|
||||
djangoblog/whoosh_index/
|
||||
google93fd32dbd906620a.html
|
||||
baidu_verify_FlHL7cUyC9.html
|
||||
BingSiteAuth.xml
|
||||
cb9339dbe2ff86a5aa169d28dba5f615.txt
|
||||
werobot_session.*
|
||||
django.jpg
|
||||
uploads/
|
||||
settings_production.py
|
||||
werobot_session.db
|
||||
bin/datas/
|
||||
@ -1,15 +0,0 @@
|
||||
FROM python:3.11
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
WORKDIR /code/djangoblog/
|
||||
RUN apt-get update && \
|
||||
apt-get install default-libmysqlclient-dev gettext -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
ADD requirements.txt requirements.txt
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt && \
|
||||
pip install --no-cache-dir gunicorn[gevent] && \
|
||||
pip cache purge
|
||||
|
||||
ADD . .
|
||||
RUN chmod +x /code/djangoblog/deploy/entrypoint.sh
|
||||
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]
|
||||
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 车亮亮
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@ -1,16 +0,0 @@
|
||||
DjangoBlog 项目
|
||||
===============
|
||||
|
||||
这是一个基于Django的博客系统项目。
|
||||
|
||||
项目结构:
|
||||
- manage.py: Django管理脚本
|
||||
- requirements.txt: Python依赖包
|
||||
- blog/: 博客主应用
|
||||
- accounts/: 用户认证应用
|
||||
- templates/: HTML模板
|
||||
|
||||
如何运行:
|
||||
1. 安装依赖: pip install -r requirements.txt
|
||||
2. 运行服务器: python manage.py runserver
|
||||
3. 访问: http://127.0.0.1:8000
|
||||
@ -1,9 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
"""
|
||||
账户应用的Django配置类
|
||||
用于配置应用的元数据和启动时的初始化行为
|
||||
"""
|
||||
name = 'accounts' #ZXY: 应用的Python路径
|
||||
@ -1,68 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||
#ZXY: 第二次迁移 - 主要进行字段名称国际化和结构调整
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""
|
||||
第二次迁移 - 修复字段命名和国际化问题
|
||||
这次迁移展示了数据库模式演变的常见模式
|
||||
"""
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0001_initial'), #ZXY: 依赖第一次迁移
|
||||
]
|
||||
|
||||
operations = [
|
||||
#ZXY: 操作1: 修改模型选项 - 将中文显示名改为英文
|
||||
migrations.AlterModelOptions(
|
||||
name='bloguser',
|
||||
options={
|
||||
'get_latest_by': 'id',
|
||||
'ordering': ['-id'],
|
||||
'verbose_name': 'user', #ZXY: 改为英文单数
|
||||
'verbose_name_plural': 'user'}, #ZXY: 改为英文复数
|
||||
),
|
||||
|
||||
#ZXY: 操作2: 删除旧的创建时间字段
|
||||
migrations.RemoveField(
|
||||
model_name='bloguser',
|
||||
name='created_time', #ZXY: 移除旧的字段命名
|
||||
),
|
||||
|
||||
#ZXY: 操作3: 删除旧的修改时间字段
|
||||
migrations.RemoveField(
|
||||
model_name='bloguser',
|
||||
name='last_mod_time', #ZXY: 移除旧的字段命名
|
||||
),
|
||||
|
||||
#ZXY: 操作4: 添加新的创建时间字段(国际化命名)
|
||||
migrations.AddField(
|
||||
model_name='bloguser',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
|
||||
#ZXY: 操作5: 添加新的修改时间字段(国际化命名)
|
||||
migrations.AddField(
|
||||
model_name='bloguser',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
|
||||
),
|
||||
|
||||
#ZXY: 操作6: 修改昵称字段的显示名称(国际化)
|
||||
migrations.AlterField(
|
||||
model_name='bloguser',
|
||||
name='nickname',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), # 改为英文
|
||||
),
|
||||
|
||||
#ZXY: 操作7: 修改来源字段的显示名称(国际化)
|
||||
migrations.AlterField(
|
||||
model_name='bloguser',
|
||||
name='source',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
|
||||
),
|
||||
]
|
||||
@ -1,30 +0,0 @@
|
||||
# Generated by Django 5.2.4 on 2025-11-17 20:23
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_alter_bloguser_options_remove_bloguser_created_time_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserChangeHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('changed_by', models.CharField(blank=True, max_length=150, verbose_name='changed by')),
|
||||
('change_time', models.DateTimeField(auto_now_add=True, verbose_name='change time')),
|
||||
('change_description', models.TextField(blank=True, verbose_name='change description')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user change history',
|
||||
'verbose_name_plural': 'user change histories',
|
||||
'ordering': ['-change_time'],
|
||||
},
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -1,112 +0,0 @@
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Register your models here.
|
||||
from .models import Article #ZNY 导入Article模型
|
||||
|
||||
|
||||
class ArticleForm(forms.ModelForm): #ZNY 定义文章表单类
|
||||
# body = forms.CharField(widget=AdminPagedownWidget()) #ZNY 注释掉的Markdown编辑器字段
|
||||
|
||||
class Meta:
|
||||
model = Article #ZNY 指定表单对应的模型
|
||||
fields = '__all__' #ZNY 包含所有模型字段
|
||||
|
||||
|
||||
def makr_article_publish(modeladmin, request, queryset): #ZNY 发布文章的管理动作函数
|
||||
queryset.update(status='p') #ZNY 将选中文章状态更新为发布
|
||||
|
||||
|
||||
def draft_article(modeladmin, request, queryset): #ZNY 设为草稿的管理动作函数
|
||||
queryset.update(status='d') #ZNY 将选中文章状态更新为草稿
|
||||
|
||||
|
||||
def close_article_commentstatus(modeladmin, request, queryset): #ZNY 关闭评论的管理动作函数
|
||||
queryset.update(comment_status='c') #ZNY 将选中文章评论状态更新为关闭
|
||||
|
||||
|
||||
def open_article_commentstatus(modeladmin, request, queryset): #ZNY 打开评论的管理动作函数
|
||||
queryset.update(comment_status='o') #ZNY 将选中文章评论状态更新为打开
|
||||
|
||||
|
||||
makr_article_publish.short_description = _('Publish selected articles') #ZNY 设置管理动作显示名称
|
||||
draft_article.short_description = _('Draft selected articles') #ZNY 设置管理动作显示名称
|
||||
close_article_commentstatus.short_description = _('Close article comments') #ZNY 设置管理动作显示名称
|
||||
open_article_commentstatus.short_description = _('Open article comments') #ZNY 设置管理动作显示名称
|
||||
|
||||
|
||||
class ArticlelAdmin(admin.ModelAdmin): #ZNY 文章模型管理类
|
||||
list_per_page = 20 #ZNY 每页显示20条记录
|
||||
search_fields = ('body', 'title') #ZNY 设置搜索字段
|
||||
form = ArticleForm #ZNY 指定使用的表单类
|
||||
list_display = ( #ZNY 设置列表页显示的字段
|
||||
'id',
|
||||
'title',
|
||||
'author',
|
||||
'link_to_category',
|
||||
'creation_time',
|
||||
'views',
|
||||
'status',
|
||||
'type',
|
||||
'article_order')
|
||||
list_display_links = ('id', 'title') #ZNY 设置可点击进入编辑页的字段
|
||||
list_filter = ('status', 'type', 'category') #ZNY 设置右侧过滤器字段
|
||||
filter_horizontal = ('tags',) #ZNY 设置标签字段使用水平过滤器
|
||||
exclude = ('creation_time', 'last_modify_time') #ZNY 排除不需要在表单中显示的字段
|
||||
view_on_site = True #ZNY 启用"在站点查看"功能
|
||||
actions = [ #ZNY 注册管理动作
|
||||
makr_article_publish,
|
||||
draft_article,
|
||||
close_article_commentstatus,
|
||||
open_article_commentstatus]
|
||||
|
||||
def link_to_category(self, obj): #ZNY 自定义方法:生成分类链接
|
||||
info = (obj.category._meta.app_label, obj.category._meta.model_name) #ZNY 获取分类模型的app和模型名称
|
||||
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) #ZNY 生成分类编辑页链接
|
||||
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name)) #ZNY 返回HTML格式的链接
|
||||
|
||||
link_to_category.short_description = _('category') #ZNY 设置自定义列显示名称
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs): #ZNY 重写获取表单方法
|
||||
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) #ZNY 调用父类方法
|
||||
form.base_fields['author'].queryset = get_user_model( #ZNY 限制作者字段只能选择超级用户
|
||||
).objects.filter(is_superuser=True)
|
||||
return form #ZNY 返回修改后的表单
|
||||
|
||||
def save_model(self, request, obj, form, change): #ZNY 重写保存模型方法
|
||||
super(ArticlelAdmin, self).save_model(request, obj, form, change) #ZNY 调用父类保存方法
|
||||
|
||||
def get_view_on_site_url(self, obj=None): #ZNY 重写获取站点查看URL方法
|
||||
if obj: #ZNY 如果有具体对象
|
||||
url = obj.get_full_url() #ZNY 获取文章的完整URL
|
||||
return url #ZNY 返回文章URL
|
||||
else: #ZNY 如果没有对象(列表页)
|
||||
from djangoblog.utils import get_current_site #ZNY 导入获取当前站点函数
|
||||
site = get_current_site().domain #ZNY 获取当前站点域名
|
||||
return site #ZNY 返回站点域名
|
||||
|
||||
|
||||
class TagAdmin(admin.ModelAdmin): #ZNY 标签模型管理类
|
||||
exclude = ('slug', 'last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段
|
||||
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin): #ZNY 分类模型管理类
|
||||
list_display = ('name', 'parent_category', 'index') #ZNY 设置列表页显示的字段
|
||||
exclude = ('slug', 'last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段
|
||||
|
||||
|
||||
class LinksAdmin(admin.ModelAdmin): #ZNY 链接模型管理类
|
||||
exclude = ('last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段
|
||||
|
||||
|
||||
class SideBarAdmin(admin.ModelAdmin): #ZNY 侧边栏模型管理类
|
||||
list_display = ('name', 'content', 'is_enable', 'sequence') #ZNY 设置列表页显示的字段
|
||||
exclude = ('last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段
|
||||
|
||||
|
||||
class BlogSettingsAdmin(admin.ModelAdmin): #ZNY 博客设置模型管理类
|
||||
pass #ZNY 使用默认管理配置
|
||||
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig #ZNY 导入Django应用配置基类
|
||||
|
||||
|
||||
class BlogConfig(AppConfig): #ZNY 定义博客应用的配置类
|
||||
name = 'blog' #ZNY 指定应用的Python路径为'blog'
|
||||
@ -1,42 +0,0 @@
|
||||
import logging #ZNY 导入日志模块
|
||||
import time #ZNY 导入时间模块
|
||||
|
||||
from ipware import get_client_ip #ZNY 导入获取客户端IP的工具
|
||||
from user_agents import parse #ZNY 导入解析用户代理的工具
|
||||
|
||||
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager #ZNY 导入Elasticsearch配置和性能文档管理器
|
||||
|
||||
logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器
|
||||
|
||||
|
||||
class OnlineMiddleware(object): #ZNY 定义在线中间件类
|
||||
def __init__(self, get_response=None): #ZNY 初始化方法
|
||||
self.get_response = get_response #ZNY 存储get_response函数
|
||||
super().__init__() #ZNY 调用父类初始化
|
||||
|
||||
def __call__(self, request): #ZNY 使实例可调用,处理请求
|
||||
''' page render time ''' #ZNY 页面渲染时间统计
|
||||
start_time = time.time() #ZNY 记录请求开始时间
|
||||
response = self.get_response(request) #ZNY 调用后续中间件和视图处理请求
|
||||
http_user_agent = request.META.get('HTTP_USER_AGENT', '') #ZNY 获取用户代理字符串
|
||||
ip, _ = get_client_ip(request) #ZNY 获取客户端IP地址
|
||||
user_agent = parse(http_user_agent) #ZNY 解析用户代理信息
|
||||
if not response.streaming: #ZNY 如果不是流式响应
|
||||
try:
|
||||
cast_time = time.time() - start_time #ZNY 计算总处理时间
|
||||
if ELASTICSEARCH_ENABLED: #ZNY 如果Elasticsearch已启用
|
||||
time_taken = round((cast_time) * 1000, 2) #ZNY 转换为毫秒并保留两位小数
|
||||
url = request.path #ZNY 获取请求路径
|
||||
from django.utils import timezone #ZNY 导入时区工具
|
||||
ElaspedTimeDocumentManager.create( #ZNY 创建性能记录文档
|
||||
url=url, #ZNY 请求URL
|
||||
time_taken=time_taken, #ZNY 耗时
|
||||
log_datetime=timezone.now(), #ZNY 当前时间
|
||||
useragent=user_agent, #ZNY 用户代理信息
|
||||
ip=ip) #ZNY IP地址
|
||||
response.content = response.content.replace( #ZNY 在响应内容中替换占位符
|
||||
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5])) #ZNY 将加载时间插入到指定位置
|
||||
except Exception as e: #ZNY 捕获异常
|
||||
logger.error("Error OnlineMiddleware: %s" % e) #ZNY 记录错误日志
|
||||
|
||||
return response #ZNY 返回响应对象
|
||||
@ -1,22 +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'), #ZNY 依赖于blog应用的初始迁移文件0001_initial
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='blogsettings', #ZNY 向BlogSettings模型添加字段
|
||||
name='global_footer', #ZNY 字段名称为global_footer
|
||||
field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), #ZNY 公共尾部字段,用于存储全站公共底部内容
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='blogsettings', #ZNY 向BlogSettings模型添加字段
|
||||
name='global_header', #ZNY 字段名称为global_header
|
||||
field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), #ZNY 公共头部字段,用于存储全站公共头部内容
|
||||
),
|
||||
]
|
||||
@ -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'), #ZNY 依赖于blog应用的第二个迁移文件0002
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='blogsettings', #ZNY 向BlogSettings模型添加新字段
|
||||
name='comment_need_review', #ZNY 字段名称为comment_need_review
|
||||
field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), #ZNY 评论审核开关字段,控制评论是否需要审核才能显示
|
||||
),
|
||||
]
|
||||
@ -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'), #ZNY 依赖于blog应用的第五个迁移文件0005
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='blogsettings', #ZNY 修改BlogSettings模型的选项配置
|
||||
options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, #ZNY 将BlogSettings模型的显示名称从中文改为英文
|
||||
),
|
||||
]
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.2.4 on 2025-11-20 20:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0006_alter_blogsettings_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='dislikes',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='不喜欢数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='likes',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='点赞数'),
|
||||
),
|
||||
]
|
||||
@ -1,14 +0,0 @@
|
||||
from haystack import indexes #ZNY 导入Haystack搜索索引模块
|
||||
|
||||
from blog.models import Article #ZNY 导入文章模型
|
||||
|
||||
|
||||
class ArticleIndex(indexes.SearchIndex, indexes.Indexable): #ZNY 定义文章搜索索引类,继承搜索索引和可索引接口
|
||||
text = indexes.CharField(document=True, use_template=True) #ZNY 定义主搜索字段,使用模板构建索引内容
|
||||
|
||||
def get_model(self): #ZNY 获取索引对应的模型方法
|
||||
return Article #ZNY 返回文章模型类
|
||||
|
||||
def index_queryset(self, using=None): #ZNY 定义索引查询集方法
|
||||
return self.get_model().objects.filter(status='p') #ZNY 只索引已发布状态的文章
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
from django.urls import path #ZNY 导入Django URL路径模块
|
||||
from django.views.decorators.cache import cache_page #ZNY 导入缓存页面装饰器
|
||||
from django.urls import path
|
||||
from . import views #ZNY 导入当前应用的视图模块
|
||||
|
||||
app_name = "blog" #ZNY 定义应用命名空间为blog
|
||||
urlpatterns = [ #ZNY 定义URL模式列表
|
||||
path( #ZNY 首页路由
|
||||
r'',
|
||||
views.IndexView.as_view(), #ZNY 使用类视图处理首页
|
||||
name='index'), #ZNY URL名称为index
|
||||
path( #ZNY 首页分页路由
|
||||
r'page/<int:page>/', #ZNY 带页码参数的URL
|
||||
views.IndexView.as_view(), #ZNY 使用相同的类视图处理分页
|
||||
name='index_page'), #ZNY URL名称为index_page
|
||||
path( #ZNY 文章详情页路由
|
||||
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html', #ZNY 包含年月日和文章ID的URL
|
||||
views.ArticleDetailView.as_view(), #ZNY 使用文章详情类视图
|
||||
name='detailbyid'), #ZNY URL名称为detailbyid
|
||||
path( #ZNY 分类详情页路由
|
||||
r'category/<slug:category_name>.html', #ZNY 包含分类名称的URL
|
||||
views.CategoryDetailView.as_view(), #ZNY 使用分类详情类视图
|
||||
name='category_detail'), #ZNY URL名称为category_detail
|
||||
path( #ZNY 分类分页路由
|
||||
r'category/<slug:category_name>/<int:page>.html', #ZNY 包含分类名称和页码的URL
|
||||
views.CategoryDetailView.as_view(), #ZNY 使用相同的类视图处理分页
|
||||
name='category_detail_page'), #ZNY URL名称为category_detail_page
|
||||
path( #ZNY 作者详情页路由
|
||||
r'author/<author_name>.html', #ZNY 包含作者名称的URL
|
||||
views.AuthorDetailView.as_view(), #ZNY 使用作者详情类视图
|
||||
name='author_detail'), #ZNY URL名称为author_detail
|
||||
path( #ZNY 作者分页路由
|
||||
r'author/<author_name>/<int:page>.html', #ZNY 包含作者名称和页码的URL
|
||||
views.AuthorDetailView.as_view(), #ZNY 使用相同的类视图处理分页
|
||||
name='author_detail_page'), #ZNY URL名称为author_detail_page
|
||||
path( #ZNY 标签详情页路由
|
||||
r'tag/<slug:tag_name>.html', #ZNY 包含标签名称的URL
|
||||
views.TagDetailView.as_view(), #ZNY 使用标签详情类视图
|
||||
name='tag_detail'), #ZNY URL名称为tag_detail
|
||||
path( #ZNY 标签分页路由
|
||||
r'tag/<slug:tag_name>/<int:page>.html', #ZNY 包含标签名称和页码的URL
|
||||
views.TagDetailView.as_view(), #ZNY 使用相同的类视图处理分页
|
||||
name='tag_detail_page'), #ZNY URL名称为tag_detail_page
|
||||
path( #ZNY 文章归档页路由
|
||||
'archives.html',
|
||||
cache_page( #ZNY 使用缓存装饰器缓存页面
|
||||
60 * 60)( #ZNY 缓存1小时
|
||||
views.ArchivesView.as_view()), #ZNY 使用归档视图
|
||||
name='archives'), #ZNY URL名称为archives
|
||||
path( #ZNY 友情链接页路由
|
||||
'links.html',
|
||||
views.LinkListView.as_view(), #ZNY 使用链接列表视图
|
||||
name='links'), #ZNY URL名称为links
|
||||
path( #ZNY 文件上传路由
|
||||
r'upload',
|
||||
views.fileupload, #ZNY 使用函数视图处理文件上传
|
||||
name='upload'), #ZNY URL名称为upload
|
||||
path( #ZNY 清理缓存路由
|
||||
r'clean',
|
||||
views.clean_cache_view, #ZNY 使用函数视图清理缓存
|
||||
name='clean'), #ZNY URL名称为clean
|
||||
# Zxy: 新增点赞和不喜欢 API 路由
|
||||
path('article/<int:article_id>/like/', views.like_article, name='like_article'),
|
||||
path('article/<int:article_id>/dislike/', views.dislike_article, name='dislike_article'),
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue