diff --git a/src/DjangoBlog/accounts/admin.py b/src/DjangoBlog/accounts/admin.py
index 29d162a..60e4118 100644
--- a/src/DjangoBlog/accounts/admin.py
+++ b/src/DjangoBlog/accounts/admin.py
@@ -9,15 +9,17 @@ from .models import BlogUser
class BlogUserCreationForm(forms.ModelForm):
+ # 密码输入字段1
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
+ # 密码确认字段2
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
- model = BlogUser
- fields = ('email',)
+ model = BlogUser # 指定模型为BlogUser
+ fields = ('email',) # 只包含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,28 +27,30 @@ 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:
- user.source = 'adminsite'
+ user.source = 'adminsite' # 设置用户来源为管理员站点
user.save()
return user
class BlogUserChangeForm(UserChangeForm):
class Meta:
- model = BlogUser
- fields = '__all__'
- field_classes = {'username': UsernameField}
+ model = BlogUser # 指定模型为BlogUser
+ fields = '__all__' # 包含所有字段
+ field_classes = {'username': UsernameField} # 指定username字段类型
def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs) # 调用父类初始化方法
class BlogUserAdmin(UserAdmin):
- form = BlogUserChangeForm
- add_form = BlogUserCreationForm
+ # 使用自定义的表单类
+ form = BlogUserChangeForm # 修改表单
+ add_form = BlogUserCreationForm # 添加表单
+ # 列表页显示的字段
list_display = (
'id',
'nickname',
@@ -55,6 +59,9 @@ class BlogUserAdmin(UserAdmin):
'last_login',
'date_joined',
'source')
+ # 列表页可点击的链接字段
list_display_links = ('id', 'username')
+ # 默认排序字段
ordering = ('-id',)
- search_fields = ('username', 'nickname', 'email')
+ # 搜索字段
+ search_fields = ('username', 'nickname', 'email')
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/apps.py b/src/DjangoBlog/accounts/apps.py
index 9b3fc5a..d6e8a46 100644
--- a/src/DjangoBlog/accounts/apps.py
+++ b/src/DjangoBlog/accounts/apps.py
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
- name = 'accounts'
+ name = 'accounts' # 指定应用的Python路径为'accounts'
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/forms.py b/src/DjangoBlog/accounts/forms.py
index fce4137..70289b9 100644
--- a/src/DjangoBlog/accounts/forms.py
+++ b/src/DjangoBlog/accounts/forms.py
@@ -11,8 +11,10 @@ from .models import BlogUser
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
+ # 设置用户名输入框的HTML属性
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
+ # 设置密码输入框的HTML属性
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
@@ -21,6 +23,7 @@ class RegisterForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
+ # 设置各字段输入框的HTML属性
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(
@@ -31,17 +34,19 @@ class RegisterForm(UserCreationForm):
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")
+ model = get_user_model() # 获取当前用户模型
+ fields = ("username", "email") # 表单包含的字段
class ForgetPasswordForm(forms.Form):
+ # 新密码字段
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
@@ -52,6 +57,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # 确认密码字段
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
@@ -62,6 +68,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # 邮箱字段
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
@@ -72,6 +79,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # 验证码字段
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
@@ -83,15 +91,18 @@ class ForgetPasswordForm(forms.Form):
)
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
@@ -101,6 +112,7 @@ class ForgetPasswordForm(forms.Form):
return user_email
def clean_code(self):
+ # 验证验证码是否正确
code = self.cleaned_data.get("code")
error = utils.verify(
email=self.cleaned_data.get("email"),
@@ -112,6 +124,7 @@ class ForgetPasswordForm(forms.Form):
class ForgetPasswordCodeForm(forms.Form):
+ # 忘记密码时的邮箱验证表单
email = forms.EmailField(
label=_('Email'),
- )
+ )
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/migrations/0001_initial.py b/src/DjangoBlog/accounts/migrations/0001_initial.py
index d2fbcab..5d48851 100644
--- a/src/DjangoBlog/accounts/migrations/0001_initial.py
+++ b/src/DjangoBlog/accounts/migrations/0001_initial.py
@@ -8,42 +8,60 @@ import django.utils.timezone
class Migration(migrations.Migration):
- initial = True
+ initial = True # 标记这是初始迁移
dependencies = [
- ('auth', '0012_alter_user_first_name_max_length'),
+ ('auth', '0012_alter_user_first_name_max_length'), # 依赖auth应用的迁移
]
operations = [
migrations.CreateModel(
- name='BlogUser',
+ name='BlogUser', # 创建自定义用户模型BlogUser
fields=[
+ # 主键ID,自增BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ # 密码字段,使用Django的密码哈希存储
('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')),
+ # 员工状态标志位,决定是否能登录admin后台
('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',
+ 'verbose_name': '用户', # 单数显示名称
+ 'verbose_name_plural': '用户', # 复数显示名称
+ 'ordering': ['-id'], # 默认按ID倒序排列
+ 'get_latest_by': 'id', # 指定获取最新记录的字段
},
managers=[
+ # 使用Django原生的UserManager管理用户对象
('objects', django.contrib.auth.models.UserManager()),
],
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
index 1a9f509..3a414b1 100644
--- a/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
+++ b/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
@@ -7,40 +7,47 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
- ('accounts', '0001_initial'),
+ ('accounts', '0001_initial'), # 依赖accounts应用的初始迁移
]
operations = [
+ # 修改BlogUser模型的元数据选项
migrations.AlterModelOptions(
name='bloguser',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
),
+ # 删除created_time字段
migrations.RemoveField(
model_name='bloguser',
name='created_time',
),
+ # 删除last_mod_time字段
migrations.RemoveField(
model_name='bloguser',
name='last_mod_time',
),
+ # 新增creation_time字段
migrations.AddField(
model_name='bloguser',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 新增last_modify_time字段
migrations.AddField(
model_name='bloguser',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
+ # 修改nickname字段的verbose_name
migrations.AlterField(
model_name='bloguser',
name='nickname',
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
),
+ # 修改source字段的verbose_name
migrations.AlterField(
model_name='bloguser',
name='source',
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/models.py b/src/DjangoBlog/accounts/models.py
index 3baddbb..a1f0e18 100644
--- a/src/DjangoBlog/accounts/models.py
+++ b/src/DjangoBlog/accounts/models.py
@@ -9,27 +9,34 @@ from djangoblog.utils import get_current_site
# Create your models here.
class BlogUser(AbstractUser):
+ # 昵称字段,最大长度100,可为空
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)
+ # 创建来源字段,最大长度100,可为空
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
+ # 获取用户详情页的URL
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
def __str__(self):
+ # 对象的字符串表示,返回邮箱
return self.email
def get_full_url(self):
+ # 获取完整的用户URL(包含域名)
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'
+ ordering = ['-id'] # 按ID倒序排列
+ verbose_name = _('user') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称(与单数相同)
+ get_latest_by = 'id' # 指定获取最新记录的字段
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/tests.py b/src/DjangoBlog/accounts/tests.py
index 6893411..3e872a6 100644
--- a/src/DjangoBlog/accounts/tests.py
+++ b/src/DjangoBlog/accounts/tests.py
@@ -13,35 +13,37 @@ from . import utils
class AccountTest(TestCase):
def setUp(self):
- self.client = Client()
- self.factory = RequestFactory()
+ # 测试初始化设置
+ self.client = Client() # 创建测试客户端
+ self.factory = RequestFactory() # 创建请求工厂
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
- )
- self.new_test = "xxx123--="
+ ) # 创建测试用户
+ self.new_test = "xxx123--=" # 新密码测试字符串
def test_validate_account(self):
+ # 测试账户验证功能
site = get_current_site().domain
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
- password="qwer!@#$ggg")
+ password="qwer!@#$ggg") # 创建超级用户
testuser = BlogUser.objects.get(username='liangliangyy1')
loginresult = self.client.login(
username='liangliangyy1',
- password='qwer!@#$ggg')
- self.assertEqual(loginresult, True)
- response = self.client.get('/admin/')
- self.assertEqual(response.status_code, 200)
+ password='qwer!@#$ggg') # 测试登录
+ self.assertEqual(loginresult, True) # 验证登录成功
+ response = self.client.get('/admin/') # 访问管理后台
+ self.assertEqual(response.status_code, 200) # 验证访问成功
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
- category.save()
+ category.save() # 创建测试分类
article = Article()
article.title = "nicetitleaaa"
@@ -50,45 +52,46 @@ class AccountTest(TestCase):
article.category = category
article.type = 'a'
article.status = 'p'
- article.save()
+ article.save() # 创建测试文章
- response = self.client.get(article.get_admin_url())
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(article.get_admin_url()) # 访问文章管理页面
+ self.assertEqual(response.status_code, 200) # 验证访问成功
def test_validate_register(self):
+ # 测试用户注册功能
self.assertEquals(
0, len(
BlogUser.objects.filter(
- email='user123@user.com')))
+ email='user123@user.com'))) # 验证用户不存在
response = self.client.post(reverse('account:register'), {
'username': 'user1233',
'email': 'user123@user.com',
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
- })
+ }) # 提交注册表单
self.assertEquals(
1, len(
BlogUser.objects.filter(
- email='user123@user.com')))
+ email='user123@user.com'))) # 验证用户创建成功
user = BlogUser.objects.filter(email='user123@user.com')[0]
- sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
+ sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) # 生成验证签名
path = reverse('accounts:result')
url = '{path}?type=validation&id={id}&sign={sign}'.format(
- path=path, id=user.id, sign=sign)
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
+ path=path, id=user.id, sign=sign) # 构建验证URL
+ response = self.client.get(url) # 访问验证页面
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- self.client.login(username='user1233', password='password123!q@wE#R$T')
+ self.client.login(username='user1233', password='password123!q@wE#R$T') # 登录用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
- user.is_superuser = True
- user.is_staff = True
+ user.is_superuser = True # 设置为超级用户
+ user.is_staff = True # 设置为员工
user.save()
- delete_sidebar_cache()
+ delete_sidebar_cache() # 清除侧边栏缓存
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
- category.save()
+ category.save() # 创建分类
article = Article()
article.category = category
@@ -98,110 +101,114 @@ class AccountTest(TestCase):
article.type = 'a'
article.status = 'p'
- article.save()
+ article.save() # 创建文章
- response = self.client.get(article.get_admin_url())
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(article.get_admin_url()) # 访问文章管理页面
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- response = self.client.get(reverse('account:logout'))
- self.assertIn(response.status_code, [301, 302, 200])
+ response = self.client.get(reverse('account:logout')) # 退出登录
+ self.assertIn(response.status_code, [301, 302, 200]) # 验证重定向
- response = self.client.get(article.get_admin_url())
- self.assertIn(response.status_code, [301, 302, 200])
+ response = self.client.get(article.get_admin_url()) # 再次访问管理页面
+ self.assertIn(response.status_code, [301, 302, 200]) # 验证被重定向(未登录)
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
- })
- self.assertIn(response.status_code, [301, 302, 200])
+ }) # 重新登录
+ self.assertIn(response.status_code, [301, 302, 200]) # 验证登录重定向
- response = self.client.get(article.get_admin_url())
- self.assertIn(response.status_code, [301, 302, 200])
+ response = self.client.get(article.get_admin_url()) # 再次访问管理页面
+ self.assertIn(response.status_code, [301, 302, 200]) # 验证访问成功
def test_verify_email_code(self):
+ # 测试邮箱验证码功能
to_email = "admin@admin.com"
- code = generate_code()
- utils.set_code(to_email, code)
- utils.send_verify_email(to_email, code)
+ code = generate_code() # 生成验证码
+ utils.set_code(to_email, code) # 设置验证码
+ utils.send_verify_email(to_email, code) # 发送验证邮件
- err = utils.verify("admin@admin.com", code)
- self.assertEqual(err, None)
+ err = utils.verify("admin@admin.com", code) # 验证正确邮箱和验证码
+ self.assertEqual(err, None) # 验证无错误
- err = utils.verify("admin@123.com", code)
- self.assertEqual(type(err), str)
+ err = utils.verify("admin@123.com", code) # 验证错误邮箱
+ self.assertEqual(type(err), str) # 验证返回错误信息
def test_forget_password_email_code_success(self):
+ # 测试忘记密码邮箱验证码成功情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
- )
+ ) # 提交忘记密码请求
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(resp.content.decode("utf-8"), "ok")
+ self.assertEqual(resp.status_code, 200) # 验证响应成功
+ self.assertEqual(resp.content.decode("utf-8"), "ok") # 验证返回内容
def test_forget_password_email_code_fail(self):
+ # 测试忘记密码邮箱验证码失败情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
- )
- self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
+ ) # 提交空数据
+ self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # 验证错误信息
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com")
- )
- self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
+ ) # 提交无效邮箱
+ self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") # 验证错误信息
def test_forget_password_email_success(self):
- code = generate_code()
- utils.set_code(self.blog_user.email, code)
+ # 测试忘记密码成功情况
+ code = generate_code() # 生成验证码
+ utils.set_code(self.blog_user.email, code) # 设置验证码
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code=code,
- )
+ ) # 准备重置密码数据
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
- )
- self.assertEqual(resp.status_code, 302)
+ ) # 提交重置密码请求
+ self.assertEqual(resp.status_code, 302) # 验证重定向响应
# 验证用户密码是否修改成功
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
- ).first() # type: BlogUser
- self.assertNotEqual(blog_user, None)
- self.assertEqual(blog_user.check_password(data["new_password1"]), True)
+ ).first() # type: BlogUser # 获取用户
+ self.assertNotEqual(blog_user, None) # 验证用户存在
+ self.assertEqual(blog_user.check_password(data["new_password1"]), True) # 验证密码修改成功
def test_forget_password_email_not_user(self):
+ # 测试忘记密码时用户不存在的情况
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com",
code="123456",
- )
+ ) # 准备不存在的用户数据
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
- )
-
- self.assertEqual(resp.status_code, 200)
+ ) # 提交重置密码请求
+ self.assertEqual(resp.status_code, 200) # 验证响应成功(但应该显示错误)
def test_forget_password_email_code_error(self):
- code = generate_code()
- utils.set_code(self.blog_user.email, code)
+ # 测试忘记密码验证码错误的情况
+ code = generate_code() # 生成验证码
+ utils.set_code(self.blog_user.email, code) # 设置正确验证码
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
- code="111111",
- )
+ code="111111", # 使用错误验证码
+ ) # 准备重置密码数据
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
- )
-
- self.assertEqual(resp.status_code, 200)
+ ) # 提交重置密码请求
+ self.assertEqual(resp.status_code, 200) # 验证响应成功(但应该显示验证码错误)
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/urls.py b/src/DjangoBlog/accounts/urls.py
index 107a801..2956136 100644
--- a/src/DjangoBlog/accounts/urls.py
+++ b/src/DjangoBlog/accounts/urls.py
@@ -4,25 +4,32 @@ from django.urls import re_path
from . import views
from .forms import LoginForm
-app_name = "accounts"
+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'),
- ]
+urlpatterns = [
+ # 登录URL,使用LoginForm作为认证表单,成功跳转到首页
+ re_path(r'^login/$',
+ views.LoginView.as_view(success_url='/'),
+ name='login',
+ kwargs={'authentication_form': LoginForm}),
+ # 注册URL,注册成功跳转到首页
+ re_path(r'^register/$',
+ views.RegisterView.as_view(success_url="/"),
+ name='register'),
+ # 退出登录URL
+ re_path(r'^logout/$',
+ views.LogoutView.as_view(),
+ name='logout'),
+ # 账户操作结果页面URL
+ path(r'account/result.html',
+ views.account_result,
+ name='result'),
+ # 忘记密码页面URL
+ re_path(r'^forget_password/$',
+ views.ForgetPasswordView.as_view(),
+ name='forget_password'),
+ # 获取忘记密码验证码URL
+ re_path(r'^forget_password_code/$',
+ views.ForgetPasswordEmailCode.as_view(),
+ name='forget_password_code'),
+]
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/user_login_backend.py b/src/DjangoBlog/accounts/user_login_backend.py
index 73cdca1..270d72e 100644
--- a/src/DjangoBlog/accounts/user_login_backend.py
+++ b/src/DjangoBlog/accounts/user_login_backend.py
@@ -8,19 +8,25 @@ class EmailOrUsernameModelBackend(ModelBackend):
"""
def authenticate(self, request, username=None, password=None, **kwargs):
+ # 判断输入是否包含@符号,决定使用邮箱还是用户名进行验证
if '@' in username:
- kwargs = {'email': username}
+ kwargs = {'email': username} # 如果包含@,按邮箱处理
else:
- kwargs = {'username': username}
+ kwargs = {'username': username} # 否则按用户名处理
try:
+ # 根据用户名或邮箱查找用户
user = get_user_model().objects.get(**kwargs)
+ # 验证密码是否正确
if user.check_password(password):
return user
except get_user_model().DoesNotExist:
+ # 用户不存在时返回None
return None
def get_user(self, username):
try:
+ # 根据用户ID获取用户对象
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
- return None
+ # 用户不存在时返回None
+ return None
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/utils.py b/src/DjangoBlog/accounts/utils.py
index 4b94bdf..5d31e35 100644
--- a/src/DjangoBlog/accounts/utils.py
+++ b/src/DjangoBlog/accounts/utils.py
@@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
from djangoblog.utils import send_email
-_code_ttl = timedelta(minutes=5)
+_code_ttl = timedelta(minutes=5) # 验证码有效期为5分钟
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
@@ -19,8 +19,8 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
"""
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)
+ "properly") % {'code': code} # 邮件内容模板,包含验证码和有效期提示
+ send_email([to_mail], subject, html_content) # 调用发送邮件函数
def verify(email: str, code: str) -> typing.Optional[str]:
@@ -34,16 +34,16 @@ def verify(email: str, code: str) -> typing.Optional[str]:
这里的错误处理不太合理,应该采用raise抛出
否测调用方也需要对error进行处理
"""
- cache_code = get_code(email)
- if cache_code != code:
- return gettext("Verification code 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)
+ cache.set(email, code, _code_ttl.seconds) # 将验证码存入缓存,设置过期时间为5分钟
def get_code(email: str) -> typing.Optional[str]:
"""获取code"""
- return cache.get(email)
+ return cache.get(email) # 从缓存获取指定邮箱的验证码
\ No newline at end of file
diff --git a/src/DjangoBlog/accounts/views.py b/src/DjangoBlog/accounts/views.py
index ae67aec..8973e4b 100644
--- a/src/DjangoBlog/accounts/views.py
+++ b/src/DjangoBlog/accounts/views.py
@@ -32,27 +32,27 @@ logger = logging.getLogger(__name__)
# Create your views here.
class RegisterView(FormView):
- form_class = RegisterForm
- template_name = 'account/registration_form.html'
+ form_class = RegisterForm # 使用注册表单
+ template_name = 'account/registration_form.html' # 注册模板
- @method_decorator(csrf_protect)
+ @method_decorator(csrf_protect) # CSRF保护装饰器
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)))
+ 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'
+ 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)
+ site=site, path=path, id=user.id, sign=sign) # 构建验证URL
content = """
请点击下面链接验证您的邮箱
@@ -63,142 +63,139 @@ class RegisterView(FormView):
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
- """.format(url=url)
+ """.format(url=url) # 邮件内容模板
send_email(
emailto=[
user.email,
],
title='验证您的电子邮箱',
- content=content)
+ content=content) # 发送验证邮件
url = reverse('accounts:result') + \
- '?type=register&id=' + str(user.id)
- return HttpResponseRedirect(url)
+ '?type=register&id=' + str(user.id) # 构建注册结果URL
+ return HttpResponseRedirect(url) # 重定向到结果页面
else:
return self.render_to_response({
- 'form': form
+ 'form': form # 表单验证失败,重新渲染表单
})
class LogoutView(RedirectView):
- url = '/login/'
+ url = '/login/' # 退出后重定向到登录页
- @method_decorator(never_cache)
+ @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)
+ 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)
+ 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) # CSRF保护
+ @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)
+ redirect_to = self.request.GET.get(self.redirect_field_name) # 获取重定向URL
if redirect_to is None:
- redirect_to = '/'
+ redirect_to = '/' # 默认重定向到首页
kwargs['redirect_to'] = redirect_to
- return super(LoginView, self).get_context_data(**kwargs)
+ return super(LoginView, self).get_context_data(**kwargs) # 返回上下文数据
def form_valid(self, form):
- form = AuthenticationForm(data=self.request.POST, request=self.request)
+ form = AuthenticationForm(data=self.request.POST, request=self.request) # 使用Django认证表单
if form.is_valid():
- delete_sidebar_cache()
+ 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('/')
+ 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) # 调用父类form_valid方法
else:
return self.render_to_response({
- 'form': form
+ 'form': form # 表单验证失败,重新渲染表单
})
def get_success_url(self):
-
- redirect_to = self.request.POST.get(self.redirect_field_name)
+ redirect_to = self.request.POST.get(self.redirect_field_name) # 从POST获取重定向URL
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
+ self.request.get_host()]): # 验证URL安全性
+ redirect_to = self.success_url # 使用默认成功URL
+ return redirect_to # 返回重定向URL
def account_result(request):
- type = request.GET.get('type')
- id = request.GET.get('id')
+ type = request.GET.get('type') # 获取操作类型
+ id = request.GET.get('id') # 获取用户ID
- user = get_object_or_404(get_user_model(), id=id)
+ user = get_object_or_404(get_user_model(), id=id) # 获取用户对象,不存在返回404
logger.info(type)
- if user.is_active:
+ if user.is_active: # 如果用户已激活,重定向到首页
return HttpResponseRedirect('/')
- if type and type in ['register', 'validation']:
+ 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()
+ c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) # 计算验证签名
+ sign = request.GET.get('sign') # 获取请求中的签名
+ if sign != c_sign: # 验证签名是否匹配
+ return HttpResponseForbidden() # 签名不匹配返回403
+ user.is_active = True # 激活用户
+ user.save() # 保存用户状态
content = '''
恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。
- '''
+ ''' # 验证成功提示
title = '验证成功'
return render(request, 'account/result.html', {
'title': title,
- 'content': content
+ 'content': content # 渲染结果页面
})
else:
- return HttpResponseRedirect('/')
+ return HttpResponseRedirect('/') # 无效类型重定向到首页
class ForgetPasswordView(FormView):
- form_class = ForgetPasswordForm
- template_name = 'account/forget_password.html'
+ 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/')
+ 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})
+ 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"]
+ 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)
+ code = generate_code() # 生成验证码
+ utils.send_verify_email(to_email, code) # 发送验证邮件
+ utils.set_code(to_email, code) # 保存验证码到缓存
- return HttpResponse("ok")
+ return HttpResponse("ok") # 返回成功响应
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/admin.py b/src/DjangoBlog/blog/admin.py
index 69d7f8e..7c2ab60 100644
--- a/src/DjangoBlog/blog/admin.py
+++ b/src/DjangoBlog/blog/admin.py
@@ -10,105 +10,113 @@ from .models import Article, Category, Tag, Links, SideBar, BlogSettings
class ArticleForm(forms.ModelForm):
- # body = forms.CharField(widget=AdminPagedownWidget())
+ # body = forms.CharField(widget=AdminPagedownWidget()) # 注释掉的Markdown编辑器部件
class Meta:
- model = Article
- fields = '__all__'
+ model = Article # 指定模型为Article
+ fields = '__all__' # 包含所有字段
def makr_article_publish(modeladmin, request, queryset):
- queryset.update(status='p')
+ # 发布选中文章的动作
+ queryset.update(status='p') # 将状态设置为已发布
def draft_article(modeladmin, request, queryset):
- queryset.update(status='d')
+ # 将选中文章设为草稿的动作
+ queryset.update(status='d') # 将状态设置为草稿
def close_article_commentstatus(modeladmin, request, queryset):
- queryset.update(comment_status='c')
+ # 关闭选中文章评论的动作
+ queryset.update(comment_status='c') # 将评论状态设置为关闭
def open_article_commentstatus(modeladmin, request, queryset):
- queryset.update(comment_status='o')
+ # 打开选中文章评论的动作
+ queryset.update(comment_status='o') # 将评论状态设置为打开
-makr_article_publish.short_description = _('Publish selected articles')
-draft_article.short_description = _('Draft selected articles')
-close_article_commentstatus.short_description = _('Close article comments')
-open_article_commentstatus.short_description = _('Open article comments')
+makr_article_publish.short_description = _('Publish selected articles') # 动作显示名称
+draft_article.short_description = _('Draft selected articles') # 动作显示名称
+close_article_commentstatus.short_description = _('Close article comments') # 动作显示名称
+open_article_commentstatus.short_description = _('Open article comments') # 动作显示名称
class ArticlelAdmin(admin.ModelAdmin):
- list_per_page = 20
- search_fields = ('body', 'title')
- form = ArticleForm
+ list_per_page = 20 # 每页显示20条记录
+ search_fields = ('body', 'title') # 搜索字段
+ form = ArticleForm # 使用自定义表单
list_display = (
'id',
'title',
'author',
- 'link_to_category',
+ 'link_to_category', # 自定义链接字段
'creation_time',
'views',
'status',
'type',
- 'article_order')
- list_display_links = ('id', 'title')
- list_filter = ('status', 'type', 'category')
- date_hierarchy = 'creation_time'
- filter_horizontal = ('tags',)
- exclude = ('creation_time', 'last_modify_time')
- view_on_site = True
+ 'article_order') # 列表页显示的字段
+ list_display_links = ('id', 'title') # 可点击链接的字段
+ list_filter = ('status', 'type', 'category') # 右侧过滤器
+ date_hierarchy = 'creation_time' # 日期层级导航
+ filter_horizontal = ('tags',) # 水平多对多选择器
+ exclude = ('creation_time', 'last_modify_time') # 排除的字段
+ view_on_site = True # 启用"在站点查看"功能
actions = [
makr_article_publish,
draft_article,
close_article_commentstatus,
- open_article_commentstatus]
- raw_id_fields = ('author', 'category',)
+ open_article_commentstatus] # 管理员动作列表
+ raw_id_fields = ('author', 'category',) # 使用原始ID字段(外键搜索)
def link_to_category(self, obj):
- info = (obj.category._meta.app_label, obj.category._meta.model_name)
- link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
- return format_html(u'%s' % (link, obj.category.name))
+ # 创建分类链接的自定义方法
+ info = (obj.category._meta.app_label, obj.category._meta.model_name) # 获取分类模型信息
+ link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) # 生成分类编辑链接
+ return format_html(u'%s' % (link, obj.category.name)) # 返回HTML链接
- link_to_category.short_description = _('category')
+ link_to_category.short_description = _('category') # 字段显示名称
def get_form(self, request, obj=None, **kwargs):
- form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
+ # 自定义表单获取方法
+ form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) # 调用父类方法
form.base_fields['author'].queryset = get_user_model(
- ).objects.filter(is_superuser=True)
+ ).objects.filter(is_superuser=True) # 限制作者只能选择超级用户
return form
def save_model(self, request, obj, form, change):
- super(ArticlelAdmin, self).save_model(request, obj, form, change)
+ # 保存模型方法
+ super(ArticlelAdmin, self).save_model(request, obj, form, change) # 调用父类保存方法
def get_view_on_site_url(self, obj=None):
+ # 获取"在站点查看"链接的方法
if obj:
- url = obj.get_full_url()
+ url = obj.get_full_url() # 获取文章完整URL
return url
else:
from djangoblog.utils import get_current_site
- site = get_current_site().domain
+ site = get_current_site().domain # 获取当前站点域名
return site
class TagAdmin(admin.ModelAdmin):
- exclude = ('slug', 'last_mod_time', 'creation_time')
+ exclude = ('slug', 'last_modify_time', 'creation_time') # 排除自动生成的字段
class CategoryAdmin(admin.ModelAdmin):
- list_display = ('name', 'parent_category', 'index')
- exclude = ('slug', 'last_mod_time', 'creation_time')
+ list_display = ('name', 'parent_category', 'index') # 列表页显示字段
+ exclude = ('slug', 'last_modify_time', 'creation_time') # 排除自动生成的字段
class LinksAdmin(admin.ModelAdmin):
- exclude = ('last_mod_time', 'creation_time')
+ exclude = ('last_modify_time', 'creation_time') # 排除时间字段
class SideBarAdmin(admin.ModelAdmin):
- list_display = ('name', 'content', 'is_enable', 'sequence')
- exclude = ('last_mod_time', 'creation_time')
+ list_display = ('name', 'content', 'is_enable', 'sequence') # 列表页显示字段
+ exclude = ('last_modify_time', 'creation_time') # 排除时间字段
class BlogSettingsAdmin(admin.ModelAdmin):
- pass
+ pass # 使用默认的ModelAdmin配置
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/apps.py b/src/DjangoBlog/blog/apps.py
index 7930587..e363990 100644
--- a/src/DjangoBlog/blog/apps.py
+++ b/src/DjangoBlog/blog/apps.py
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class BlogConfig(AppConfig):
- name = 'blog'
+ name = 'blog' # 指定应用的Python路径为'blog'
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/context_processors.py b/src/DjangoBlog/blog/context_processors.py
index 73e3088..fe376ae 100644
--- a/src/DjangoBlog/blog/context_processors.py
+++ b/src/DjangoBlog/blog/context_processors.py
@@ -9,35 +9,36 @@ logger = logging.getLogger(__name__)
def seo_processor(requests):
- key = 'seo_processor'
- value = cache.get(key)
+ # SEO上下文处理器,为模板提供SEO相关变量
+ key = 'seo_processor' # 缓存键
+ value = cache.get(key) # 尝试从缓存获取数据
if value:
- return value
+ return value # 如果缓存存在,直接返回
else:
- logger.info('set processor cache.')
- setting = get_blog_setting()
+ 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(),
+ 'SITE_NAME': setting.site_name, # 网站名称
+ 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, # 是否显示Google广告
+ 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, # Google广告代码
+ 'SITE_SEO_DESCRIPTION': setting.site_seo_description, # 网站SEO描述
+ 'SITE_DESCRIPTION': setting.site_description, # 网站描述
+ 'SITE_KEYWORDS': setting.site_keywords, # 网站关键词
+ 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', # 网站基础URL
+ '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,
+ 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
+ cache.set(key, value, 60 * 60 * 10) # 设置缓存,有效期10小时
+ return value # 返回上下文数据
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/documents.py b/src/DjangoBlog/blog/documents.py
index 0f1db7b..cf26d36 100644
--- a/src/DjangoBlog/blog/documents.py
+++ b/src/DjangoBlog/blog/documents.py
@@ -1,5 +1,3 @@
-import time
-
import elasticsearch.client
from django.conf import settings
from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean
@@ -7,19 +5,19 @@ from elasticsearch_dsl.connections import connections
from blog.models import Article
-ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
+ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') # 检查是否启用Elasticsearch
if ELASTICSEARCH_ENABLED:
connections.create_connection(
- hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
+ hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) # 创建Elasticsearch连接
from elasticsearch import Elasticsearch
- es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
+ es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) # Elasticsearch客户端实例
from elasticsearch.client import IngestClient
- c = IngestClient(es)
+ c = IngestClient(es) # Ingest管道客户端
try:
- c.get_pipeline('geoip')
+ c.get_pipeline('geoip') # 尝试获取geoip管道
except elasticsearch.exceptions.NotFoundError:
c.put_pipeline('geoip', body='''{
"description" : "Add geoip info",
@@ -30,158 +28,172 @@ if ELASTICSEARCH_ENABLED:
}
}
]
- }''')
+ }''') # 创建geoip处理管道
class GeoIp(InnerDoc):
- continent_name = Keyword()
- country_iso_code = Keyword()
- country_name = Keyword()
- location = GeoPoint()
+ # GeoIP地理位置信息内嵌文档
+ continent_name = Keyword() # 大洲名称
+ country_iso_code = Keyword() # 国家ISO代码
+ country_name = Keyword() # 国家名称
+ location = GeoPoint() # 地理位置坐标
class UserAgentBrowser(InnerDoc):
- Family = Keyword()
- Version = Keyword()
+ # 用户代理浏览器信息
+ Family = Keyword() # 浏览器家族
+ Version = Keyword() # 浏览器版本
class UserAgentOS(UserAgentBrowser):
+ # 用户代理操作系统信息
pass
class UserAgentDevice(InnerDoc):
- Family = Keyword()
- Brand = Keyword()
- Model = Keyword()
+ # 用户代理设备信息
+ 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()
+ # 完整的用户代理信息
+ 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)
+ # 响应时间文档,用于性能监控
+ url = Keyword() # 请求URL
+ time_taken = Long() # 耗时(毫秒)
+ log_datetime = Date() # 日志时间
+ ip = Keyword() # IP地址
+ geoip = Object(GeoIp, required=False) # GeoIP地理位置信息
+ useragent = Object(UserAgent, required=False) # 用户代理信息
class Index:
- name = 'performance'
+ name = 'performance' # 索引名称
settings = {
- "number_of_shards": 1,
- "number_of_replicas": 0
+ "number_of_shards": 1, # 分片数量
+ "number_of_replicas": 0 # 副本数量
}
class Meta:
- doc_type = 'ElapsedTime'
+ 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")
+ res = client.indices.exists(index="performance") # 检查索引是否存在
if not res:
- ElapsedTimeDocument.init()
+ 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])
+ es.indices.delete(index='performance', ignore=[400, 404]) # 忽略404错误
@staticmethod
def create(url, time_taken, log_datetime, useragent, ip):
- ElaspedTimeDocumentManager.build_index()
+ # 创建响应时间文档记录
+ ElaspedTimeDocumentManager.build_index() # 确保索引存在
ua = UserAgent()
ua.browser = UserAgentBrowser()
- ua.browser.Family = useragent.browser.family
- ua.browser.Version = useragent.browser.version_string
+ 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.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
+ 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))
+ 1000)) # 使用时间戳作为文档ID
},
url=url,
time_taken=time_taken,
log_datetime=log_datetime,
useragent=ua, ip=ip)
- doc.save(pipeline="geoip")
+ doc.save(pipeline="geoip") # 保存文档并使用geoip管道处理
class ArticleDocument(Document):
- body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
- title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
+ # 文章文档,用于全文搜索
+ body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') # 正文,使用IK分词器
+ title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') # 标题,使用IK分词器
author = Object(properties={
- 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
+ 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 作者昵称
+ 'id': Integer() # 作者ID
})
category = Object(properties={
- 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
+ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 分类名称
+ 'id': Integer() # 分类ID
})
tags = Object(properties={
- 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
+ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 标签名称
+ 'id': Integer() # 标签ID
})
- pub_time = Date()
- status = Text()
- comment_status = Text()
- type = Text()
- views = Integer()
- article_order = Integer()
+ pub_time = Date() # 发布时间
+ status = Text() # 文章状态
+ comment_status = Text() # 评论状态
+ type = Text() # 文章类型
+ views = Integer() # 浏览量
+ article_order = Integer() # 文章排序
class Index:
- name = 'blog'
+ name = 'blog' # 索引名称
settings = {
- "number_of_shards": 1,
- "number_of_replicas": 0
+ "number_of_shards": 1, # 分片数量
+ "number_of_replicas": 0 # 副本数量
}
class Meta:
- doc_type = 'Article'
+ doc_type = 'Article' # 文档类型
class ArticleDocumentManager():
+ # 文章文档管理器
def __init__(self):
- self.create_index()
+ self.create_index() # 初始化时创建索引
def create_index(self):
- ArticleDocument.init()
+ 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])
+ es.indices.delete(index='blog', ignore=[400, 404]) # 忽略404错误
def convert_to_doc(self, articles):
+ # 将文章模型转换为Elasticsearch文档
return [
ArticleDocument(
meta={
- 'id': article.id},
+ 'id': article.id}, # 使用文章ID作为文档ID
body=article.body,
title=article.title,
author={
@@ -193,7 +205,7 @@ class ArticleDocumentManager():
tags=[
{
'name': t.name,
- 'id': t.id} for t in article.tags.all()],
+ 'id': t.id} for t in article.tags.all()], # 转换标签列表
pub_time=article.pub_time,
status=article.status,
comment_status=article.comment_status,
@@ -202,12 +214,14 @@ class ArticleDocumentManager():
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)
+ # 重建索引
+ ArticleDocument.init() # 重新初始化索引
+ articles = articles if articles else Article.objects.all() # 获取所有文章或指定文章
+ docs = self.convert_to_doc(articles) # 转换为文档
for doc in docs:
- doc.save()
+ doc.save() # 保存所有文档
def update_docs(self, docs):
+ # 更新文档
for doc in docs:
- doc.save()
+ doc.save() # 保存更新的文档
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/forms.py b/src/DjangoBlog/blog/forms.py
index 715be76..6d6f5c9 100644
--- a/src/DjangoBlog/blog/forms.py
+++ b/src/DjangoBlog/blog/forms.py
@@ -7,13 +7,14 @@ logger = logging.getLogger(__name__)
class BlogSearchForm(SearchForm):
- querydata = forms.CharField(required=True)
+ querydata = forms.CharField(required=True) # 搜索查询字段,必须填写
def search(self):
- datas = super(BlogSearchForm, self).search()
- if not self.is_valid():
- return self.no_query_found()
+ # 重写搜索方法
+ 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
+ logger.info(self.cleaned_data['querydata']) # 记录搜索查询日志
+ return datas # 返回搜索结果
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/build_index.py b/src/DjangoBlog/blog/management/commands/build_index.py
index 3c4acd7..4b57188 100644
--- a/src/DjangoBlog/blog/management/commands/build_index.py
+++ b/src/DjangoBlog/blog/management/commands/build_index.py
@@ -6,13 +6,14 @@ from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedT
# TODO 参数化
class Command(BaseCommand):
- help = 'build search index'
+ 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()
+ # 处理命令的主方法
+ if ELASTICSEARCH_ENABLED: # 检查Elasticsearch是否启用
+ ElaspedTimeDocumentManager.build_index() # 构建耗时文档索引
+ manager = ElapsedTimeDocument() # 创建耗时文档管理器实例
+ manager.init() # 初始化耗时文档
+ manager = ArticleDocumentManager() # 创建文章文档管理器实例
+ manager.delete_index() # 删除现有文章索引
+ manager.rebuild() # 重新构建文章索引
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/build_search_words.py b/src/DjangoBlog/blog/management/commands/build_search_words.py
index cfe7e0d..f9d1e99 100644
--- a/src/DjangoBlog/blog/management/commands/build_search_words.py
+++ b/src/DjangoBlog/blog/management/commands/build_search_words.py
@@ -5,9 +5,12 @@ from blog.models import Tag, Category
# TODO 参数化
class Command(BaseCommand):
- help = 'build search words'
+ 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))
+ # 将去重后的数据按行打印输出
+ print('\n'.join(datas))
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/clear_cache.py b/src/DjangoBlog/blog/management/commands/clear_cache.py
index 0d66172..09f8c93 100644
--- a/src/DjangoBlog/blog/management/commands/clear_cache.py
+++ b/src/DjangoBlog/blog/management/commands/clear_cache.py
@@ -4,8 +4,9 @@ from djangoblog.utils import cache
class Command(BaseCommand):
- help = 'clear the whole cache'
+ help = 'clear the whole cache' # 命令帮助信息:清除整个缓存
def handle(self, *args, **options):
- cache.clear()
- self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
+ # 处理命令的主方法
+ cache.clear() # 清除所有缓存
+ self.stdout.write(self.style.SUCCESS('Cleared cache\n')) # 输出成功信息
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/create_testdata.py b/src/DjangoBlog/blog/management/commands/create_testdata.py
index 675d2ba..7c79b2a 100644
--- a/src/DjangoBlog/blog/management/commands/create_testdata.py
+++ b/src/DjangoBlog/blog/management/commands/create_testdata.py
@@ -6,35 +6,44 @@ from blog.models import Article, Tag, Category
class Command(BaseCommand):
- help = 'create test datas'
+ 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()
+ 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()
+ article.save() # 保存文章
from djangoblog.utils import cache
- cache.clear()
- self.stdout.write(self.style.SUCCESS('created test datas \n'))
+ cache.clear() # 清除缓存
+ self.stdout.write(self.style.SUCCESS('created test datas \n')) # 输出成功信息
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/ping_baidu.py b/src/DjangoBlog/blog/management/commands/ping_baidu.py
index 2c7fbdd..6bf82fb 100644
--- a/src/DjangoBlog/blog/management/commands/ping_baidu.py
+++ b/src/DjangoBlog/blog/management/commands/ping_baidu.py
@@ -4,47 +4,52 @@ 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
+site = get_current_site().domain # 获取当前站点域名
class Command(BaseCommand):
- help = 'notify baidu url'
+ help = 'notify baidu url' # 命令帮助信息:通知百度URL
def add_arguments(self, parser):
+ # 添加命令行参数
parser.add_argument(
- 'data_type',
- type=str,
+ 'data_type', # 参数名
+ type=str, # 参数类型
choices=[
'all',
'article',
'tag',
- 'category'],
- help='article : all article,tag : all tag,category: all category,all: All of these')
+ 'category'], # 参数可选值
+ help='article : all article,tag : all tag,category: all category,all: All of these') # 参数帮助信息
def get_full_url(self, path):
+ # 根据相对路径构建完整URL
url = "https://{site}{path}".format(site=site, path=path)
return url
def handle(self, *args, **options):
- type = options['data_type']
- self.stdout.write('start get %s' % type)
+ type = options['data_type'] # 获取数据类型参数
+ self.stdout.write('start get %s' % type) # 输出开始信息
- urls = []
+ urls = [] # 初始化URL列表
+ # 根据类型收集文章URL
if type == 'article' or type == 'all':
- for article in Article.objects.filter(status='p'):
- urls.append(article.get_full_url())
+ for article in Article.objects.filter(status='p'): # 只获取已发布的文章
+ urls.append(article.get_full_url()) # 添加文章完整URL
+ # 根据类型收集标签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))
+ url = tag.get_absolute_url() # 获取标签相对URL
+ urls.append(self.get_full_url(url)) # 添加标签完整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))
+ url = category.get_absolute_url() # 获取分类相对URL
+ urls.append(self.get_full_url(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'))
+ len(urls))) # 输出开始通知信息
+ SpiderNotify.baidu_notify(urls) # 调用百度站长平台URL推送
+ self.stdout.write(self.style.SUCCESS('finish notify')) # 输出完成信息
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog/blog/management/commands/sync_user_avatar.py
index d0f4612..22608ae 100644
--- a/src/DjangoBlog/blog/management/commands/sync_user_avatar.py
+++ b/src/DjangoBlog/blog/management/commands/sync_user_avatar.py
@@ -8,40 +8,41 @@ from oauth.oauthmanager import get_manager_by_type
class Command(BaseCommand):
- help = 'sync user avatar'
+ help = 'sync user avatar' # 命令帮助信息:同步用户头像
def test_picture(self, url):
+ # 测试图片URL是否可访问
try:
- if requests.get(url, timeout=2).status_code == 200:
- return True
+ if requests.get(url, timeout=2).status_code == 200: # 发送HTTP请求测试图片
+ return True # 图片可访问返回True
except:
- pass
+ pass # 发生异常时静默处理
def handle(self, *args, **options):
- static_url = static("../")
- users = OAuthUser.objects.all()
- self.stdout.write(f'开始同步{len(users)}个用户头像')
+ static_url = static("../") # 获取静态文件基础URL
+ users = OAuthUser.objects.all() # 获取所有OAuth用户
+ self.stdout.write(f'开始同步{len(users)}个用户头像') # 输出开始同步信息
for u in users:
- self.stdout.write(f'开始同步:{u.nickname}')
- url = u.picture
+ self.stdout.write(f'开始同步:{u.nickname}') # 输出当前同步的用户
+ url = u.picture # 获取用户当前头像URL
if url:
- if url.startswith(static_url):
- if self.test_picture(url):
- continue
+ if url.startswith(static_url): # 检查是否已经是静态文件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)
+ if u.metadata: # 如果有用户元数据
+ manage = get_manager_by_type(u.type) # 根据OAuth类型获取管理器
+ url = manage.get_picture(u.metadata) # 从OAuth平台获取头像URL
+ url = save_user_avatar(url) # 保存头像并返回新URL
else:
- url = static('blog/img/avatar.png')
+ url = static('blog/img/avatar.png') # 使用默认头像
else:
- url = save_user_avatar(url)
+ url = save_user_avatar(url) # 保存外部头像并返回新URL
else:
- url = static('blog/img/avatar.png')
+ url = static('blog/img/avatar.png') # 没有头像时使用默认头像
if url:
self.stdout.write(
- f'结束同步:{u.nickname}.url:{url}')
- u.picture = url
- u.save()
- self.stdout.write('结束同步')
+ f'结束同步:{u.nickname}.url:{url}') # 输出同步完成信息
+ u.picture = url # 更新用户头像URL
+ u.save() # 保存用户信息
+ self.stdout.write('结束同步') # 输出同步结束信息
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/middleware.py b/src/DjangoBlog/blog/middleware.py
index 94dd70c..58a07e2 100644
--- a/src/DjangoBlog/blog/middleware.py
+++ b/src/DjangoBlog/blog/middleware.py
@@ -11,32 +11,32 @@ logger = logging.getLogger(__name__)
class OnlineMiddleware(object):
def __init__(self, get_response=None):
- self.get_response = get_response
+ 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:
+ ''' 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) # 获取客户端IP
+ 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
+ cast_time = time.time() - start_time # 计算耗时
+ if ELASTICSEARCH_ENABLED: # 如果启用了Elasticsearch
+ 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(),
+ log_datetime=timezone.now(), # 当前时间
useragent=user_agent,
- ip=ip)
+ ip=ip) # 创建性能监控记录
response.content = response.content.replace(
- b'', str.encode(str(cast_time)[:5]))
+ b'', str.encode(str(cast_time)[:5])) # 替换加载时间占位符
except Exception as e:
- logger.error("Error OnlineMiddleware: %s" % e)
+ logger.error("Error OnlineMiddleware: %s" % e) # 记录错误日志
- return response
+ return response # 返回响应
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0001_initial.py b/src/DjangoBlog/blog/migrations/0001_initial.py
index 3d391b6..1e33130 100644
--- a/src/DjangoBlog/blog/migrations/0001_initial.py
+++ b/src/DjangoBlog/blog/migrations/0001_initial.py
@@ -9,13 +9,14 @@ import mdeditor.fields
class Migration(migrations.Migration):
- initial = True
+ initial = True # 标记为初始迁移
dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型
]
operations = [
+ # 创建网站配置表
migrations.CreateModel(
name='BlogSettings',
fields=[
@@ -37,10 +38,11 @@ class Migration(migrations.Migration):
('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')),
],
options={
- 'verbose_name': '网站配置',
- 'verbose_name_plural': '网站配置',
+ 'verbose_name': '网站配置', # 单数显示名称
+ 'verbose_name_plural': '网站配置', # 复数显示名称
},
),
+ # 创建友情链接表
migrations.CreateModel(
name='Links',
fields=[
@@ -56,9 +58,10 @@ class Migration(migrations.Migration):
options={
'verbose_name': '友情链接',
'verbose_name_plural': '友情链接',
- 'ordering': ['sequence'],
+ 'ordering': ['sequence'], # 按排序字段升序排列
},
),
+ # 创建侧边栏表
migrations.CreateModel(
name='SideBar',
fields=[
@@ -73,49 +76,52 @@ class Migration(migrations.Migration):
options={
'verbose_name': '侧边栏',
'verbose_name_plural': '侧边栏',
- 'ordering': ['sequence'],
+ 'ordering': ['sequence'], # 按排序字段升序排列
},
),
+ # 创建标签表
migrations.CreateModel(
name='Tag',
fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('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)),
+ ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), # URL友好标识
],
options={
'verbose_name': '标签',
'verbose_name_plural': '标签',
- 'ordering': ['name'],
+ 'ordering': ['name'], # 按名称升序排列
},
),
+ # 创建分类表
migrations.CreateModel(
name='Category',
fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('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)),
+ ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), # URL友好标识
('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='父级分类')),
+ ('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'],
+ 'ordering': ['-index'], # 按权重倒序排列
},
),
+ # 创建文章表
migrations.CreateModel(
name='Article',
fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('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='正文')),
+ ('body', mdeditor.fields.MDTextField(verbose_name='正文')), # Markdown编辑器字段
('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='评论状态')),
@@ -123,15 +129,15 @@ class Migration(migrations.Migration):
('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='标签集合')),
+ ('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',
+ 'ordering': ['-article_order', '-pub_time'], # 按文章排序和发布时间倒序排列
+ 'get_latest_by': 'id', # 指定获取最新记录的字段
},
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py
index adbaa36..c943792 100644
--- a/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py
+++ b/src/DjangoBlog/blog/migrations/0002_blogsettings_global_footer_and_more.py
@@ -6,18 +6,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('blog', '0001_initial'),
+ ('blog', '0001_initial'), # 依赖blog应用的初始迁移
]
operations = [
+ # 向BlogSettings模型添加global_footer字段
migrations.AddField(
model_name='blogsettings',
name='global_footer',
field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'),
),
+ # 向BlogSettings模型添加global_header字段
migrations.AddField(
model_name='blogsettings',
name='global_header',
field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py
index e9f5502..9830c6b 100644
--- a/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py
+++ b/src/DjangoBlog/blog/migrations/0003_blogsettings_comment_need_review.py
@@ -5,13 +5,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('blog', '0002_blogsettings_global_footer_and_more'),
+ ('blog', '0002_blogsettings_global_footer_and_more'), # 依赖blog应用的第二个迁移
]
operations = [
+ # 向BlogSettings模型添加comment_need_review字段
migrations.AddField(
model_name='blogsettings',
name='comment_need_review',
field=models.BooleanField(default=False, verbose_name='评论是否需要审核'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
index ceb1398..f33876d 100644
--- a/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
+++ b/src/DjangoBlog/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
@@ -5,23 +5,26 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
- ('blog', '0003_blogsettings_comment_need_review'),
+ ('blog', '0003_blogsettings_comment_need_review'), # 依赖blog应用的第三个迁移
]
operations = [
+ # 重命名字段:analyticscode -> analytics_code
migrations.RenameField(
model_name='blogsettings',
old_name='analyticscode',
new_name='analytics_code',
),
+ # 重命名字段:beiancode -> beian_code
migrations.RenameField(
model_name='blogsettings',
old_name='beiancode',
new_name='beian_code',
),
+ # 重命名字段:sitename -> site_name
migrations.RenameField(
model_name='blogsettings',
old_name='sitename',
new_name='site_name',
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
index d08e853..be2274d 100644
--- a/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
+++ b/src/DjangoBlog/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
@@ -11,290 +11,349 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
+ ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), # 依赖blog应用的第四个迁移
]
operations = [
+ # 修改Article模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='article',
options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
),
+ # 修改Category模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='category',
options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
),
+ # 修改Links模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='links',
options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
),
+ # 修改Sidebar模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='sidebar',
options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
),
+ # 修改Tag模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='tag',
options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
),
+ # 删除Article模型的created_time字段
migrations.RemoveField(
model_name='article',
name='created_time',
),
+ # 删除Article模型的last_mod_time字段
migrations.RemoveField(
model_name='article',
name='last_mod_time',
),
+ # 删除Category模型的created_time字段
migrations.RemoveField(
model_name='category',
name='created_time',
),
+ # 删除Category模型的last_mod_time字段
migrations.RemoveField(
model_name='category',
name='last_mod_time',
),
+ # 删除Links模型的created_time字段
migrations.RemoveField(
model_name='links',
name='created_time',
),
+ # 删除Sidebar模型的created_time字段
migrations.RemoveField(
model_name='sidebar',
name='created_time',
),
+ # 删除Tag模型的created_time字段
migrations.RemoveField(
model_name='tag',
name='created_time',
),
+ # 删除Tag模型的last_mod_time字段
migrations.RemoveField(
model_name='tag',
name='last_mod_time',
),
+ # 向Article模型添加creation_time字段
migrations.AddField(
model_name='article',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 向Article模型添加last_modify_time字段
migrations.AddField(
model_name='article',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
+ # 向Category模型添加creation_time字段
migrations.AddField(
model_name='category',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 向Category模型添加last_modify_time字段
migrations.AddField(
model_name='category',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
+ # 向Links模型添加creation_time字段
migrations.AddField(
model_name='links',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 向Sidebar模型添加creation_time字段
migrations.AddField(
model_name='sidebar',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 向Tag模型添加creation_time字段
migrations.AddField(
model_name='tag',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 向Tag模型添加last_modify_time字段
migrations.AddField(
model_name='tag',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
+ # 修改Article模型的article_order字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='article_order',
field=models.IntegerField(default=0, verbose_name='order'),
),
+ # 修改Article模型的author字段verbose_name(国际化)
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'),
),
+ # 修改Article模型的body字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='body',
field=mdeditor.fields.MDTextField(verbose_name='body'),
),
+ # 修改Article模型的category字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
),
+ # 修改Article模型的comment_status字段verbose_name和选项(国际化)
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'),
),
+ # 修改Article模型的pub_time字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='pub_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
),
+ # 修改Article模型的show_toc字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='show_toc',
field=models.BooleanField(default=False, verbose_name='show toc'),
),
+ # 修改Article模型的status字段verbose_name和选项(国际化)
migrations.AlterField(
model_name='article',
name='status',
field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
),
+ # 修改Article模型的tags字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='tags',
field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
),
+ # 修改Article模型的title字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='title',
field=models.CharField(max_length=200, unique=True, verbose_name='title'),
),
+ # 修改Article模型的type字段verbose_name和选项(国际化)
migrations.AlterField(
model_name='article',
name='type',
field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
),
+ # 修改Article模型的views字段verbose_name(国际化)
migrations.AlterField(
model_name='article',
name='views',
field=models.PositiveIntegerField(default=0, verbose_name='views'),
),
+ # 修改BlogSettings模型的article_comment_count字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='article_comment_count',
field=models.IntegerField(default=5, verbose_name='article comment count'),
),
+ # 修改BlogSettings模型的article_sub_length字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='article_sub_length',
field=models.IntegerField(default=300, verbose_name='article sub length'),
),
+ # 修改BlogSettings模型的google_adsense_codes字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='google_adsense_codes',
field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
),
+ # 修改BlogSettings模型的open_site_comment字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='open_site_comment',
field=models.BooleanField(default=True, verbose_name='open site comment'),
),
+ # 修改BlogSettings模型的show_google_adsense字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='show_google_adsense',
field=models.BooleanField(default=False, verbose_name='show adsense'),
),
+ # 修改BlogSettings模型的sidebar_article_count字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='sidebar_article_count',
field=models.IntegerField(default=10, verbose_name='sidebar article count'),
),
+ # 修改BlogSettings模型的sidebar_comment_count字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='sidebar_comment_count',
field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
),
+ # 修改BlogSettings模型的site_description字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='site_description',
field=models.TextField(default='', max_length=1000, verbose_name='site description'),
),
+ # 修改BlogSettings模型的site_keywords字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='site_keywords',
field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
),
+ # 修改BlogSettings模型的site_name字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='site_name',
field=models.CharField(default='', max_length=200, verbose_name='site name'),
),
+ # 修改BlogSettings模型的site_seo_description字段verbose_name(国际化)
migrations.AlterField(
model_name='blogsettings',
name='site_seo_description',
field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
),
+ # 修改Category模型的index字段verbose_name(国际化)
migrations.AlterField(
model_name='category',
name='index',
field=models.IntegerField(default=0, verbose_name='index'),
),
+ # 修改Category模型的name字段verbose_name(国际化)
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
),
+ # 修改Category模型的parent_category字段verbose_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'),
),
+ # 修改Links模型的is_enable字段verbose_name(国际化)
migrations.AlterField(
model_name='links',
name='is_enable',
field=models.BooleanField(default=True, verbose_name='is show'),
),
+ # 修改Links模型的last_mod_time字段verbose_name(国际化)
migrations.AlterField(
model_name='links',
name='last_mod_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
+ # 修改Links模型的link字段verbose_name(国际化)
migrations.AlterField(
model_name='links',
name='link',
field=models.URLField(verbose_name='link'),
),
+ # 修改Links模型的name字段verbose_name(国际化)
migrations.AlterField(
model_name='links',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
),
+ # 修改Links模型的sequence字段verbose_name(国际化)
migrations.AlterField(
model_name='links',
name='sequence',
field=models.IntegerField(unique=True, verbose_name='order'),
),
+ # 修改Links模型的show_type字段verbose_name和选项(国际化)
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'),
),
+ # 修改Sidebar模型的content字段verbose_name(国际化)
migrations.AlterField(
model_name='sidebar',
name='content',
field=models.TextField(verbose_name='content'),
),
+ # 修改Sidebar模型的is_enable字段verbose_name(国际化)
migrations.AlterField(
model_name='sidebar',
name='is_enable',
field=models.BooleanField(default=True, verbose_name='is enable'),
),
+ # 修改Sidebar模型的last_mod_time字段verbose_name(国际化)
migrations.AlterField(
model_name='sidebar',
name='last_mod_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
+ # 修改Sidebar模型的name字段verbose_name(国际化)
migrations.AlterField(
model_name='sidebar',
name='name',
field=models.CharField(max_length=100, verbose_name='title'),
),
+ # 修改Sidebar模型的sequence字段verbose_name(国际化)
migrations.AlterField(
model_name='sidebar',
name='sequence',
field=models.IntegerField(unique=True, verbose_name='order'),
),
+ # 修改Tag模型的name字段verbose_name(国际化)
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py
index e36feb4..81ecef6 100644
--- a/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py
+++ b/src/DjangoBlog/blog/migrations/0006_alter_blogsettings_options.py
@@ -8,7 +8,22 @@ class Migration(migrations.Migration):
dependencies = [
('blog', '0005_alter_article_options_alter_category_options_and_more'),
]
+ # 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'), # 依赖blog应用的第五个迁移
+ ]
+
+ operations = [
+ # 修改BlogSettings模型的元数据选项(国际化)
+ migrations.AlterModelOptions(
+ name='blogsettings',
+ options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'},
+ ),
+ ]
operations = [
migrations.AlterModelOptions(
name='blogsettings',
diff --git a/src/DjangoBlog/blog/models.py b/src/DjangoBlog/blog/models.py
index 083788b..a2bac48 100644
--- a/src/DjangoBlog/blog/models.py
+++ b/src/DjangoBlog/blog/models.py
@@ -18,106 +18,114 @@ logger = logging.getLogger(__name__)
class LinkShowType(models.TextChoices):
- I = ('i', _('index'))
- L = ('l', _('list'))
- P = ('p', _('post'))
- A = ('a', _('all'))
- S = ('s', _('slide'))
+ # 链接显示类型选择
+ I = ('i', _('index')) # 首页显示
+ L = ('l', _('list')) # 列表页显示
+ P = ('p', _('post')) # 文章页面显示
+ A = ('a', _('all')) # 全站显示
+ S = ('s', _('slide')) # 幻灯片显示
class BaseModel(models.Model):
- id = models.AutoField(primary_key=True)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_modify_time = models.DateTimeField(_('modify time'), default=now)
+ # 基础模型类,提供公共字段和方法
+ id = models.AutoField(primary_key=True) # 自增主键
+ creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
+ last_modify_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
def save(self, *args, **kwargs):
+ # 重写保存方法,处理特殊逻辑
is_update_views = isinstance(
self,
- Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
+ Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] # 检查是否为更新浏览量
if is_update_views:
- Article.objects.filter(pk=self.pk).update(views=self.views)
+ Article.objects.filter(pk=self.pk).update(views=self.views) # 直接更新浏览量,避免触发其他逻辑
else:
- if 'slug' in self.__dict__:
+ if 'slug' in self.__dict__: # 如果有slug字段
slug = getattr(
self, 'title') if 'title' in self.__dict__ else getattr(
- self, 'name')
- setattr(self, 'slug', slugify(slug))
- super().save(*args, **kwargs)
+ self, 'name') # 根据title或name生成slug
+ setattr(self, 'slug', slugify(slug)) # 设置slug值
+ super().save(*args, **kwargs) # 调用父类保存方法
def get_full_url(self):
- site = get_current_site().domain
+ # 获取完整URL
+ site = get_current_site().domain # 获取当前站点域名
url = "https://{site}{path}".format(site=site,
- path=self.get_absolute_url())
+ path=self.get_absolute_url()) # 构建完整URL
return url
class Meta:
- abstract = True
+ abstract = True # 抽象基类,不会创建数据库表
@abstractmethod
def get_absolute_url(self):
+ # 抽象方法,子类必须实现获取绝对URL的方法
pass
class Article(BaseModel):
- """文章"""
+ """文章模型"""
STATUS_CHOICES = (
- ('d', _('Draft')),
- ('p', _('Published')),
+ ('d', _('Draft')), # 草稿状态
+ ('p', _('Published')), # 已发布状态
)
COMMENT_STATUS = (
- ('o', _('Open')),
- ('c', _('Close')),
+ ('o', _('Open')), # 评论开启
+ ('c', _('Close')), # 评论关闭
)
TYPE = (
- ('a', _('Article')),
- ('p', _('Page')),
+ ('a', _('Article')), # 文章类型
+ ('p', _('Page')), # 页面类型
)
- title = models.CharField(_('title'), max_length=200, unique=True)
- body = MDTextField(_('body'))
+ title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题
+ body = MDTextField(_('body')) # 文章正文,使用Markdown编辑器
pub_time = models.DateTimeField(
- _('publish time'), blank=False, null=False, default=now)
+ _('publish time'), blank=False, null=False, default=now) # 发布时间
status = models.CharField(
_('status'),
max_length=1,
choices=STATUS_CHOICES,
- default='p')
+ default='p') # 文章状态
comment_status = models.CharField(
_('comment status'),
max_length=1,
choices=COMMENT_STATUS,
- default='o')
- type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
- views = models.PositiveIntegerField(_('views'), default=0)
+ default='o') # 评论状态
+ type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') # 文章类型
+ views = models.PositiveIntegerField(_('views'), default=0) # 浏览量
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
blank=False,
null=False,
- on_delete=models.CASCADE)
+ on_delete=models.CASCADE) # 作者外键
article_order = models.IntegerField(
- _('order'), blank=False, null=False, default=0)
- show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False)
+ _('order'), blank=False, null=False, default=0) # 文章排序
+ show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) # 是否显示目录
category = models.ForeignKey(
'Category',
verbose_name=_('category'),
on_delete=models.CASCADE,
blank=False,
- null=False)
- tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True)
+ null=False) # 分类外键
+ tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 标签多对多关系
def body_to_string(self):
+ # 返回正文字符串
return self.body
def __str__(self):
+ # 对象字符串表示
return self.title
class Meta:
- ordering = ['-article_order', '-pub_time']
- verbose_name = _('article')
- verbose_name_plural = verbose_name
- get_latest_by = 'id'
+ ordering = ['-article_order', '-pub_time'] # 默认排序:按文章排序倒序,发布时间倒序
+ verbose_name = _('article') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
+ get_latest_by = 'id' # 指定获取最新记录的字段
def get_absolute_url(self):
+ # 获取文章绝对URL
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
'year': self.creation_time.year,
@@ -125,252 +133,269 @@ class Article(BaseModel):
'day': self.creation_time.day
})
- @cache_decorator(60 * 60 * 10)
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self):
- tree = self.category.get_category_tree()
- names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
+ # 获取分类树
+ tree = self.category.get_category_tree() # 获取分类树
+ names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) # 转换为名称和URL的元组列表
return names
def save(self, *args, **kwargs):
+ # 保存文章
super().save(*args, **kwargs)
def viewed(self):
+ # 增加浏览量
self.views += 1
- self.save(update_fields=['views'])
+ self.save(update_fields=['views']) # 只更新views字段
def comment_list(self):
- cache_key = 'article_comments_{id}'.format(id=self.id)
- value = cache.get(cache_key)
+ # 获取评论列表(带缓存)
+ cache_key = 'article_comments_{id}'.format(id=self.id) # 缓存键
+ value = cache.get(cache_key) # 尝试从缓存获取
if value:
- logger.info('get article comments:{id}'.format(id=self.id))
+ logger.info('get article comments:{id}'.format(id=self.id)) # 记录缓存命中日志
return value
else:
- comments = self.comment_set.filter(is_enable=True).order_by('-id')
- cache.set(cache_key, comments, 60 * 100)
- logger.info('set article comments:{id}'.format(id=self.id))
+ comments = self.comment_set.filter(is_enable=True).order_by('-id') # 获取有效评论并按ID倒序
+ cache.set(cache_key, comments, 60 * 100) # 设置缓存,有效期100分钟
+ logger.info('set article comments:{id}'.format(id=self.id)) # 记录缓存设置日志
return comments
def get_admin_url(self):
- info = (self._meta.app_label, self._meta.model_name)
- return reverse('admin:%s_%s_change' % info, args=(self.pk,))
+ # 获取管理后台URL
+ info = (self._meta.app_label, self._meta.model_name) # 获取应用和模型信息
+ return reverse('admin:%s_%s_change' % info, args=(self.pk,)) # 生成管理后台编辑URL
- @cache_decorator(expiration=60 * 100)
+ @cache_decorator(expiration=60 * 100) # 缓存100分钟
def next_article(self):
- # 下一篇
+ # 获取下一篇文章
return Article.objects.filter(
- id__gt=self.id, status='p').order_by('id').first()
+ id__gt=self.id, status='p').order_by('id').first() # 获取ID大于当前文章的第一篇已发布文章
- @cache_decorator(expiration=60 * 100)
+ @cache_decorator(expiration=60 * 100) # 缓存100分钟
def prev_article(self):
- # 前一篇
- return Article.objects.filter(id__lt=self.id, status='p').first()
+ # 获取上一篇文章
+ return Article.objects.filter(id__lt=self.id, status='p').first() # 获取ID小于当前文章的第一篇已发布文章
def get_first_image_url(self):
"""
- Get the first image url from article.body.
+ 从文章正文中获取第一张图片URL
:return:
"""
- match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
+ match = re.search(r'!\[.*?\]\((.+?)\)', self.body) # 使用正则表达式匹配Markdown图片语法
if match:
- return match.group(1)
- return ""
+ return match.group(1) # 返回图片URL
+ return "" # 没有图片返回空字符串
class Category(BaseModel):
- """文章分类"""
- name = models.CharField(_('category name'), max_length=30, unique=True)
+ """文章分类模型"""
+ name = models.CharField(_('category name'), max_length=30, unique=True) # 分类名称
parent_category = models.ForeignKey(
'self',
verbose_name=_('parent category'),
blank=True,
null=True,
- on_delete=models.CASCADE)
- slug = models.SlugField(default='no-slug', max_length=60, blank=True)
- index = models.IntegerField(default=0, verbose_name=_('index'))
+ on_delete=models.CASCADE) # 父级分类,自关联
+ slug = models.SlugField(default='no-slug', max_length=60, blank=True) # URL友好标识
+ index = models.IntegerField(default=0, verbose_name=_('index')) # 排序索引
class Meta:
- ordering = ['-index']
- verbose_name = _('category')
- verbose_name_plural = verbose_name
+ ordering = ['-index'] # 默认按索引倒序排列
+ verbose_name = _('category') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
def get_absolute_url(self):
+ # 获取分类绝对URL
return reverse(
'blog:category_detail', kwargs={
- 'category_name': self.slug})
+ 'category_name': self.slug}) # 使用slug作为URL参数
def __str__(self):
+ # 对象字符串表示
return self.name
- @cache_decorator(60 * 60 * 10)
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self):
"""
递归获得分类目录的父级
:return:
"""
- categorys = []
+ categorys = [] # 存储分类树
def parse(category):
- categorys.append(category)
- if category.parent_category:
- parse(category.parent_category)
+ # 递归解析分类树
+ categorys.append(category) # 添加当前分类
+ if category.parent_category: # 如果有父级分类
+ parse(category.parent_category) # 递归解析父级分类
- parse(self)
+ parse(self) # 从当前分类开始解析
return categorys
- @cache_decorator(60 * 60 * 10)
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_sub_categorys(self):
"""
获得当前分类目录所有子集
:return:
"""
- categorys = []
- all_categorys = Category.objects.all()
+ categorys = [] # 存储子分类
+ all_categorys = Category.objects.all() # 获取所有分类
def parse(category):
+ # 递归解析子分类
if category not in categorys:
- categorys.append(category)
- childs = all_categorys.filter(parent_category=category)
+ categorys.append(category) # 添加当前分类
+ childs = all_categorys.filter(parent_category=category) # 获取直接子分类
for child in childs:
if category not in categorys:
- categorys.append(child)
- parse(child)
+ categorys.append(child) # 添加子分类
+ parse(child) # 递归解析子分类的子分类
- parse(self)
+ parse(self) # 从当前分类开始解析
return categorys
class Tag(BaseModel):
- """文章标签"""
- name = models.CharField(_('tag name'), max_length=30, unique=True)
- slug = models.SlugField(default='no-slug', max_length=60, blank=True)
+ """文章标签模型"""
+ name = models.CharField(_('tag name'), max_length=30, unique=True) # 标签名称
+ slug = models.SlugField(default='no-slug', max_length=60, blank=True) # URL友好标识
def __str__(self):
+ # 对象字符串表示
return self.name
def get_absolute_url(self):
- return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
+ # 获取标签绝对URL
+ return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) # 使用slug作为URL参数
- @cache_decorator(60 * 60 * 10)
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_article_count(self):
- return Article.objects.filter(tags__name=self.name).distinct().count()
+ # 获取标签下的文章数量
+ return Article.objects.filter(tags__name=self.name).distinct().count() # 统计不重复的文章数量
class Meta:
- ordering = ['name']
- verbose_name = _('tag')
- verbose_name_plural = verbose_name
+ ordering = ['name'] # 默认按名称升序排列
+ verbose_name = _('tag') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
class Links(models.Model):
- """友情链接"""
+ """友情链接模型"""
- name = models.CharField(_('link name'), max_length=30, unique=True)
- link = models.URLField(_('link'))
- sequence = models.IntegerField(_('order'), unique=True)
+ name = models.CharField(_('link name'), max_length=30, unique=True) # 链接名称
+ link = models.URLField(_('link')) # 链接地址
+ sequence = models.IntegerField(_('order'), unique=True) # 排序序号
is_enable = models.BooleanField(
- _('is show'), default=True, blank=False, null=False)
+ _('is show'), default=True, blank=False, null=False) # 是否显示
show_type = models.CharField(
_('show type'),
max_length=1,
choices=LinkShowType.choices,
- default=LinkShowType.I)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_mod_time = models.DateTimeField(_('modify time'), default=now)
+ default=LinkShowType.I) # 显示类型
+ creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
+ last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
class Meta:
- ordering = ['sequence']
- verbose_name = _('link')
- verbose_name_plural = verbose_name
+ ordering = ['sequence'] # 默认按序号升序排列
+ verbose_name = _('link') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
def __str__(self):
+ # 对象字符串表示
return self.name
class SideBar(models.Model):
- """侧边栏,可以展示一些html内容"""
- name = models.CharField(_('title'), max_length=100)
- content = models.TextField(_('content'))
- sequence = models.IntegerField(_('order'), unique=True)
- is_enable = models.BooleanField(_('is enable'), default=True)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_mod_time = models.DateTimeField(_('modify time'), default=now)
+ """侧边栏模型,可以展示一些html内容"""
+ name = models.CharField(_('title'), max_length=100) # 侧边栏标题
+ content = models.TextField(_('content')) # 侧边栏内容
+ sequence = models.IntegerField(_('order'), unique=True) # 排序序号
+ is_enable = models.BooleanField(_('is enable'), default=True) # 是否启用
+ creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
+ last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
class Meta:
- ordering = ['sequence']
- verbose_name = _('sidebar')
- verbose_name_plural = verbose_name
+ ordering = ['sequence'] # 默认按序号升序排列
+ verbose_name = _('sidebar') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
def __str__(self):
+ # 对象字符串表示
return self.name
class BlogSettings(models.Model):
- """blog的配置"""
+ """博客配置模型"""
site_name = models.CharField(
_('site name'),
max_length=200,
null=False,
blank=False,
- default='')
+ default='') # 网站名称
site_description = models.TextField(
_('site description'),
max_length=1000,
null=False,
blank=False,
- default='')
+ default='') # 网站描述
site_seo_description = models.TextField(
- _('site seo description'), max_length=1000, null=False, blank=False, default='')
+ _('site seo description'), max_length=1000, null=False, blank=False, default='') # 网站SEO描述
site_keywords = models.TextField(
_('site keywords'),
max_length=1000,
null=False,
blank=False,
- default='')
- article_sub_length = models.IntegerField(_('article sub length'), default=300)
- sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10)
- sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5)
- article_comment_count = models.IntegerField(_('article comment count'), default=5)
- show_google_adsense = models.BooleanField(_('show adsense'), default=False)
+ default='') # 网站关键词
+ article_sub_length = models.IntegerField(_('article sub length'), default=300) # 文章摘要长度
+ sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) # 侧边栏文章数量
+ sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) # 侧边栏评论数量
+ article_comment_count = models.IntegerField(_('article comment count'), default=5) # 文章页面评论数量
+ show_google_adsense = models.BooleanField(_('show adsense'), default=False) # 是否显示Google广告
google_adsense_codes = models.TextField(
- _('adsense code'), max_length=2000, null=True, blank=True, default='')
- open_site_comment = models.BooleanField(_('open site comment'), default=True)
- global_header = models.TextField("公共头部", null=True, blank=True, default='')
- global_footer = models.TextField("公共尾部", null=True, blank=True, default='')
+ _('adsense code'), max_length=2000, null=True, blank=True, default='') # Google广告代码
+ open_site_comment = models.BooleanField(_('open site comment'), default=True) # 是否开启网站评论
+ global_header = models.TextField("公共头部", null=True, blank=True, default='') # 全局头部内容
+ global_footer = models.TextField("公共尾部", null=True, blank=True, default='') # 全局尾部内容
beian_code = models.CharField(
'备案号',
max_length=2000,
null=True,
blank=True,
- default='')
+ default='') # 备案号
analytics_code = models.TextField(
"网站统计代码",
max_length=1000,
null=False,
blank=False,
- default='')
+ default='') # 网站统计代码
show_gongan_code = models.BooleanField(
- '是否显示公安备案号', default=False, null=False)
+ '是否显示公安备案号', default=False, null=False) # 是否显示公安备案号
gongan_beiancode = models.TextField(
'公安备案号',
max_length=2000,
null=True,
blank=True,
- default='')
+ default='') # 公安备案号
comment_need_review = models.BooleanField(
- '评论是否需要审核', default=False, null=False)
+ '评论是否需要审核', default=False, null=False) # 评论是否需要审核
class Meta:
- verbose_name = _('Website configuration')
- verbose_name_plural = verbose_name
+ verbose_name = _('Website configuration') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
def __str__(self):
+ # 对象字符串表示
return self.site_name
def clean(self):
+ # 数据清理验证,确保只能有一个配置实例
if BlogSettings.objects.exclude(id=self.id).count():
- raise ValidationError(_('There can only be one configuration'))
+ raise ValidationError(_('There can only be one configuration')) # 只能有一个配置
def save(self, *args, **kwargs):
+ # 保存配置,同时清除缓存
super().save(*args, **kwargs)
from djangoblog.utils import cache
- cache.clear()
+ cache.clear() # 清除所有缓存
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/search_indexes.py b/src/DjangoBlog/blog/search_indexes.py
index 7f1dfac..0d793ca 100644
--- a/src/DjangoBlog/blog/search_indexes.py
+++ b/src/DjangoBlog/blog/search_indexes.py
@@ -4,10 +4,13 @@ from blog.models import Article
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
- text = indexes.CharField(document=True, use_template=True)
+ # Haystack搜索索引类,用于全文搜索
+ 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')
+ # 返回要建立索引的查询集,只包含已发布的文章
+ return self.get_model().objects.filter(status='p')
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/templatetags/blog_tags.py b/src/DjangoBlog/blog/templatetags/blog_tags.py
index 1f994bc..a4d376b 100644
--- a/src/DjangoBlog/blog/templatetags/blog_tags.py
+++ b/src/DjangoBlog/blog/templatetags/blog_tags.py
@@ -22,18 +22,20 @@ from djangoblog.plugin_manage import hooks
logger = logging.getLogger(__name__)
-register = template.Library()
+register = template.Library() # 创建模板标签库实例
@register.simple_tag(takes_context=True)
def head_meta(context):
+ # 头部meta标签,应用插件过滤器
return mark_safe(hooks.apply_filters('head_meta', '', context))
@register.simple_tag
def timeformat(data):
+ # 时间格式化标签
try:
- return data.strftime(settings.TIME_FORMAT)
+ return data.strftime(settings.TIME_FORMAT) # 使用设置中的时间格式
except Exception as e:
logger.error(e)
return ""
@@ -41,8 +43,9 @@ def timeformat(data):
@register.simple_tag
def datetimeformat(data):
+ # 日期时间格式化标签
try:
- return data.strftime(settings.DATE_TIME_FORMAT)
+ return data.strftime(settings.DATE_TIME_FORMAT) # 使用设置中的日期时间格式
except Exception as e:
logger.error(e)
return ""
@@ -55,19 +58,20 @@ def custom_markdown(content):
通用markdown过滤器,应用文章内容插件
主要用于文章内容处理
"""
- html_content = CommonMarkdown.get_markdown(content)
-
+ html_content = CommonMarkdown.get_markdown(content) # 将markdown转换为HTML
+
# 然后应用插件过滤器优化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)
+ optimized_html = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, html_content) # 应用文章内容插件
+
+ return mark_safe(optimized_html) # 标记为安全HTML
@register.filter()
@stringfilter
def sidebar_markdown(content):
+ # 侧边栏markdown过滤器
html_content = CommonMarkdown.get_markdown(content)
return mark_safe(html_content)
@@ -76,7 +80,7 @@ def sidebar_markdown(content):
def render_article_content(context, article, is_summary=False):
"""
渲染文章内容,包含完整的上下文信息供插件使用
-
+
Args:
context: 模板上下文
article: 文章对象
@@ -84,56 +88,58 @@ def render_article_content(context, article, is_summary=False):
"""
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,
+ ARTICLE_CONTENT_HOOK_NAME,
+ html_content,
+ article=article,
request=request,
context=context,
is_summary=is_summary # 传递摘要标志,插件可以据此调整行为
)
-
+
return mark_safe(optimized_html)
@register.simple_tag
def get_markdown_toc(content):
+ # 获取markdown目录
from djangoblog.utils import CommonMarkdown
- body, toc = CommonMarkdown.get_markdown_with_toc(content)
+ body, toc = CommonMarkdown.get_markdown_with_toc(content) # 获取带目录的markdown
return mark_safe(toc)
@register.filter()
@stringfilter
def comment_markdown(content):
+ # 评论markdown过滤器,并进行HTML清理
content = CommonMarkdown.get_markdown(content)
- return mark_safe(sanitize_html(content))
+ return mark_safe(sanitize_html(content)) # 清理不安全的HTML
@register.filter(is_safe=True)
@@ -146,13 +152,14 @@ def truncatechars_content(content):
"""
from django.template.defaultfilters import truncatechars_html
from djangoblog.utils import get_blog_setting
- blogsetting = get_blog_setting()
- return truncatechars_html(content, blogsetting.article_sub_length)
+ blogsetting = get_blog_setting() # 获取博客设置
+ return truncatechars_html(content, blogsetting.article_sub_length) # 根据设置截断HTML内容
@register.filter(is_safe=True)
@stringfilter
def truncate(content):
+ # 简单截断过滤器,去除HTML标签后截取前150字符
from django.utils.html import strip_tags
return strip_tags(content)[:150]
@@ -165,12 +172,12 @@ def load_breadcrumb(article):
:param article:
:return:
"""
- names = article.get_category_tree()
+ names = article.get_category_tree() # 获取分类树
from djangoblog.utils import get_blog_setting
blogsetting = get_blog_setting()
site = get_current_site().domain
- names.append((blogsetting.site_name, '/'))
- names = names[::-1]
+ names.append((blogsetting.site_name, '/')) # 添加网站首页
+ names = names[::-1] # 反转列表顺序
return {
'names': names,
@@ -189,10 +196,10 @@ def load_articletags(article):
tags = article.tags.all()
tags_list = []
for tag in tags:
- url = tag.get_absolute_url()
- count = tag.get_article_count()
+ url = tag.get_absolute_url() # 获取标签URL
+ count = tag.get_article_count() # 获取标签下文章数量
tags_list.append((
- url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES)
+ url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) # 随机选择Bootstrap颜色
))
return {
'article_tags_list': tags_list
@@ -205,7 +212,7 @@ def load_sidebar(user, linktype):
加载侧边栏
:return:
"""
- value = cache.get("sidebar" + linktype)
+ value = cache.get("sidebar" + linktype) # 尝试从缓存获取侧边栏
if value:
value['user'] = user
return value
@@ -214,30 +221,30 @@ def load_sidebar(user, linktype):
from djangoblog.utils import get_blog_setting
blogsetting = get_blog_setting()
recent_articles = Article.objects.filter(
- status='p')[:blogsetting.sidebar_article_count]
- sidebar_categorys = Category.objects.all()
+ status='p')[:blogsetting.sidebar_article_count] # 最近文章
+ sidebar_categorys = Category.objects.all() # 全部分类
extra_sidebars = SideBar.objects.filter(
- is_enable=True).order_by('sequence')
+ is_enable=True).order_by('sequence') # 额外侧边栏
most_read_articles = Article.objects.filter(status='p').order_by(
- '-views')[:blogsetting.sidebar_article_count]
- dates = Article.objects.datetimes('creation_time', 'month', order='DESC')
+ '-views')[:blogsetting.sidebar_article_count] # 最多阅读文章
+ dates = Article.objects.datetimes('creation_time', 'month', order='DESC') # 文章归档日期
links = Links.objects.filter(is_enable=True).filter(
- Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A))
+ Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) # 友情链接
commment_list = Comment.objects.filter(is_enable=True).order_by(
- '-id')[:blogsetting.sidebar_comment_count]
+ '-id')[:blogsetting.sidebar_comment_count] # 最新评论
# 标签云 计算字体大小
# 根据总数计算出平均值 大小为 (数目/平均值)*步长
increment = 5
tags = Tag.objects.all()
sidebar_tags = None
if tags and len(tags) > 0:
- s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]]
- count = sum([t[1] for t in s])
- dd = 1 if (count == 0 or not len(tags)) else count / len(tags)
+ s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]] # 过滤有文章的标签
+ count = sum([t[1] for t in s]) # 计算总文章数
+ dd = 1 if (count == 0 or not len(tags)) else count / len(tags) # 计算平均值
import random
sidebar_tags = list(
- map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s))
- random.shuffle(sidebar_tags)
+ map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) # 计算标签字体大小
+ random.shuffle(sidebar_tags) # 随机打乱标签顺序
value = {
'recent_articles': recent_articles,
@@ -253,7 +260,7 @@ def load_sidebar(user, linktype):
'sidebar_tags': sidebar_tags,
'extra_sidebars': extra_sidebars
}
- cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3)
+ cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) # 缓存3小时
logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype))
value['user'] = user
return value
@@ -274,9 +281,10 @@ def load_article_metas(article, user):
@register.inclusion_tag('blog/tags/article_pagination.html')
def load_pagination_info(page_obj, page_type, tag_name):
+ # 分页信息标签
previous_url = ''
next_url = ''
- if page_type == '':
+ if page_type == '': # 首页分页
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse('blog:index_page', kwargs={'page': next_number})
@@ -285,7 +293,7 @@ def load_pagination_info(page_obj, page_type, tag_name):
previous_url = reverse(
'blog:index_page', kwargs={
'page': previous_number})
- if page_type == '分类标签归档':
+ if page_type == '分类标签归档': # 标签分页
tag = get_object_or_404(Tag, name=tag_name)
if page_obj.has_next():
next_number = page_obj.next_page_number()
@@ -301,7 +309,7 @@ def load_pagination_info(page_obj, page_type, tag_name):
kwargs={
'page': previous_number,
'tag_name': tag.slug})
- if page_type == '作者文章归档':
+ if page_type == '作者文章归档': # 作者分页
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse(
@@ -317,7 +325,7 @@ def load_pagination_info(page_obj, page_type, tag_name):
'page': previous_number,
'author_name': tag_name})
- if page_type == '分类目录归档':
+ if page_type == '分类目录归档': # 分类分页
category = get_object_or_404(Category, name=tag_name)
if page_obj.has_next():
next_number = page_obj.next_page_number()
@@ -366,10 +374,10 @@ def load_article_detail(article, isindex, user):
def gravatar_url(email, size=40):
"""获得用户头像 - 优先使用OAuth头像,否则使用默认头像"""
cachekey = 'avatar/' + email
- url = cache.get(cachekey)
+ url = cache.get(cachekey) # 尝试从缓存获取头像
if url:
return url
-
+
# 检查OAuth用户是否有自定义头像
usermodels = OAuthUser.objects.filter(email=email)
if usermodels:
@@ -378,18 +386,19 @@ def gravatar_url(email, size=40):
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')]
+ 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小时
@@ -403,7 +412,7 @@ def gravatar(email, size=40):
url = gravatar_url(email, size)
return mark_safe(
'
' %
- (url, size, size))
+ (url, size, size)) # 生成头像img标签
@register.simple_tag
@@ -414,10 +423,10 @@ def query(qs, **kwargs):
...
{% endfor %}
"""
- return qs.filter(**kwargs)
+ return qs.filter(**kwargs) # 查询集过滤
@register.filter
def addstr(arg1, arg2):
"""concatenate arg1 & arg2"""
- return str(arg1) + str(arg2)
+ return str(arg1) + str(arg2) # 字符串连接
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/tests.py b/src/DjangoBlog/blog/tests.py
index ee13505..dd235d3 100644
--- a/src/DjangoBlog/blog/tests.py
+++ b/src/DjangoBlog/blog/tests.py
@@ -21,54 +21,57 @@ from oauth.models import OAuthUser, OAuthConfig
class ArticleTest(TestCase):
def setUp(self):
- self.client = Client()
- self.factory = RequestFactory()
+ # 测试初始化设置
+ self.client = Client() # 创建测试客户端
+ self.factory = RequestFactory() # 创建请求工厂
def test_validate_article(self):
+ # 测试文章验证功能
site = get_current_site().domain
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
- username="liangliangyy")[0]
- user.set_password("liangliangyy")
- user.is_staff = True
- user.is_superuser = True
+ username="liangliangyy")[0] # 获取或创建测试用户
+ user.set_password("liangliangyy") # 设置密码
+ user.is_staff = True # 设置为员工
+ user.is_superuser = True # 设置为超级用户
user.save()
- response = self.client.get(user.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- response = self.client.get('/admin/servermanager/emailsendlog/')
- response = self.client.get('admin/admin/logentry/')
- s = SideBar()
- s.sequence = 1
- s.name = 'test'
- s.content = 'test content'
- s.is_enable = True
+ response = self.client.get(user.get_absolute_url()) # 访问用户详情页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
+ response = self.client.get('/admin/servermanager/emailsendlog/') # 访问邮件发送日志管理页
+ response = self.client.get('admin/admin/logentry/') # 访问日志条目管理页
+ s = SideBar() # 创建侧边栏
+ s.sequence = 1 # 设置排序
+ s.name = 'test' # 设置名称
+ s.content = 'test content' # 设置内容
+ s.is_enable = True # 启用侧边栏
s.save()
- category = Category()
- category.name = "category"
- category.creation_time = timezone.now()
- category.last_mod_time = timezone.now()
+ category = Category() # 创建分类
+ category.name = "category" # 设置分类名称
+ category.creation_time = timezone.now() # 设置创建时间
+ category.last_mod_time = timezone.now() # 设置修改时间
category.save()
- tag = Tag()
- tag.name = "nicetag"
+ tag = Tag() # 创建标签
+ tag.name = "nicetag" # 设置标签名称
tag.save()
- article = Article()
- article.title = "nicetitle"
- article.body = "nicecontent"
- article.author = user
- article.category = category
- article.type = 'a'
- article.status = 'p'
+ article = Article() # 创建文章
+ article.title = "nicetitle" # 设置文章标题
+ article.body = "nicecontent" # 设置文章内容
+ article.author = user # 设置作者
+ article.category = category # 设置分类
+ article.type = 'a' # 设置类型为文章
+ article.status = 'p' # 设置状态为已发布
article.save()
- self.assertEqual(0, article.tags.count())
- article.tags.add(tag)
+ self.assertEqual(0, article.tags.count()) # 验证初始标签数为0
+ article.tags.add(tag) # 添加标签
article.save()
- self.assertEqual(1, article.tags.count())
+ self.assertEqual(1, article.tags.count()) # 验证标签数变为1
for i in range(20):
+ # 批量创建20篇文章
article = Article()
article.title = "nicetitle" + str(i)
article.body = "nicetitle" + str(i)
@@ -81,152 +84,156 @@ class ArticleTest(TestCase):
article.save()
from blog.documents import ELASTICSEARCH_ENABLED
if ELASTICSEARCH_ENABLED:
- call_command("build_index")
- response = self.client.get('/search', {'q': 'nicetitle'})
- self.assertEqual(response.status_code, 200)
+ call_command("build_index") # 构建搜索索引
+ response = self.client.get('/search', {'q': 'nicetitle'}) # 搜索文章
+ self.assertEqual(response.status_code, 200) # 验证搜索成功
- response = self.client.get(article.get_absolute_url())
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(article.get_absolute_url()) # 访问文章详情页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
from djangoblog.spider_notify import SpiderNotify
- SpiderNotify.notify(article.get_absolute_url())
- response = self.client.get(tag.get_absolute_url())
- self.assertEqual(response.status_code, 200)
+ SpiderNotify.notify(article.get_absolute_url()) # 通知搜索引擎
+ response = self.client.get(tag.get_absolute_url()) # 访问标签详情页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- response = self.client.get(category.get_absolute_url())
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(category.get_absolute_url()) # 访问分类详情页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- response = self.client.get('/search', {'q': 'django'})
- self.assertEqual(response.status_code, 200)
- s = load_articletags(article)
- self.assertIsNotNone(s)
+ response = self.client.get('/search', {'q': 'django'}) # 搜索django相关内容
+ self.assertEqual(response.status_code, 200) # 验证搜索成功
+ s = load_articletags(article) # 加载文章标签
+ self.assertIsNotNone(s) # 验证标签加载成功
- self.client.login(username='liangliangyy', password='liangliangyy')
+ self.client.login(username='liangliangyy', password='liangliangyy') # 登录用户
- response = self.client.get(reverse('blog:archives'))
- self.assertEqual(response.status_code, 200)
+ response = self.client.get(reverse('blog:archives')) # 访问文章归档页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
- self.check_pagination(p, '', '')
+ p = Paginator(Article.objects.all(), settings.PAGINATE_BY) # 创建所有文章的分页器
+ self.check_pagination(p, '', '') # 测试分页功能
- p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
- self.check_pagination(p, '分类标签归档', tag.slug)
+ p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY) # 创建标签文章的分页器
+ self.check_pagination(p, '分类标签归档', tag.slug) # 测试标签分页
p = Paginator(
Article.objects.filter(
- author__username='liangliangyy'), settings.PAGINATE_BY)
- self.check_pagination(p, '作者文章归档', 'liangliangyy')
+ author__username='liangliangyy'), settings.PAGINATE_BY) # 创建作者文章的分页器
+ self.check_pagination(p, '作者文章归档', 'liangliangyy') # 测试作者分页
- p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
- self.check_pagination(p, '分类目录归档', category.slug)
+ p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY) # 创建分类文章的分页器
+ self.check_pagination(p, '分类目录归档', category.slug) # 测试分类分页
- f = BlogSearchForm()
- f.search()
+ f = BlogSearchForm() # 创建搜索表单
+ f.search() # 执行搜索
# self.client.login(username='liangliangyy', password='liangliangyy')
from djangoblog.spider_notify import SpiderNotify
- SpiderNotify.baidu_notify([article.get_full_url()])
+ SpiderNotify.baidu_notify([article.get_full_url()]) # 通知百度搜索引擎
from blog.templatetags.blog_tags import gravatar_url, gravatar
- u = gravatar_url('liangliangyy@gmail.com')
- u = gravatar('liangliangyy@gmail.com')
+ u = gravatar_url('liangliangyy@gmail.com') # 获取头像URL
+ u = gravatar('liangliangyy@gmail.com') # 获取头像HTML
- link = Links(
+ link = Links( # 创建友情链接
sequence=1,
name="lylinux",
link='https://wwww.lylinux.net')
link.save()
- response = self.client.get('/links.html')
- self.assertEqual(response.status_code, 200)
+ response = self.client.get('/links.html') # 访问友情链接页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- response = self.client.get('/feed/')
- self.assertEqual(response.status_code, 200)
+ response = self.client.get('/feed/') # 访问RSS订阅
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- response = self.client.get('/sitemap.xml')
- self.assertEqual(response.status_code, 200)
+ response = self.client.get('/sitemap.xml') # 访问网站地图
+ self.assertEqual(response.status_code, 200) # 验证访问成功
- self.client.get("/admin/blog/article/1/delete/")
- self.client.get('/admin/servermanager/emailsendlog/')
- self.client.get('/admin/admin/logentry/')
- self.client.get('/admin/admin/logentry/1/change/')
+ self.client.get("/admin/blog/article/1/delete/") # 访问文章删除页
+ self.client.get('/admin/servermanager/emailsendlog/') # 访问邮件发送日志管理页
+ self.client.get('/admin/admin/logentry/') # 访问日志条目管理页
+ self.client.get('/admin/admin/logentry/1/change/') # 访问日志条目编辑页
def check_pagination(self, p, type, value):
- for page in range(1, p.num_pages + 1):
- s = load_pagination_info(p.page(page), type, value)
- self.assertIsNotNone(s)
- if s['previous_url']:
- response = self.client.get(s['previous_url'])
- self.assertEqual(response.status_code, 200)
- if s['next_url']:
- response = self.client.get(s['next_url'])
- self.assertEqual(response.status_code, 200)
+ # 检查分页功能
+ for page in range(1, p.num_pages + 1): # 遍历所有分页
+ s = load_pagination_info(p.page(page), type, value) # 加载分页信息
+ self.assertIsNotNone(s) # 验证分页信息不为空
+ if s['previous_url']: # 如果有上一页链接
+ response = self.client.get(s['previous_url']) # 访问上一页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
+ if s['next_url']: # 如果有下一页链接
+ response = self.client.get(s['next_url']) # 访问下一页
+ self.assertEqual(response.status_code, 200) # 验证访问成功
def test_image(self):
+ # 测试图片上传功能
import requests
rsp = requests.get(
- 'https://www.python.org/static/img/python-logo.png')
- imagepath = os.path.join(settings.BASE_DIR, 'python.png')
+ 'https://www.python.org/static/img/python-logo.png') # 下载测试图片
+ imagepath = os.path.join(settings.BASE_DIR, 'python.png') # 图片保存路径
with open(imagepath, 'wb') as file:
- file.write(rsp.content)
- rsp = self.client.post('/upload')
- self.assertEqual(rsp.status_code, 403)
- sign = get_sha256(get_sha256(settings.SECRET_KEY))
+ file.write(rsp.content) # 保存图片
+ rsp = self.client.post('/upload') # 尝试上传图片(无签名)
+ self.assertEqual(rsp.status_code, 403) # 验证被拒绝(需要签名)
+ sign = get_sha256(get_sha256(settings.SECRET_KEY)) # 生成上传签名
with open(imagepath, 'rb') as file:
imgfile = SimpleUploadedFile(
- 'python.png', file.read(), content_type='image/jpg')
+ 'python.png', file.read(), content_type='image/jpg') # 创建上传文件对象
form_data = {'python.png': imgfile}
rsp = self.client.post(
- '/upload?sign=' + sign, form_data, follow=True)
- self.assertEqual(rsp.status_code, 200)
- os.remove(imagepath)
+ '/upload?sign=' + sign, form_data, follow=True) # 带签名上传图片
+ self.assertEqual(rsp.status_code, 200) # 验证上传成功
+ os.remove(imagepath) # 删除临时图片文件
from djangoblog.utils import save_user_avatar, send_email
- send_email(['qq@qq.com'], 'testTitle', 'testContent')
+ send_email(['qq@qq.com'], 'testTitle', 'testContent') # 测试发送邮件
save_user_avatar(
- 'https://www.python.org/static/img/python-logo.png')
+ 'https://www.python.org/static/img/python-logo.png') # 测试保存用户头像
def test_errorpage(self):
- rsp = self.client.get('/eee')
- self.assertEqual(rsp.status_code, 404)
+ # 测试错误页面
+ rsp = self.client.get('/eee') # 访问不存在的页面
+ self.assertEqual(rsp.status_code, 404) # 验证返回404错误
def test_commands(self):
+ # 测试管理命令
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
- username="liangliangyy")[0]
- user.set_password("liangliangyy")
- user.is_staff = True
- user.is_superuser = True
+ username="liangliangyy")[0] # 获取或创建测试用户
+ user.set_password("liangliangyy") # 设置密码
+ user.is_staff = True # 设置为员工
+ user.is_superuser = True # 设置为超级用户
user.save()
- c = OAuthConfig()
- c.type = 'qq'
- c.appkey = 'appkey'
- c.appsecret = 'appsecret'
+ c = OAuthConfig() # 创建OAuth配置
+ c.type = 'qq' # 设置类型为QQ
+ c.appkey = 'appkey' # 设置应用密钥
+ c.appsecret = 'appsecret' # 设置应用密钥
c.save()
- u = OAuthUser()
- u.type = 'qq'
- u.openid = 'openid'
- u.user = user
- u.picture = static("/blog/img/avatar.png")
+ u = OAuthUser() # 创建OAuth用户
+ u.type = 'qq' # 设置类型为QQ
+ u.openid = 'openid' # 设置OpenID
+ u.user = user # 关联用户
+ u.picture = static("/blog/img/avatar.png") # 设置头像
u.metadata = '''
{
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
-}'''
+}''' # 设置元数据
u.save()
- u = OAuthUser()
- u.type = 'qq'
- u.openid = 'openid1'
- u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
+ u = OAuthUser() # 创建另一个OAuth用户
+ u.type = 'qq' # 设置类型为QQ
+ u.openid = 'openid1' # 设置不同的OpenID
+ u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30' # 设置网络头像
u.metadata = '''
{
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
- }'''
+ }''' # 设置元数据
u.save()
from blog.documents import ELASTICSEARCH_ENABLED
if ELASTICSEARCH_ENABLED:
- call_command("build_index")
- call_command("ping_baidu", "all")
- call_command("create_testdata")
- call_command("clear_cache")
- call_command("sync_user_avatar")
- call_command("build_search_words")
+ call_command("build_index") # 构建搜索索引
+ call_command("ping_baidu", "all") # 通知百度搜索引擎
+ call_command("create_testdata") # 创建测试数据
+ call_command("clear_cache") # 清除缓存
+ call_command("sync_user_avatar") # 同步用户头像
+ call_command("build_search_words") # 构建搜索词
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/urls.py b/src/DjangoBlog/blog/urls.py
index adf2703..84737b0 100644
--- a/src/DjangoBlog/blog/urls.py
+++ b/src/DjangoBlog/blog/urls.py
@@ -3,60 +3,74 @@ from django.views.decorators.cache import cache_page
from . import views
-app_name = "blog"
+app_name = "blog" # 定义应用的命名空间
+
urlpatterns = [
+ # 首页URL
path(
r'',
views.IndexView.as_view(),
name='index'),
+ # 首页分页URL
path(
r'page//',
views.IndexView.as_view(),
name='index_page'),
+ # 文章详情页URL(按ID和时间)
path(
r'article////.html',
views.ArticleDetailView.as_view(),
name='detailbyid'),
+ # 分类详情页URL
path(
r'category/.html',
views.CategoryDetailView.as_view(),
name='category_detail'),
+ # 分类详情分页URL
path(
r'category//.html',
views.CategoryDetailView.as_view(),
name='category_detail_page'),
+ # 作者详情页URL
path(
r'author/.html',
views.AuthorDetailView.as_view(),
name='author_detail'),
+ # 作者详情分页URL
path(
r'author//.html',
views.AuthorDetailView.as_view(),
name='author_detail_page'),
+ # 标签详情页URL
path(
r'tag/.html',
views.TagDetailView.as_view(),
name='tag_detail'),
+ # 标签详情分页URL
path(
r'tag//.html',
views.TagDetailView.as_view(),
name='tag_detail_page'),
+ # 文章归档页URL(缓存1小时)
path(
'archives.html',
cache_page(
60 * 60)(
views.ArchivesView.as_view()),
name='archives'),
+ # 友情链接页URL
path(
'links.html',
views.LinkListView.as_view(),
name='links'),
+ # 文件上传URL
path(
r'upload',
views.fileupload,
name='upload'),
+ # 缓存清理URL
path(
r'clean',
views.clean_cache_view,
name='clean'),
-]
+]
\ No newline at end of file
diff --git a/src/DjangoBlog/blog/views.py b/src/DjangoBlog/blog/views.py
index ace9e63..6c92616 100644
--- a/src/DjangoBlog/blog/views.py
+++ b/src/DjangoBlog/blog/views.py
@@ -25,39 +25,39 @@ logger = logging.getLogger(__name__)
class ArticleListView(ListView):
- # template_name属性用于指定使用哪个模板进行渲染
- template_name = 'blog/article_index.html'
+ # 文章列表视图基类
+ template_name = 'blog/article_index.html' # 指定使用的模板
- # context_object_name属性用于给上下文变量取名(在模板中使用该名字)
- context_object_name = 'article_list'
+ context_object_name = 'article_list' # 上下文变量名称
# 页面类型,分类目录或标签列表等
page_type = ''
- paginate_by = settings.PAGINATE_BY
- page_kwarg = 'page'
- link_type = LinkShowType.L
+ paginate_by = settings.PAGINATE_BY # 分页大小
+ page_kwarg = 'page' # 页码参数名
+ link_type = LinkShowType.L # 链接显示类型
def get_view_cache_key(self):
return self.request.get['pages']
@property
def page_number(self):
+ # 获取当前页码
page_kwarg = self.page_kwarg
page = self.kwargs.get(
- page_kwarg) or self.request.GET.get(page_kwarg) or 1
+ page_kwarg) or self.request.GET.get(page_kwarg) or 1 # 从URL参数或GET参数获取页码
return page
def get_queryset_cache_key(self):
"""
子类重写.获得queryset的缓存key
"""
- raise NotImplementedError()
+ raise NotImplementedError() # 子类必须实现此方法
def get_queryset_data(self):
"""
子类重写.获取queryset的数据
"""
- raise NotImplementedError()
+ raise NotImplementedError() # 子类必须实现此方法
def get_queryset_from_cache(self, cache_key):
'''
@@ -65,14 +65,14 @@ class ArticleListView(ListView):
:param cache_key: 缓存key
:return:
'''
- value = cache.get(cache_key)
+ value = cache.get(cache_key) # 尝试从缓存获取数据
if value:
- logger.info('get view cache.key:{key}'.format(key=cache_key))
+ logger.info('get view cache.key:{key}'.format(key=cache_key)) # 记录缓存命中日志
return value
else:
- article_list = self.get_queryset_data()
- cache.set(cache_key, article_list)
- logger.info('set view cache.key:{key}'.format(key=cache_key))
+ article_list = self.get_queryset_data() # 获取数据
+ cache.set(cache_key, article_list) # 设置缓存
+ logger.info('set view cache.key:{key}'.format(key=cache_key)) # 记录缓存设置日志
return article_list
def get_queryset(self):
@@ -80,296 +80,315 @@ class ArticleListView(ListView):
重写默认,从缓存获取数据
:return:
'''
- key = self.get_queryset_cache_key()
- value = self.get_queryset_from_cache(key)
+ key = self.get_queryset_cache_key() # 获取缓存键
+ value = self.get_queryset_from_cache(key) # 从缓存获取数据
return value
def get_context_data(self, **kwargs):
- kwargs['linktype'] = self.link_type
- return super(ArticleListView, self).get_context_data(**kwargs)
+ kwargs['linktype'] = self.link_type # 添加链接类型到上下文
+ return super(ArticleListView, self).get_context_data(**kwargs) # 调用父类方法
class IndexView(ArticleListView):
'''
- 首页
+ 首页视图
'''
- # 友情链接类型
- link_type = LinkShowType.I
+ link_type = LinkShowType.I # 首页链接类型
def get_queryset_data(self):
- article_list = Article.objects.filter(type='a', status='p')
+ # 获取首页文章列表数据
+ article_list = Article.objects.filter(type='a', status='p') # 过滤文章类型和状态
return article_list
def get_queryset_cache_key(self):
- cache_key = 'index_{page}'.format(page=self.page_number)
+ # 生成首页缓存键
+ cache_key = 'index_{page}'.format(page=self.page_number) # 包含页码的缓存键
return cache_key
class ArticleDetailView(DetailView):
'''
- 文章详情页面
+ 文章详情页面视图
'''
- template_name = 'blog/article_detail.html'
- model = Article
- pk_url_kwarg = 'article_id'
- context_object_name = "article"
+ template_name = 'blog/article_detail.html' # 文章详情模板
+ model = Article # 关联的模型
+ pk_url_kwarg = 'article_id' # URL中的主键参数名
+ context_object_name = "article" # 上下文变量名
def get_context_data(self, **kwargs):
- comment_form = CommentForm()
-
- article_comments = self.object.comment_list()
- parent_comments = article_comments.filter(parent_comment=None)
- blog_setting = get_blog_setting()
- paginator = Paginator(parent_comments, blog_setting.article_comment_count)
- page = self.request.GET.get('comment_page', '1')
- if not page.isnumeric():
+ # 获取文章详情页的上下文数据
+ comment_form = CommentForm() # 评论表单
+
+ article_comments = self.object.comment_list() # 获取文章评论列表
+ parent_comments = article_comments.filter(parent_comment=None) # 获取父级评论
+ blog_setting = get_blog_setting() # 获取博客设置
+ paginator = Paginator(parent_comments, blog_setting.article_comment_count) # 评论分页器
+ page = self.request.GET.get('comment_page', '1') # 获取评论页码
+ if not page.isnumeric(): # 验证页码是否为数字
page = 1
else:
page = int(page)
- if page < 1:
+ if page < 1: # 页码不能小于1
page = 1
- if page > paginator.num_pages:
+ if page > paginator.num_pages: # 页码不能大于总页数
page = paginator.num_pages
- p_comments = paginator.page(page)
- next_page = p_comments.next_page_number() if p_comments.has_next() else None
- prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None
+ p_comments = paginator.page(page) # 获取当前页评论
+ next_page = p_comments.next_page_number() if p_comments.has_next() else None # 下一页页码
+ prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None # 上一页页码
- if next_page:
+ if next_page: # 如果有下一页
kwargs[
- 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
- if prev_page:
+ 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container' # 构建下一页URL
+ if prev_page: # 如果有上一页
kwargs[
- 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container'
- kwargs['form'] = comment_form
- kwargs['article_comments'] = article_comments
- kwargs['p_comments'] = p_comments
+ 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container' # 构建上一页URL
+ kwargs['form'] = comment_form # 评论表单
+ kwargs['article_comments'] = article_comments # 所有评论
+ kwargs['p_comments'] = p_comments # 当前页评论
kwargs['comment_count'] = len(
- article_comments) if article_comments else 0
+ article_comments) if article_comments else 0 # 评论总数
- kwargs['next_article'] = self.object.next_article
- kwargs['prev_article'] = self.object.prev_article
+ kwargs['next_article'] = self.object.next_article # 下一篇文章
+ kwargs['prev_article'] = self.object.prev_article # 上一篇文章
- context = super(ArticleDetailView, self).get_context_data(**kwargs)
+ context = super(ArticleDetailView, self).get_context_data(**kwargs) # 调用父类方法
article = self.object
# Action Hook, 通知插件"文章详情已获取"
- hooks.run_action('after_article_body_get', article=article, request=self.request)
+ hooks.run_action('after_article_body_get', article=article, request=self.request) # 执行插件动作
return context
class CategoryDetailView(ArticleListView):
'''
- 分类目录列表
+ 分类目录列表视图
'''
- page_type = "分类目录归档"
+ page_type = "分类目录归档" # 页面类型
def get_queryset_data(self):
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
+ # 获取分类目录下的文章数据
+ slug = self.kwargs['category_name'] # 从URL获取分类slug
+ category = get_object_or_404(Category, slug=slug) # 获取分类对象
- categoryname = category.name
- self.categoryname = categoryname
+ categoryname = category.name # 分类名称
+ self.categoryname = categoryname # 保存分类名称
categorynames = list(
- map(lambda c: c.name, category.get_sub_categorys()))
+ map(lambda c: c.name, category.get_sub_categorys())) # 获取所有子分类名称
article_list = Article.objects.filter(
- category__name__in=categorynames, status='p')
+ category__name__in=categorynames, status='p') # 过滤分类下的文章
return article_list
def get_queryset_cache_key(self):
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
- categoryname = category.name
- self.categoryname = categoryname
+ # 生成分类目录缓存键
+ slug = self.kwargs['category_name'] # 分类slug
+ category = get_object_or_404(Category, slug=slug) # 分类对象
+ categoryname = category.name # 分类名称
+ self.categoryname = categoryname # 保存分类名称
cache_key = 'category_list_{categoryname}_{page}'.format(
- categoryname=categoryname, page=self.page_number)
+ categoryname=categoryname, page=self.page_number) # 包含分类名和页码的缓存键
return cache_key
def get_context_data(self, **kwargs):
-
+ # 获取分类目录页的上下文数据
categoryname = self.categoryname
try:
- categoryname = categoryname.split('/')[-1]
+ categoryname = categoryname.split('/')[-1] # 提取分类名称的最后部分
except BaseException:
pass
- kwargs['page_type'] = CategoryDetailView.page_type
- kwargs['tag_name'] = categoryname
- return super(CategoryDetailView, self).get_context_data(**kwargs)
+ kwargs['page_type'] = CategoryDetailView.page_type # 页面类型
+ kwargs['tag_name'] = categoryname # 分类名称
+ return super(CategoryDetailView, self).get_context_data(**kwargs) # 调用父类方法
class AuthorDetailView(ArticleListView):
'''
- 作者详情页
+ 作者详情页视图
'''
- page_type = '作者文章归档'
+ page_type = '作者文章归档' # 页面类型
def get_queryset_cache_key(self):
+ # 生成作者详情页缓存键
from uuslug import slugify
- author_name = slugify(self.kwargs['author_name'])
+ author_name = slugify(self.kwargs['author_name']) # 作者名称slug化
cache_key = 'author_{author_name}_{page}'.format(
- author_name=author_name, page=self.page_number)
+ author_name=author_name, page=self.page_number) # 包含作者名和页码的缓存键
return cache_key
def get_queryset_data(self):
- author_name = self.kwargs['author_name']
+ # 获取作者文章数据
+ author_name = self.kwargs['author_name'] # 作者名称
article_list = Article.objects.filter(
- author__username=author_name, type='a', status='p')
+ author__username=author_name, type='a', status='p') # 过滤作者文章
return article_list
def get_context_data(self, **kwargs):
- author_name = self.kwargs['author_name']
- kwargs['page_type'] = AuthorDetailView.page_type
- kwargs['tag_name'] = author_name
- return super(AuthorDetailView, self).get_context_data(**kwargs)
+ # 获取作者详情页的上下文数据
+ author_name = self.kwargs['author_name'] # 作者名称
+ kwargs['page_type'] = AuthorDetailView.page_type # 页面类型
+ kwargs['tag_name'] = author_name # 作者名称
+ return super(AuthorDetailView, self).get_context_data(**kwargs) # 调用父类方法
class TagDetailView(ArticleListView):
'''
- 标签列表页面
+ 标签列表页面视图
'''
- page_type = '分类标签归档'
+ page_type = '分类标签归档' # 页面类型
def get_queryset_data(self):
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
+ # 获取标签下的文章数据
+ slug = self.kwargs['tag_name'] # 标签slug
+ tag = get_object_or_404(Tag, slug=slug) # 标签对象
+ tag_name = tag.name # 标签名称
+ self.name = tag_name # 保存标签名称
article_list = Article.objects.filter(
- tags__name=tag_name, type='a', status='p')
+ tags__name=tag_name, type='a', status='p') # 过滤标签文章
return article_list
def get_queryset_cache_key(self):
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
+ # 生成标签详情页缓存键
+ slug = self.kwargs['tag_name'] # 标签slug
+ tag = get_object_or_404(Tag, slug=slug) # 标签对象
+ tag_name = tag.name # 标签名称
+ self.name = tag_name # 保存标签名称
cache_key = 'tag_{tag_name}_{page}'.format(
- tag_name=tag_name, page=self.page_number)
+ tag_name=tag_name, page=self.page_number) # 包含标签名和页码的缓存键
return cache_key
def get_context_data(self, **kwargs):
- # tag_name = self.kwargs['tag_name']
- tag_name = self.name
- kwargs['page_type'] = TagDetailView.page_type
- kwargs['tag_name'] = tag_name
- return super(TagDetailView, self).get_context_data(**kwargs)
+ # 获取标签详情页的上下文数据
+ tag_name = self.name # 标签名称
+ kwargs['page_type'] = TagDetailView.page_type # 页面类型
+ kwargs['tag_name'] = tag_name # 标签名称
+ return super(TagDetailView, self).get_context_data(**kwargs) # 调用父类方法
class ArchivesView(ArticleListView):
'''
- 文章归档页面
+ 文章归档页面视图
'''
- page_type = '文章归档'
- paginate_by = None
- page_kwarg = None
- template_name = 'blog/article_archives.html'
+ page_type = '文章归档' # 页面类型
+ paginate_by = None # 不分页
+ page_kwarg = None # 无页码参数
+ template_name = 'blog/article_archives.html' # 归档页面模板
def get_queryset_data(self):
- return Article.objects.filter(status='p').all()
+ # 获取所有文章数据
+ return Article.objects.filter(status='p').all() # 所有已发布文章
def get_queryset_cache_key(self):
- cache_key = 'archives'
+ # 生成归档页面缓存键
+ cache_key = 'archives' # 固定缓存键
return cache_key
class LinkListView(ListView):
- model = Links
- template_name = 'blog/links_list.html'
+ # 友情链接列表视图
+ model = Links # 关联的模型
+ template_name = 'blog/links_list.html' # 模板名称
def get_queryset(self):
- return Links.objects.filter(is_enable=True)
+ # 获取启用的友情链接
+ return Links.objects.filter(is_enable=True) # 过滤启用的链接
class EsSearchView(SearchView):
+ # Elasticsearch搜索视图
def get_context(self):
- paginator, page = self.build_page()
+ # 获取搜索结果的上下文数据
+ paginator, page = self.build_page() # 构建分页
context = {
- "query": self.query,
- "form": self.form,
- "page": page,
- "paginator": paginator,
- "suggestion": None,
+ "query": self.query, # 搜索查询
+ "form": self.form, # 搜索表单
+ "page": page, # 当前页
+ "paginator": paginator, # 分页器
+ "suggestion": None, # 搜索建议
}
if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
- context["suggestion"] = self.results.query.get_spelling_suggestion()
- context.update(self.extra_context())
+ context["suggestion"] = self.results.query.get_spelling_suggestion() # 获取拼写建议
+ context.update(self.extra_context()) # 更新额外上下文
return context
-@csrf_exempt
+@csrf_exempt # 免除CSRF保护
def fileupload(request):
"""
- 该方法需自己写调用端来上传图片,该方法仅提供图床功能
+ 文件上传方法,提供图床功能
:param request:
:return:
"""
- if request.method == 'POST':
- sign = request.GET.get('sign', None)
- if not sign:
- return HttpResponseForbidden()
- if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
- return HttpResponseForbidden()
- response = []
- for filename in request.FILES:
- timestr = timezone.now().strftime('%Y/%m/%d')
- imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
- fname = u''.join(str(filename))
- isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0
- base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr)
- if not os.path.exists(base_dir):
- os.makedirs(base_dir)
- savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}"))
- if not savepath.startswith(base_dir):
+ if request.method == 'POST': # 只处理POST请求
+ sign = request.GET.get('sign', None) # 获取签名
+ if not sign: # 验证签名是否存在
+ return HttpResponseForbidden() # 无签名返回403
+ if not sign == get_sha256(get_sha256(settings.SECRET_KEY)): # 验证签名是否正确
+ return HttpResponseForbidden() # 签名错误返回403
+ response = [] # 响应列表
+ for filename in request.FILES: # 遍历所有上传的文件
+ timestr = timezone.now().strftime('%Y/%m/%d') # 时间目录
+ imgextensions = ['jpg', 'png', 'jpeg', 'bmp'] # 图片扩展名
+ fname = u''.join(str(filename)) # 文件名
+ isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0 # 判断是否为图片
+ base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr) # 基础目录
+ if not os.path.exists(base_dir): # 如果目录不存在
+ os.makedirs(base_dir) # 创建目录
+ savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) # 保存路径
+ if not savepath.startswith(base_dir): # 路径安全检查
return HttpResponse("only for post")
- with open(savepath, 'wb+') as wfile:
+ with open(savepath, 'wb+') as wfile: # 写入文件
for chunk in request.FILES[filename].chunks():
wfile.write(chunk)
- if isimage:
+ if isimage: # 如果是图片
from PIL import Image
- image = Image.open(savepath)
- image.save(savepath, quality=20, optimize=True)
- url = static(savepath)
- response.append(url)
- return HttpResponse(response)
+ image = Image.open(savepath) # 打开图片
+ image.save(savepath, quality=20, optimize=True) # 压缩图片
+ url = static(savepath) # 生成静态文件URL
+ response.append(url) # 添加到响应列表
+ return HttpResponse(response) # 返回URL列表
else:
- return HttpResponse("only for post")
+ return HttpResponse("only for post") # 非POST请求返回提示
def page_not_found_view(
request,
exception,
template_name='blog/error_page.html'):
+ # 404错误页面视图
if exception:
- logger.error(exception)
- url = request.get_full_path()
+ logger.error(exception) # 记录错误日志
+ url = request.get_full_path() # 获取请求URL
return render(request,
template_name,
{'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
- 'statuscode': '404'},
- status=404)
+ 'statuscode': '404'}, # 错误信息
+ status=404) # 返回404状态码
def server_error_view(request, template_name='blog/error_page.html'):
+ # 500错误页面视图
return render(request,
template_name,
{'message': _('Sorry, the server is busy, please click the home page to see other?'),
- 'statuscode': '500'},
- status=500)
+ 'statuscode': '500'}, # 错误信息
+ status=500) # 返回500状态码
def permission_denied_view(
request,
exception,
template_name='blog/error_page.html'):
+ # 403错误页面视图
if exception:
- logger.error(exception)
+ logger.error(exception) # 记录错误日志
return render(
request, template_name, {
'message': _('Sorry, you do not have permission to access this page?'),
- 'statuscode': '403'}, status=403)
+ 'statuscode': '403'}, status=403) # 返回403状态码
def clean_cache_view(request):
- cache.clear()
- return HttpResponse('ok')
+ # 清理缓存视图
+ cache.clear() # 清除所有缓存
+ return HttpResponse('ok') # 返回成功响应
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/admin.py b/src/DjangoBlog/comments/admin.py
index dbde14f..a243fc7 100644
--- a/src/DjangoBlog/comments/admin.py
+++ b/src/DjangoBlog/comments/admin.py
@@ -5,45 +5,50 @@ from django.utils.translation import gettext_lazy as _
def disable_commentstatus(modeladmin, request, queryset):
- queryset.update(is_enable=False)
+ # 禁用选中评论的动作
+ queryset.update(is_enable=False) # 将is_enable字段设置为False
def enable_commentstatus(modeladmin, request, queryset):
- queryset.update(is_enable=True)
+ # 启用选中评论的动作
+ queryset.update(is_enable=True) # 将is_enable字段设置为True
-disable_commentstatus.short_description = _('Disable comments')
-enable_commentstatus.short_description = _('Enable comments')
+disable_commentstatus.short_description = _('Disable comments') # 动作显示名称
+enable_commentstatus.short_description = _('Enable comments') # 动作显示名称
class CommentAdmin(admin.ModelAdmin):
- list_per_page = 20
+ # 评论管理类
+ list_per_page = 20 # 每页显示20条记录
list_display = (
'id',
'body',
- 'link_to_userinfo',
- 'link_to_article',
+ 'link_to_userinfo', # 自定义用户信息链接字段
+ 'link_to_article', # 自定义文章链接字段
'is_enable',
- 'creation_time')
- list_display_links = ('id', 'body', 'is_enable')
- list_filter = ('is_enable',)
- exclude = ('creation_time', 'last_modify_time')
- actions = [disable_commentstatus, enable_commentstatus]
- raw_id_fields = ('author', 'article')
- search_fields = ('body',)
+ 'creation_time') # 列表页显示的字段
+ list_display_links = ('id', 'body', 'is_enable') # 可点击链接的字段
+ list_filter = ('is_enable',) # 右侧过滤器
+ exclude = ('creation_time', 'last_modify_time') # 排除的字段
+ actions = [disable_commentstatus, enable_commentstatus] # 管理员动作列表
+ raw_id_fields = ('author', 'article') # 使用原始ID字段(外键搜索)
+ search_fields = ('body',) # 搜索字段
def link_to_userinfo(self, obj):
- info = (obj.author._meta.app_label, obj.author._meta.model_name)
- link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
+ # 创建用户信息链接的自定义方法
+ 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))
+ (link, obj.author.nickname if obj.author.nickname else obj.author.email)) # 显示昵称或邮箱
def link_to_article(self, obj):
- info = (obj.article._meta.app_label, obj.article._meta.model_name)
- link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
+ # 创建文章链接的自定义方法
+ info = (obj.article._meta.app_label, obj.article._meta.model_name) # 获取文章模型信息
+ link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) # 生成文章编辑链接
return format_html(
- u'%s' % (link, obj.article.title))
+ u'%s' % (link, obj.article.title)) # 显示文章标题
- link_to_userinfo.short_description = _('User')
- link_to_article.short_description = _('Article')
+ link_to_userinfo.short_description = _('User') # 字段显示名称
+ link_to_article.short_description = _('Article') # 字段显示名称
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/apps.py b/src/DjangoBlog/comments/apps.py
index ff01b77..99b13a8 100644
--- a/src/DjangoBlog/comments/apps.py
+++ b/src/DjangoBlog/comments/apps.py
@@ -2,4 +2,4 @@ from django.apps import AppConfig
class CommentsConfig(AppConfig):
- name = 'comments'
+ name = 'comments' # 指定应用的Python路径为'comments'
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/forms.py b/src/DjangoBlog/comments/forms.py
index e83737d..5b258e5 100644
--- a/src/DjangoBlog/comments/forms.py
+++ b/src/DjangoBlog/comments/forms.py
@@ -5,9 +5,10 @@ from .models import Comment
class CommentForm(ModelForm):
+ # 父评论ID字段,使用隐藏输入框,非必填
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
class Meta:
- model = Comment
- fields = ['body']
+ model = Comment # 指定模型为Comment
+ fields = ['body'] # 表单只包含body字段
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/migrations/0001_initial.py b/src/DjangoBlog/comments/migrations/0001_initial.py
index 61d1e53..47b78cd 100644
--- a/src/DjangoBlog/comments/migrations/0001_initial.py
+++ b/src/DjangoBlog/comments/migrations/0001_initial.py
@@ -8,31 +8,32 @@ import django.utils.timezone
class Migration(migrations.Migration):
- initial = True
+ initial = True # 标记为初始迁移
dependencies = [
- ('blog', '0001_initial'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('blog', '0001_initial'), # 依赖blog应用的初始迁移
+ 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='上级评论')),
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), # 自增主键
+ ('body', models.TextField(max_length=300, verbose_name='正文')), # 评论正文,最大长度300
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 创建时间
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # 最后修改时间
+ ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # 是否启用显示
+ ('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',
+ 'verbose_name': '评论', # 单数显示名称
+ 'verbose_name_plural': '评论', # 复数显示名称
+ 'ordering': ['-id'], # 按ID倒序排列
+ 'get_latest_by': 'id', # 指定获取最新记录的字段
},
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py
index 17c44db..2e83f07 100644
--- a/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py
+++ b/src/DjangoBlog/comments/migrations/0002_alter_comment_is_enable.py
@@ -6,13 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('comments', '0001_initial'),
+ ('comments', '0001_initial'), # 依赖comments应用的初始迁移
]
operations = [
+ # 修改Comment模型的is_enable字段默认值
migrations.AlterField(
model_name='comment',
name='is_enable',
- field=models.BooleanField(default=False, verbose_name='是否显示'),
+ field=models.BooleanField(default=False, verbose_name='是否显示'), # 默认值从True改为False
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
index a1ca970..a30344e 100644
--- a/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
+++ b/src/DjangoBlog/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
@@ -9,52 +9,61 @@ 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'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL), # 依赖用户模型
+ ('blog', '0005_alter_article_options_alter_category_options_and_more'), # 依赖blog应用的第五个迁移
+ ('comments', '0002_alter_comment_is_enable'), # 依赖comments应用的第二个迁移
]
operations = [
+ # 修改Comment模型的元数据选项(国际化)
migrations.AlterModelOptions(
name='comment',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'},
),
+ # 删除created_time字段
migrations.RemoveField(
model_name='comment',
name='created_time',
),
+ # 删除last_mod_time字段
migrations.RemoveField(
model_name='comment',
name='last_mod_time',
),
+ # 添加creation_time字段
migrations.AddField(
model_name='comment',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # 添加last_modify_time字段
migrations.AddField(
model_name='comment',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
+ # 修改article字段的verbose_name(国际化)
migrations.AlterField(
model_name='comment',
name='article',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'),
),
+ # 修改author字段的verbose_name(国际化)
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'),
),
+ # 修改is_enable字段的verbose_name(国际化)
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='enable'),
),
+ # 修改parent_comment字段的verbose_name(国际化)
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'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/models.py b/src/DjangoBlog/comments/models.py
index 7c3bbc8..3429fa4 100644
--- a/src/DjangoBlog/comments/models.py
+++ b/src/DjangoBlog/comments/models.py
@@ -9,31 +9,33 @@ 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)
+ # 评论模型
+ body = models.TextField('正文', max_length=300) # 评论正文,最大长度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)
+ on_delete=models.CASCADE) # 关联作者,级联删除
article = models.ForeignKey(
Article,
verbose_name=_('article'),
- on_delete=models.CASCADE)
+ on_delete=models.CASCADE) # 关联文章,级联删除
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
- on_delete=models.CASCADE)
+ on_delete=models.CASCADE) # 自关联父级评论,可为空
is_enable = models.BooleanField(_('enable'),
- default=False, blank=False, null=False)
+ default=False, blank=False, null=False) # 是否启用,默认禁用
class Meta:
- ordering = ['-id']
- verbose_name = _('comment')
- verbose_name_plural = verbose_name
- get_latest_by = 'id'
+ ordering = ['-id'] # 按ID倒序排列
+ verbose_name = _('comment') # 单数显示名称
+ verbose_name_plural = verbose_name # 复数显示名称
+ get_latest_by = 'id' # 指定获取最新记录的字段
def __str__(self):
- return self.body
+ # 对象的字符串表示
+ return self.body # 返回评论正文
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/tests.py b/src/DjangoBlog/comments/tests.py
index 2a7f55f..eabd95c 100644
--- a/src/DjangoBlog/comments/tests.py
+++ b/src/DjangoBlog/comments/tests.py
@@ -12,68 +12,71 @@ from djangoblog.utils import get_max_articleid_commentid
class CommentsTest(TransactionTestCase):
def setUp(self):
- self.client = Client()
- self.factory = RequestFactory()
+ # 测试初始化设置
+ self.client = Client() # 创建测试客户端
+ self.factory = RequestFactory() # 创建请求工厂
from blog.models import BlogSettings
- value = BlogSettings()
- value.comment_need_review = True
+ value = BlogSettings() # 创建博客设置
+ value.comment_need_review = True # 设置评论需要审核
value.save()
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
- password="liangliangyy1")
+ password="liangliangyy1") # 创建超级用户
def update_article_comment_status(self, article):
- comments = article.comment_set.all()
+ # 更新文章评论状态为启用
+ comments = article.comment_set.all() # 获取文章的所有评论
for comment in comments:
- comment.is_enable = True
+ comment.is_enable = True # 启用评论
comment.save()
def test_validate_comment(self):
- self.client.login(username='liangliangyy1', password='liangliangyy1')
+ # 测试评论验证功能
+ self.client.login(username='liangliangyy1', password='liangliangyy1') # 登录用户
- category = Category()
- category.name = "categoryccc"
+ 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 = 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})
+ 'article_id': article.id}) # 生成评论提交URL
response = self.client.post(comment_url,
{
- 'body': '123ffffffffff'
+ 'body': '123ffffffffff' # 提交评论内容
})
- self.assertEqual(response.status_code, 302)
+ 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)
+ article = Article.objects.get(pk=article.pk) # 重新获取文章
+ self.assertEqual(len(article.comment_list()), 0) # 验证评论数为0(需要审核)
+ self.update_article_comment_status(article) # 更新评论状态为启用
- self.assertEqual(len(article.comment_list()), 1)
+ self.assertEqual(len(article.comment_list()), 1) # 验证评论数变为1
response = self.client.post(comment_url,
{
- 'body': '123ffffffffff',
+ 'body': '123ffffffffff', # 提交第二条评论
})
- self.assertEqual(response.status_code, 302)
+ 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
+ article = Article.objects.get(pk=article.pk) # 重新获取文章
+ self.update_article_comment_status(article) # 更新评论状态为启用
+ self.assertEqual(len(article.comment_list()), 2) # 验证评论数变为2
+ parent_comment_id = article.comment_list()[0].id # 获取第一条评论的ID作为父评论
response = self.client.post(comment_url,
{
@@ -89,21 +92,21 @@ class CommentsTest(TransactionTestCase):
[ddd](http://www.baidu.com)
- ''',
- 'parent_comment_id': parent_comment_id
+ ''', # 提交包含Markdown格式的评论
+ 'parent_comment_id': parent_comment_id # 设置父评论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)
+ 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) # 验证评论数变为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() # 获取最大文章ID和评论ID
+ self.assertIsNotNone(s) # 验证返回值不为空
from comments.utils import send_comment_email
- send_comment_email(comment)
+ send_comment_email(comment) # 测试发送评论邮件
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/urls.py b/src/DjangoBlog/comments/urls.py
index 7df3fab..f7320a3 100644
--- a/src/DjangoBlog/comments/urls.py
+++ b/src/DjangoBlog/comments/urls.py
@@ -1,11 +1,11 @@
-from django.urls import path
-
from . import views
-app_name = "comments"
+app_name = "comments" # 定义应用的命名空间
+
urlpatterns = [
+ # 文章评论提交URL
path(
- 'article//postcomment',
- views.CommentPostView.as_view(),
- name='postcomment'),
-]
+ 'article//postcomment', # URL模式:文章ID/postcomment
+ views.CommentPostView.as_view(), # 关联评论提交视图
+ name='postcomment'), # URL名称
+]
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/utils.py b/src/DjangoBlog/comments/utils.py
index f01dba7..6a1f025 100644
--- a/src/DjangoBlog/comments/utils.py
+++ b/src/DjangoBlog/comments/utils.py
@@ -9,20 +9,21 @@ 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()}"
+ # 发送评论相关邮件
+ site = get_current_site().domain # 获取当前站点域名
+ subject = _('Thanks for your comment') # 邮件主题
+ article_url = f"https://{site}{comment.article.get_absolute_url()}" # 构建文章完整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)
+ %(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:
+ if comment.parent_comment: # 如果是对父评论的回复
html_content = _("""Your comment on %(article_title)s
has
received a reply.
%(comment_body)s
@@ -31,8 +32,8 @@ def send_comment_email(comment):
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)
+ 'comment_body': comment.parent_comment.body} # 评论回复通知邮件内容
+ tomail = comment.parent_comment.author.email # 父评论作者的邮箱
+ send_email([tomail], subject, html_content) # 发送回复通知邮件
except Exception as e:
- logger.error(e)
+ logger.error(e) # 记录邮件发送错误日志
\ No newline at end of file
diff --git a/src/DjangoBlog/comments/views.py b/src/DjangoBlog/comments/views.py
index ad9b2b9..173e592 100644
--- a/src/DjangoBlog/comments/views.py
+++ b/src/DjangoBlog/comments/views.py
@@ -13,51 +13,55 @@ from .models import Comment
class CommentPostView(FormView):
- form_class = CommentForm
- template_name = 'blog/article_detail.html'
+ # 评论提交视图
+ form_class = CommentForm # 使用评论表单
+ template_name = 'blog/article_detail.html' # 模板名称
- @method_decorator(csrf_protect)
+ @method_decorator(csrf_protect) # CSRF保护装饰器
def dispatch(self, *args, **kwargs):
- return super(CommentPostView, self).dispatch(*args, **kwargs)
+ return super(CommentPostView, self).dispatch(*args, **kwargs) # 调用父类dispatch方法
def get(self, request, *args, **kwargs):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
- url = article.get_absolute_url()
- return HttpResponseRedirect(url + "#comments")
+ # 处理GET请求,重定向到文章详情页的评论区域
+ article_id = self.kwargs['article_id'] # 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取文章对象
+ url = article.get_absolute_url() # 获取文章绝对URL
+ return HttpResponseRedirect(url + "#comments") # 重定向到文章评论区域
def form_invalid(self, form):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
+ # 表单验证失败时的处理
+ article_id = self.kwargs['article_id'] # 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取文章对象
return self.render_to_response({
- 'form': form,
- 'article': article
+ 'form': form, # 返回包含错误信息的表单
+ 'article': article # 文章对象
})
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
- user = self.request.user
- author = BlogUser.objects.get(pk=user.pk)
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
-
- if article.comment_status == 'c' or article.status == 'c':
- raise ValidationError("该文章评论已关闭.")
- comment = form.save(False)
- comment.article = article
+ user = self.request.user # 获取当前用户
+ author = BlogUser.objects.get(pk=user.pk) # 获取博客用户对象
+ article_id = self.kwargs['article_id'] # 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取文章对象
+
+ if article.comment_status == 'c' or article.status == 'c': # 检查评论是否关闭
+ raise ValidationError("该文章评论已关闭.") # 抛出验证错误
+
+ comment = form.save(False) # 创建评论对象但不保存到数据库
+ comment.article = article # 设置评论关联的文章
from djangoblog.utils import get_blog_setting
- settings = get_blog_setting()
- if not settings.comment_need_review:
- comment.is_enable = True
- comment.author = author
+ settings = get_blog_setting() # 获取博客设置
+ if not settings.comment_need_review: # 如果评论不需要审核
+ comment.is_enable = True # 直接启用评论
+ comment.author = author # 设置评论作者
- if form.cleaned_data['parent_comment_id']:
+ if form.cleaned_data['parent_comment_id']: # 如果有父评论ID
parent_comment = Comment.objects.get(
- pk=form.cleaned_data['parent_comment_id'])
- comment.parent_comment = parent_comment
+ pk=form.cleaned_data['parent_comment_id']) # 获取父评论对象
+ comment.parent_comment = parent_comment # 设置父评论
- comment.save(True)
+ comment.save(True) # 保存评论到数据库
return HttpResponseRedirect(
"%s#div-comment-%d" %
- (article.get_absolute_url(), comment.pk))
+ (article.get_absolute_url(), comment.pk)) # 重定向到新评论的位置
\ No newline at end of file