diff --git a/src%2FDjangoBlog-master/accounts代码注释 b/src%2FDjangoBlog-master/accounts代码注释 new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/src%2FDjangoBlog-master/accounts代码注释 @@ -0,0 +1 @@ +undefined \ No newline at end of file diff --git a/src%2FDjangoBlog-master/代码注释 b/src%2FDjangoBlog-master/代码注释 new file mode 100644 index 0000000..e69de29 diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py index 32e483c..13b3f92 100644 --- a/src/DjangoBlog-master/accounts/admin.py +++ b/src/DjangoBlog-master/accounts/admin.py @@ -9,14 +9,22 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """ + 自定义用户创建表单,用于Django管理后台 + 扩展了默认的用户创建功能,添加密码确认逻辑 + """ password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: model = BlogUser - fields = ('email',) + fields = ('email',) #ZXY: 创建用户时只需要邮箱字段 def clean_password2(self): + """ + 验证两次输入的密码是否一致 + 这是Django表单的标准验证方法命名 + """ # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -25,19 +33,25 @@ 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"]) + user.set_password(self.cleaned_data["password1"]) #ZXY: 使用Django的密码哈希 if commit: - user.source = 'adminsite' + user.source = 'adminsite' #ZXY: 标记用户来源为管理后台 user.save() return user class BlogUserChangeForm(UserChangeForm): + """ + 自定义用户修改表单 + 继承Django默认的用户修改表单,可以自定义字段显示 + """ class Meta: model = BlogUser - fields = '__all__' + fields = '__all__' #ZXY: 包含所有字段 field_classes = {'username': UsernameField} def __init__(self, *args, **kwargs): @@ -45,8 +59,14 @@ class BlogUserChangeForm(UserChangeForm): class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm + """ + 自定义用户管理类 + 配置Django管理后台中用户的显示和编辑方式 + """ + form = BlogUserChangeForm #ZXY: 修改用户时使用的表单 + add_form = BlogUserCreationForm #ZXY: 创建用户时使用的表单 + + #ZXY: 列表页显示的字段 list_display = ( 'id', 'nickname', @@ -54,6 +74,6 @@ class BlogUserAdmin(UserAdmin): 'email', 'last_login', 'date_joined', - 'source') - list_display_links = ('id', 'username') - ordering = ('-id',) + 'source') #ZXY: 显示用户来源 + list_display_links = ('id', 'username') #ZXY: 可点击跳转到编辑页的字段 + ordering = ('-id',) #ZXY: 按ID倒序排列 diff --git a/src/DjangoBlog-master/accounts/apps.py b/src/DjangoBlog-master/accounts/apps.py index 9b3fc5a..c60fbcd 100644 --- a/src/DjangoBlog-master/accounts/apps.py +++ b/src/DjangoBlog-master/accounts/apps.py @@ -2,4 +2,8 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): - name = 'accounts' + """ + 账户应用的Django配置类 + 用于配置应用的元数据和启动时的初始化行为 + """ + name = 'accounts' #ZXY: 应用的Python路径 diff --git a/src/DjangoBlog-master/accounts/forms.py b/src/DjangoBlog-master/accounts/forms.py index fce4137..0ca5373 100644 --- a/src/DjangoBlog-master/accounts/forms.py +++ b/src/DjangoBlog-master/accounts/forms.py @@ -9,8 +9,13 @@ from .models import BlogUser class LoginForm(AuthenticationForm): + """ + 用户登录表单 + 继承Django的AuthenticationForm,添加Bootstrap样式支持 + """ def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) + #ZXY: 自定义表单字段的widget,添加placeholder和CSS类 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['password'].widget = widgets.PasswordInput( @@ -18,9 +23,14 @@ class LoginForm(AuthenticationForm): class RegisterForm(UserCreationForm): + """ + 用户注册表单 + 扩展Django的UserCreationForm,添加邮箱字段和样式支持 + """ def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) + #ZXY: 为所有字段添加Bootstrap样式和占位符文本 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['email'].widget = widgets.EmailInput( @@ -31,17 +41,26 @@ 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() #ZXY: 使用项目中自定义的用户模型 + fields = ("username", "email") #ZXY: 表单包含的字段 class ForgetPasswordForm(forms.Form): + """ + 忘记密码重置表单 + 包含新密码设置、邮箱验证和验证码确认 + """ + #ZXY: 新密码字段 - 使用Django的密码输入组件 new_password1 = forms.CharField( label=_("New password"), widget=forms.PasswordInput( @@ -52,6 +71,7 @@ class ForgetPasswordForm(forms.Form): ), ) + #ZXY: 确认密码字段 - 标签没有国际化,需要改进 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -62,6 +82,7 @@ class ForgetPasswordForm(forms.Form): ), ) + #ZXY: 邮箱字段 - 用于识别用户和发送验证码 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -72,6 +93,7 @@ class ForgetPasswordForm(forms.Form): ), ) + #ZXY: 验证码字段 - 用于安全验证 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -83,15 +105,22 @@ class ForgetPasswordForm(forms.Form): ) def clean_new_password2(self): + """ + 验证两次输入的新密码是否一致 + 并使用Django的密码验证器检查密码强度 + """ 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) + password_validation.validate_password(password2) #ZXY: Django密码强度验证 return password2 def clean_email(self): + """ + 验证邮箱是否存在系统中 + """ user_email = self.cleaned_data.get("email") if not BlogUser.objects.filter( email=user_email @@ -101,6 +130,10 @@ class ForgetPasswordForm(forms.Form): return user_email def clean_code(self): + """ + 验证邮箱验证码是否正确 + 调用utils模块的验证功能 + """ code = self.cleaned_data.get("code") error = utils.verify( email=self.cleaned_data.get("email"), @@ -112,6 +145,10 @@ class ForgetPasswordForm(forms.Form): class ForgetPasswordCodeForm(forms.Form): + """ + 获取忘记密码验证码的表单 + 简单的邮箱输入表单,用于请求发送验证码邮件 + """ email = forms.EmailField( label=_('Email'), ) diff --git a/src/DjangoBlog-master/accounts/migrations/0001_initial.py b/src/DjangoBlog-master/accounts/migrations/0001_initial.py index d2fbcab..b6bbe8e 100644 --- a/src/DjangoBlog-master/accounts/migrations/0001_initial.py +++ b/src/DjangoBlog-master/accounts/migrations/0001_initial.py @@ -7,18 +7,26 @@ import django.utils.timezone class Migration(migrations.Migration): + """ + 初始迁移类 - 创建BlogUser用户模型的数据库表结构 + 迁移是Django用于管理数据库模式变更的机制 + """ - initial = True + initial = True #ZXY: 标记这是应用的初始迁移 dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), + ('auth', '0012_alter_user_first_name_max_length'), #ZXY: 依赖Django认证系统的迁移 ] operations = [ + #ZXY: 创建BlogUser表的操作 migrations.CreateModel( name='BlogUser', fields=[ + #ZXY: ID主键字段 - Django自动创建的自增BigInteger主键 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + + #ZXY: 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')), @@ -29,20 +37,25 @@ class Migration(migrations.Migration): ('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')), + + #ZXY: 自定义扩展字段 ↓ ('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='创建来源')), + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZXY: 记录创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZXY: 最后修改时间 + ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), #ZXY: 用户注册来源 + + #ZXY: Django权限系统的多对多关系字段 ('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': '用户', #ZXY: 单数显示名称 + 'verbose_name_plural': '用户', #ZXY: 复数显示名称 + 'ordering': ['-id'], #ZXY: 默认按ID倒序排列 + 'get_latest_by': 'id', #ZXY: 获取最新记录的依据字段 }, managers=[ + #ZXY: 指定模型管理器,使用Django默认的用户管理器 ('objects', django.contrib.auth.models.UserManager()), ], ), diff --git a/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py index 1a9f509..4c4cc76 100644 --- a/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py @@ -1,43 +1,65 @@ # Generated by Django 4.2.5 on 2023-09-06 13:13 +#ZXY: 第二次迁移 - 主要进行字段名称国际化和结构调整 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): + """ + 第二次迁移 - 修复字段命名和国际化问题 + 这次迁移展示了数据库模式演变的常见模式 + """ dependencies = [ - ('accounts', '0001_initial'), + ('accounts', '0001_initial'), #ZXY: 依赖第一次迁移 ] operations = [ + #ZXY: 操作1: 修改模型选项 - 将中文显示名改为英文 migrations.AlterModelOptions( name='bloguser', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, + options={ + 'get_latest_by': 'id', + 'ordering': ['-id'], + 'verbose_name': 'user', #ZXY: 改为英文单数 + 'verbose_name_plural': 'user'}, #ZXY: 改为英文复数 ), + + #ZXY: 操作2: 删除旧的创建时间字段 migrations.RemoveField( model_name='bloguser', - name='created_time', + name='created_time', #ZXY: 移除旧的字段命名 ), + + #ZXY: 操作3: 删除旧的修改时间字段 migrations.RemoveField( model_name='bloguser', - name='last_mod_time', + name='last_mod_time', #ZXY: 移除旧的字段命名 ), + + #ZXY: 操作4: 添加新的创建时间字段(国际化命名) migrations.AddField( model_name='bloguser', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + + #ZXY: 操作5: 添加新的修改时间字段(国际化命名) migrations.AddField( model_name='bloguser', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), ), + + #ZXY: 操作6: 修改昵称字段的显示名称(国际化) migrations.AlterField( model_name='bloguser', name='nickname', - field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), + field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), #ZXY 改为英文 ), + + #ZXY: 操作7: 修改来源字段的显示名称(国际化) migrations.AlterField( model_name='bloguser', name='source', diff --git a/src/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py index 3baddbb..76dc501 100644 --- a/src/DjangoBlog-master/accounts/models.py +++ b/src/DjangoBlog-master/accounts/models.py @@ -9,27 +9,72 @@ from djangoblog.utils import get_current_site # Create your models here. class BlogUser(AbstractUser): - nickname = models.CharField(_('nick name'), max_length=100, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - source = models.CharField(_('create source'), max_length=100, blank=True) + """ + 博客用户模型,继承自Django内置的AbstractUser基类 + 扩展了标准用户模型,增加博客系统特有的字段 + """ + + #ZXY: 昵称字段:用户显示名称,可为空 + nickname = models.CharField( + _('nick name'), #ZXY: 国际化标签,在admin中显示为"昵称" + max_length=100, #ZXY: 最大长度100个字符 + blank=True) #ZXY: 允许为空,非必填字段 + + #ZXY: 创建时间:记录用户账号创建的时间点 + creation_time = models.DateTimeField( + _('creation time'), #ZXY: 显示为"创建时间" + default=now) #ZXY: 默认值为当前时间,自动记录创建时间戳 + + #ZXY: 最后修改时间:记录用户信息最后一次修改的时间 + last_modify_time = models.DateTimeField( + _('last modify time'), #ZXY: 显示为"最后修改时间" + default=now) #ZXY: 默认值为当前时间 + + #ZXY: 注册来源:记录用户是通过什么渠道注册的 + source = models.CharField( + _('create source'), #ZXY: 显示为"创建来源" + max_length=100, #ZXY: 最大长度100个字符 + blank=True) #ZXY: 允许为空 def get_absolute_url(self): + """ + 获取用户详情页的URL路径(不含域名) + 用于Django的通用视图和admin中的"查看现场"链接 + Returns: + str: 用户详情页的URL路径,如 '/author/testuser/' + """ return reverse( 'blog:author_detail', kwargs={ - 'author_name': self.username}) + 'author_name': self.username}) #ZXY: 使用用户名作为URL参数 def __str__(self): + """ + 定义模型的字符串表示,在admin和shell中显示 + 这里使用邮箱而不是用户名,便于管理员识别用户 + Returns: + str: 用户的邮箱地址 + """ return self.email def get_full_url(self): - site = get_current_site().domain - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) + """ + 获取包含完整域名的用户详情页URL + 用于邮件通知、外部链接等需要完整URL的场景 + Returns: + str: 完整的用户URL,如 'https://example.com/author/testuser/' + """ + site = get_current_site().domain #ZXY: 获取当前站点的域名 + url = "https://{site}{path}".format( + site=site, + path=self.get_absolute_url()) #ZXY: 组合域名和路径 return url class Meta: - ordering = ['-id'] - verbose_name = _('user') - verbose_name_plural = verbose_name - get_latest_by = 'id' + """ + 模型的元数据配置类 + 用于定义模型级别的选项和配置 + """ + ordering = ['-id'] #ZXY: 默认按ID降序排列,新的用户显示在前面 + verbose_name = _('user') #ZXY: 单数形式的显示名称 + verbose_name_plural = verbose_name #ZXY: 复数形式与单数相同 + get_latest_by = 'id' #ZXY: 指定获取最新记录的字段 diff --git a/src/DjangoBlog-master/accounts/tests.py b/src/DjangoBlog-master/accounts/tests.py index 6893411..04e6b67 100644 --- a/src/DjangoBlog-master/accounts/tests.py +++ b/src/DjangoBlog-master/accounts/tests.py @@ -12,78 +12,121 @@ from . import utils # Create your tests here. class AccountTest(TestCase): + """ + 账户功能测试套件 + 测试用户注册、登录、密码重置等核心功能的正确性 + 继承Django的TestCase,提供数据库事务支持和断言方法 + """ + def setUp(self): - self.client = Client() - self.factory = RequestFactory() + """ + 测试前置设置方法 + 在每个测试方法执行前运行,用于准备测试数据 + 避免测试间的数据污染,确保测试独立性 + """ + self.client = Client() #ZXY: Django测试客户端,用于模拟HTTP请求 + self.factory = RequestFactory() #ZXY: 请求工厂,用于创建请求对象进行视图测试 + #ZXY: 创建测试用户 - 使用create_user方法创建普通用户 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", - password="12345678" + password="12345678" #ZXY: 测试密码,实际项目中应使用更强密码 ) - self.new_test = "xxx123--=" + self.new_test = "xxx123--=" #ZXY: 测试用的新密码 def test_validate_account(self): - site = get_current_site().domain + """ + 测试账户验证功能 + 验证超级用户创建、登录权限和管理后台访问 + 这是一个综合性测试,覆盖用户生命周期多个环节 + """ + site = get_current_site().domain #ZXY: 获取当前站点域名 + #ZXY: 创建超级用户 - 测试管理员账户创建功能 user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", - password="qwer!@#$ggg") - testuser = BlogUser.objects.get(username='liangliangyy1') + password="qwer!@#$ggg") #ZXY: 复杂密码测试 + testuser = BlogUser.objects.get(username='liangliangyy1') #ZXY: 从数据库检索用户验证创建成功 + #ZXY: 测试登录功能 - 使用测试客户端模拟用户登录 loginresult = self.client.login( username='liangliangyy1', password='qwer!@#$ggg') - self.assertEqual(loginresult, True) + self.assertEqual(loginresult, True) #ZXY: 断言登录成功返回True + + #ZXY: 测试管理后台访问权限 - 超级用户应该能访问/admin/ response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) #ZXY: 断言HTTP 200响应 + #ZXY: 创建测试分类 - 为后续文章测试准备数据 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() + #ZXY: 创建测试文章 - 验证用户与文章的关联关系 article = Article() article.title = "nicetitleaaa" article.body = "nicecontentaaa" - article.author = user + article.author = user #ZXY: 关联刚创建的超级用户 article.category = category - article.type = 'a' - article.status = 'p' + article.type = 'a' #ZXY: 文章类型 + article.status = 'p' #ZXY: 发布状态 article.save() + #ZXY: 测试文章管理页面访问 - 验证用户对自有文章的编辑权限 response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) #ZXY: 断言可以正常访问文章管理页 def test_validate_register(self): + """ + 测试用户注册完整流程 + 覆盖注册表单提交、邮箱验证、账户激活等关键步骤 + 验证新用户能否成功注册并正常使用系统 + """ + #ZXY: 验证注册前用户不存在 self.assertEquals( 0, len( BlogUser.objects.filter( - email='user123@user.com'))) + email='user123@user.com'))) #ZXY: 断言邮箱尚未注册 + + #ZXY: 提交注册表单 - 模拟用户注册请求 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', + 'password1': 'password123!q@wE#R$T', #ZXY: 强密码测试 + 'password2': 'password123!q@wE#R$T', #ZXY: 确认密码 }) + + #ZXY: 验证用户创建成功 self.assertEquals( 1, len( BlogUser.objects.filter( - email='user123@user.com'))) - user = BlogUser.objects.filter(email='user123@user.com')[0] + email='user123@user.com'))) #ZXY: 断言用户已创建 + user = BlogUser.objects.filter(email='user123@user.com')[0] #ZXY: 获取新创建的用户 + + #ZXY: 生成邮箱验证签名 - 测试安全验证机制 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) + path=path, id=user.id, sign=sign) #ZXY: 构建验证URL + + #ZXY: 测试邮箱验证页面访问 response = self.client.get(url) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) #ZXY: 断言验证页面可访问 + #ZXY: 测试新用户登录功能 self.client.login(username='user1233', password='password123!q@wE#R$T') user = BlogUser.objects.filter(email='user123@user.com')[0] + + #ZXY: 提升用户权限进行进一步测试 user.is_superuser = True user.is_staff = True user.save() - delete_sidebar_cache() + delete_sidebar_cache() #ZXY: 清理缓存 + + #ZXY: 创建测试数据验证完整功能流程 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() @@ -100,85 +143,118 @@ class AccountTest(TestCase): article.status = 'p' article.save() + #ZXY: 测试文章管理权限 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) + #ZXY: 测试退出登录功能 response = self.client.get(reverse('account:logout')) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #ZXY: 退出可能重定向 + #ZXY: 验证退出后权限失效 response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #ZXY: 应该无法直接访问 + #ZXY: 测试重新登录 response = self.client.post(reverse('account:login'), { 'username': 'user1233', 'password': 'password123' }) self.assertIn(response.status_code, [301, 302, 200]) + #ZXY: 验证重新登录后的权限恢复 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() + code = generate_code() #ZXY: 生成随机验证码 + + #ZXY: 测试验证码存储功能 utils.set_code(to_email, code) + #ZXY: 测试邮件发送功能 - 在实际环境中会真正发送邮件 utils.send_verify_email(to_email, code) + #ZXY: 测试正确验证码验证 err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) + self.assertEqual(err, None) #ZXY: 断言验证成功,返回None + #ZXY: 测试错误验证码验证 err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) + self.assertEqual(type(err), str) #ZXY: 断言验证失败返回错误信息字符串 def test_forget_password_email_code_success(self): + """ + 测试忘记密码验证码请求 - 成功场景 + 验证系统能正确处理合法的密码重置请求 + """ resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict(email="admin@admin.com") + data=dict(email="admin@admin.com") #ZXY: 使用已注册的邮箱 ) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok") + self.assertEqual(resp.status_code, 200) #ZXY: 断言请求成功 + self.assertEqual(resp.content.decode("utf-8"), "ok") #ZXY: 断言返回正确消息 def test_forget_password_email_code_fail(self): + """ + 测试忘记密码验证码请求 - 失败场景 + 验证系统能正确处理各种异常输入情况 + """ + #ZXY: 测试空数据提交 resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict() + data=dict() #ZXY: 空数据 ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #ZXY: 断言错误处理 + #ZXY: 测试无效邮箱格式 resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict(email="admin@com") + data=dict(email="admin@com") #ZXY: 无效邮箱格式 ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #ZXY: 断言格式验证 def test_forget_password_email_success(self): + """ + 测试完整密码重置流程 - 成功场景 + 验证用户能通过验证码成功重置密码 + """ code = generate_code() - utils.set_code(self.blog_user.email, code) + utils.set_code(self.blog_user.email, code) #ZXY: 存储验证码 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code=code, + code=code, #ZXY: 正确的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.status_code, 302) #ZXY: 断言重定向响应 - # 验证用户密码是否修改成功 + #ZXY: 验证用户密码是否修改成功 - 从数据库重新获取用户验证密码变更 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) + self.assertNotEqual(blog_user, None) #ZXY: 断言用户存在 + self.assertEqual(blog_user.check_password(data["new_password1"]), True) #ZXY: 断言密码验证通过 def test_forget_password_email_not_user(self): + """ + 测试密码重置 - 用户不存在场景 + 验证系统对未注册邮箱的处理 + """ data = dict( new_password1=self.new_test, new_password2=self.new_test, - email="123@123.com", + email="123@123.com", #ZXY: 未注册的邮箱 code="123456", ) resp = self.client.post( @@ -186,22 +262,26 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 200) #ZXY: 断言停留在原页面(表单验证失败) def test_forget_password_email_code_error(self): + """ + 测试密码重置 - 验证码错误场景 + 验证系统对错误验证码的拒绝处理 + """ code = generate_code() - utils.set_code(self.blog_user.email, code) + utils.set_code(self.blog_user.email, code) #ZXY: 存储正确验证码 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111", #ZXY: 错误的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 200) #ZXY: 断言表单验证失败,停留在原页面 diff --git a/src/DjangoBlog-master/accounts/urls.py b/src/DjangoBlog-master/accounts/urls.py index 107a801..9aa7f71 100644 --- a/src/DjangoBlog-master/accounts/urls.py +++ b/src/DjangoBlog-master/accounts/urls.py @@ -4,25 +4,41 @@ from django.urls import re_path from . import views from .forms import LoginForm -app_name = "accounts" +app_name = "accounts" #ZXY: 应用命名空间,用于反向解析URL时避免冲突 -urlpatterns = [re_path(r'^login/$', - views.LoginView.as_view(success_url='/'), - name='login', - kwargs={'authentication_form': LoginForm}), +""" +账户模块的URL配置 +定义用户相关的所有页面路由和对应的视图处理函数 +""" +urlpatterns = [ + #ZXY: 登录页面 - 使用正则表达式匹配路径 + re_path(r'^login/$', + views.LoginView.as_view(success_url='/'), #ZXY: 类视图,登录成功后跳转到首页 + name='login', #ZXY: URL名称,用于反向解析 + kwargs={'authentication_form': LoginForm}), #ZXY: 传递额外的参数到视图 + + #ZXY: 注册页面 re_path(r'^register/$', - views.RegisterView.as_view(success_url="/"), + views.RegisterView.as_view(success_url="/"), #ZXY: 注册成功后跳转到首页 name='register'), + + #ZXY: 退出登录 re_path(r'^logout/$', - views.LogoutView.as_view(), + views.LogoutView.as_view(), #ZXY: 退出登录视图 name='logout'), + + #ZXY: 账户操作结果页面 - 使用path函数,更现代的URL定义方式 path(r'account/result.html', - views.account_result, + views.account_result, #ZXY: 函数视图 name='result'), + + #ZXY: 忘记密码页面 re_path(r'^forget_password/$', - views.ForgetPasswordView.as_view(), + views.ForgetPasswordView.as_view(), #ZXY: 密码重置表单视图 name='forget_password'), + + #ZXY: 获取忘记密码验证码 re_path(r'^forget_password_code/$', - views.ForgetPasswordEmailCode.as_view(), + views.ForgetPasswordEmailCode.as_view(), #ZXY: 验证码发送API name='forget_password_code'), ] diff --git a/src/DjangoBlog-master/accounts/user_login_backend.py b/src/DjangoBlog-master/accounts/user_login_backend.py index 73cdca1..65a0a4a 100644 --- a/src/DjangoBlog-master/accounts/user_login_backend.py +++ b/src/DjangoBlog-master/accounts/user_login_backend.py @@ -4,23 +4,69 @@ from django.contrib.auth.backends import ModelBackend class EmailOrUsernameModelBackend(ModelBackend): """ - 允许使用用户名或邮箱登录 + 自定义用户认证后端 + 扩展Django默认认证,支持使用邮箱或用户名登录 + + 继承自ModelBackend,重写authenticate方法实现多字段登录 + Django默认只支持用户名登录,这个后端增加了邮箱登录支持 """ def authenticate(self, request, username=None, password=None, **kwargs): + """ + 用户认证核心方法 + 根据输入判断是邮箱还是用户名,然后进行密码验证 + + Args: + request: HttpRequest对象,包含请求信息 + username (str): 用户输入的用户名或邮箱 + password (str): 用户输入的密码 + **kwargs: 其他可能的参数 + + Returns: + User: 认证成功返回用户对象 + None: 认证失败返回None + + Logic: + 1. 判断输入是否包含@符号 → 邮箱登录 + 2. 不包含@符号 → 用户名登录 + 3. 查询数据库获取用户 + 4. 验证密码是否正确 + """ + + #ZXY: 步骤1:判断登录方式(邮箱 or 用户名) if '@' in username: - kwargs = {'email': username} + #ZXY: 输入包含@符号,认为是邮箱登录 + kwargs = {'email': username} #ZXY: 构建邮箱查询条件 else: - kwargs = {'username': username} + #ZXY: 输入不包含@符号,认为是用户名登录 + kwargs = {'username': username} #ZXY: 构建用户名查询条件 try: + #ZXY: 步骤2:根据查询条件获取用户对象 + #ZXY: get_user_model() 获取项目中使用的用户模型 user = get_user_model().objects.get(**kwargs) + + #ZXY: 步骤3:验证密码 + #ZXY: check_password() 方法会自动处理密码哈希比较 if user.check_password(password): - return user + return user #ZXY: 密码正确,返回用户对象 except get_user_model().DoesNotExist: + #ZXY: 用户不存在,返回None表示认证失败 return None def get_user(self, username): + """ + 根据用户ID获取用户对象 + 用于session认证时从数据库加载用户信息 + + Args: + username (str): 实际上是用户ID(Django的命名历史遗留) + + Returns: + User: 用户对象 + None: 用户不存在 + """ try: + #ZXY: 根据主键(用户ID)获取用户对象 return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: return None diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py index 4b94bdf..4976524 100644 --- a/src/DjangoBlog-master/accounts/utils.py +++ b/src/DjangoBlog-master/accounts/utils.py @@ -7,43 +7,97 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import send_email +#ZXY: 验证码有效期配置:5分钟,使用timedelta便于时间计算 _code_ttl = timedelta(minutes=5) - def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 + """发送邮箱验证码邮件 + 用于密码重置等需要邮箱验证的场景 Args: - to_mail: 接受邮箱 - subject: 邮件主题 - code: 验证码 + to_mail (str): 接收邮件的邮箱地址 + code (str): 6位数字验证码 + subject (str): 邮件主题,默认为"验证邮件"(支持国际化) + + Example: + >>> send_verify_email("user@example.com", "123456") + # 发送包含验证码123456的邮件到user@example.com """ + #ZXY: 构建邮件正文,使用国际化翻译 + #ZXY: %(code)s是字符串格式化占位符,会被实际的验证码替换 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) + + #ZXY: 调用项目通用的邮件发送函数 + send_email( + [to_mail], #ZXY: 收件人列表,包装成列表形式 + subject, #ZXY: 邮件主题 + html_content) #ZXY:邮件内容(HTML格式) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 + """ + 验证用户输入的验证码是否正确 + Args: - email: 请求邮箱 - code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + email (str): 用户邮箱,作为缓存键 + code (str): 用户输入的验证码 + + Returns: + Optional[str]: + - 验证成功返回 None + - 验证失败返回错误信息字符串 + + Note: + 当前设计问题:返回错误字符串的方式不够优雅 + 建议改进:使用异常抛出,让调用方处理异常 + + Example: + >>> result = verify("user@example.com", "123456") + >>> if result: + ... print(f"验证失败: {result}") """ + #ZXY: 从缓存中获取该邮箱对应的验证码 cache_code = get_code(email) + + #ZXY: 比较缓存中的验证码和用户输入的验证码 if cache_code != code: + #ZXY: 验证失败,返回错误信息(支持国际化) return gettext("Verification code error") + #ZXY: 验证成功,返回None + return None def set_code(email: str, code: str): + """ + 将验证码存储到缓存中,设置5分钟有效期 + + Args: + email (str): 邮箱地址,作为缓存键 + code (str): 要存储的验证码 + + Technical: + 使用Django的缓存框架,支持多种缓存后端(数据库、Redis、内存等) + timedelta.seconds 将时间差转换为秒数 + """ """设置code""" - cache.set(email, code, _code_ttl.seconds) + cache.set( + email, #ZXY: 缓存键:使用邮箱地址 + code, #ZXY: 缓存值:验证码字符串 + _code_ttl.seconds) #ZXY: 过期时间:5分钟(300秒) def get_code(email: str) -> typing.Optional[str]: + """ + 从缓存中获取指定邮箱的验证码 + + Args: + email (str): 邮箱地址,作为缓存键 + + Returns: + Optional[str]: + - 存在验证码则返回验证码字符串 + - 不存在或已过期则返回None + """ """获取code""" - return cache.get(email) + return cache.get(email) #ZXY: 从缓存中获取值,不存在返回None diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py index ae67aec..5aceab3 100644 --- a/src/DjangoBlog-master/accounts/views.py +++ b/src/DjangoBlog-master/accounts/views.py @@ -32,25 +32,60 @@ logger = logging.getLogger(__name__) # Create your views here. class RegisterView(FormView): + """ + 用户注册视图类,继承自Django的FormView + 处理用户注册的全流程:表单展示、数据验证、用户创建、邮件发送 + """ + + #ZXY: 指定使用的表单类 form_class = RegisterForm + + #ZXY: 指定渲染的模板路径 template_name = 'account/registration_form.html' @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): + """ + 重写dispatch方法,添加CSRF保护装饰器 + CSRF保护:防止跨站请求伪造攻击,确保表单提交来自本站 + Returns: + HttpResponse: 处理后的HTTP响应 + """ return super(RegisterView, self).dispatch(*args, **kwargs) def form_valid(self, form): + """ + 表单数据验证成功后的处理逻辑 + 这是注册流程的核心方法,包含用户创建和邮件发送 + Args: + form: 已验证通过的RegisterForm实例 + Returns: + HttpResponseRedirect: 重定向到结果页面 + """ if form.is_valid(): + #ZXY: 第一步:创建用户对象但不立即保存到数据库 + #ZXY: save(False)表示只创建对象实例,不执行数据库INSERT user = form.save(False) - user.is_active = False - user.source = 'Register' + + #ZXY: 第二步:设置用户状态和属性 + user.is_active = False #ZXY: 邮箱验证前用户处于未激活状态,无法登录 + user.source = 'Register' #ZXY: 记录注册来源,便于后续统计分析 + + #ZXY: 第三步:保存用户到数据库 + #ZXY: save(True)表示立即执行数据库保存操作 user.save(True) - site = get_current_site().domain + + #ZXY: 第四步:准备邮箱验证所需数据 + site = get_current_site().domain #ZXY: 获取当前站点域名 + #ZXY: 生成安全签名:对密钥+用户ID进行双重SHA256哈希,防止篡改 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + #ZXY: 开发环境下使用本地地址,生产环境使用真实域名 if settings.DEBUG: - site = '127.0.0.1:8000' - path = reverse('account:result') + site = '127.0.0.1:8000' #ZXY: 开发环境固定地址 + + # 第五步:构建邮箱验证链接 + path = reverse('account:result') #ZXY: 生成结果页面的URL路径 url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( site=site, path=path, id=user.id, sign=sign) @@ -64,141 +99,215 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + + #ZXY: 第七步:发送验证邮件 send_email( emailto=[ - user.email, + user.email, #ZXY: 收件人列表,这里只有注册用户 ], - title='验证您的电子邮箱', - content=content) + title='验证您的电子邮箱', #ZXY: 邮件标题 + content=content) #ZXY: 邮件正文内容 + #ZXY: 步骤8:重定向到注册结果页面 url = reverse('accounts:result') + \ '?type=register&id=' + str(user.id) return HttpResponseRedirect(url) else: + #ZXY: 表单验证失败,重新显示表单页 return self.render_to_response({ 'form': form }) class LogoutView(RedirectView): - url = '/login/' + """ + 用户退出登录视图 + 功能:处理用户退出,清除session,重定向到登录页 + """ + url = '/login/' #ZXY: 退出后重定向的URL - @method_decorator(never_cache) + @method_decorator(never_cache) #ZXY: 禁止缓存,确保每次都是最新状态 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() + """ + 处理GET请求退出登录 + """ + logout(request) #ZXY: Django内置的退出函数,清除session + delete_sidebar_cache() #ZXY: 清除侧边栏缓存 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 #ZXY: 登录表单 + template_name = 'account/login.html' #ZXY: 登录页面模板 + success_url = '/' #ZXY: 登录成功后的默认跳转页面 + redirect_field_name = REDIRECT_FIELD_NAME #ZXY: 重定向字段名,默认为'next' + login_ttl = 2626560 #ZXY: "记住我"功能的session有效期(一个月,单位:秒) + + @method_decorator(sensitive_post_parameters('password')) #ZXY: 敏感参数保护,不在错误报告中显示密码 + @method_decorator(csrf_protect) #ZXY: CSRF保护 + @method_decorator(never_cache) #ZXY: 禁止缓存 def dispatch(self, request, *args, **kwargs): - + """ + 添加安全装饰器的dispatch方法 + """ return super(LoginView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): + """ + 添加上下文数据,主要是重定向URL + """ redirect_to = self.request.GET.get(self.redirect_field_name) if redirect_to is None: - redirect_to = '/' + redirect_to = '/' #ZXY: 默认重定向到首页 kwargs['redirect_to'] = redirect_to return super(LoginView, self).get_context_data(**kwargs) def form_valid(self, form): + """ + 登录表单验证成功后的处理 + """ + #ZXY: 使用Django内置的AuthenticationForm进行认证 form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): - delete_sidebar_cache() - logger.info(self.redirect_field_name) + delete_sidebar_cache() #ZXY: 清除侧边栏缓存 + logger.info(self.redirect_field_name) #ZXY: 记录日志 + #ZXY: 执行用户登录 auth.login(self.request, form.get_user()) + + #ZXY: 处理"记住我"功能 if self.request.POST.get("remember"): + #ZXY: 设置session过期时间为一个月 self.request.session.set_expiry(self.login_ttl) return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') + #ZXY: return HttpResponseRedirect('/') else: + #ZXY: 认证失败,重新显示表单 return self.render_to_response({ 'form': form }) def get_success_url(self): - + """ + 获取登录成功后的跳转URL + 支持next参数指定的重定向 + """ redirect_to = self.request.POST.get(self.redirect_field_name) + #ZXY: 安全检查:确保重定向URL在允许的host范围内 if not url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts=[ self.request.get_host()]): - redirect_to = self.success_url + redirect_to = self.success_url #ZXY: 不安全的URL使用默认首页 return redirect_to def account_result(request): - type = request.GET.get('type') - id = request.GET.get('id') + """ + 账户操作结果页面视图函数 + 处理注册结果和邮箱验证结果展示 + """ + type = request.GET.get('type') #ZXY: 操作类型:register或validation + id = request.GET.get('id') #ZXY: 用户ID + + + user = get_object_or_404(get_user_model(), id=id) #ZXY: 获取用户对象,不存在返回404 + logger.info(type) #ZXY: 记录操作类型日志 - user = get_object_or_404(get_user_model(), id=id) - logger.info(type) + #ZXY: 如果用户已激活,直接跳转到首页 if user.is_active: return HttpResponseRedirect('/') if type and type in ['register', 'validation']: if type == 'register': + #ZXY: 注册成功页面 content = ''' 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 ''' title = '注册成功' else: + #ZXY: 邮箱验证处理 c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) sign = request.GET.get('sign') + + #ZXY: 签名验证,防止URL篡改 if sign != c_sign: - return HttpResponseForbidden() + return HttpResponseForbidden() #ZXY: 签名不匹配,返回403禁止访问 + + #ZXY: 激活用户账号 user.is_active = True user.save() content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' + + #ZXY: 渲染结果页面 return render(request, 'account/result.html', { 'title': title, 'content': content }) else: + #ZXY: 无效的操作类型,跳转到首页 return HttpResponseRedirect('/') class ForgetPasswordView(FormView): - form_class = ForgetPasswordForm - template_name = 'account/forget_password.html' + """ + 忘记密码视图 + 功能:处理用户密码重置 + """ + form_class = ForgetPasswordForm #ZXY: 忘记密码表单 + template_name = 'account/forget_password.html' #ZXY: 模板路径 def form_valid(self, form): + """ + 表单验证成功后的密码重置逻辑 + """ if form.is_valid(): + #ZXY: 根据邮箱查找用户 blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() + + #ZXY: 使用Django的密码哈希函数设置新密码 blog_user.password = make_password(form.cleaned_data["new_password2"]) - blog_user.save() + blog_user.save() #ZXY: 保存到数据库 + + #ZXY: 重定向到登录页面 return HttpResponseRedirect('/login/') else: + #ZXY: 表单验证失败,重新显示表单 return self.render_to_response({'form': form}) class ForgetPasswordEmailCode(View): + """ + 忘记密码邮箱验证码发送视图 + 功能:接收邮箱地址,发送密码重置验证码 + """ def post(self, request: HttpRequest): + """ + 处理POST请求,发送验证码邮件 + """ form = ForgetPasswordCodeForm(request.POST) if not form.is_valid(): - return HttpResponse("错误的邮箱") - to_email = form.cleaned_data["email"] + return HttpResponse("错误的邮箱") #ZXY: 表单验证失败 + to_email = form.cleaned_data["email"] #ZXY: 获取邮箱地址 + #ZXY: 生成6位验证码 code = generate_code() + + #ZXY: 发送验证码邮件 utils.send_verify_email(to_email, code) + + #ZXY: 将验证码存储到缓存(5分钟有效期) utils.set_code(to_email, code) - return HttpResponse("ok") + return HttpResponse("ok") #ZXY: 返回成功响应 diff --git a/src/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/blog/admin.py index 46c3420..91cbeab 100644 --- a/src/DjangoBlog-master/blog/admin.py +++ b/src/DjangoBlog-master/blog/admin.py @@ -6,44 +6,44 @@ from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ # Register your models here. -from .models import Article +from .models import Article #ZNY 导入Article模型 -class ArticleForm(forms.ModelForm): - # body = forms.CharField(widget=AdminPagedownWidget()) +class ArticleForm(forms.ModelForm): #ZNY 定义文章表单类 + # body = forms.CharField(widget=AdminPagedownWidget()) #ZNY 注释掉的Markdown编辑器字段 class Meta: - model = Article - fields = '__all__' + model = Article #ZNY 指定表单对应的模型 + fields = '__all__' #ZNY 包含所有模型字段 -def makr_article_publish(modeladmin, request, queryset): - queryset.update(status='p') +def makr_article_publish(modeladmin, request, queryset): #ZNY 发布文章的管理动作函数 + queryset.update(status='p') #ZNY 将选中文章状态更新为发布 -def draft_article(modeladmin, request, queryset): - queryset.update(status='d') +def draft_article(modeladmin, request, queryset): #ZNY 设为草稿的管理动作函数 + queryset.update(status='d') #ZNY 将选中文章状态更新为草稿 -def close_article_commentstatus(modeladmin, request, queryset): - queryset.update(comment_status='c') +def close_article_commentstatus(modeladmin, request, queryset): #ZNY 关闭评论的管理动作函数 + queryset.update(comment_status='c') #ZNY 将选中文章评论状态更新为关闭 -def open_article_commentstatus(modeladmin, request, queryset): - queryset.update(comment_status='o') +def open_article_commentstatus(modeladmin, request, queryset): #ZNY 打开评论的管理动作函数 + queryset.update(comment_status='o') #ZNY 将选中文章评论状态更新为打开 -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') #ZNY 设置管理动作显示名称 +draft_article.short_description = _('Draft selected articles') #ZNY 设置管理动作显示名称 +close_article_commentstatus.short_description = _('Close article comments') #ZNY 设置管理动作显示名称 +open_article_commentstatus.short_description = _('Open article comments') #ZNY 设置管理动作显示名称 -class ArticlelAdmin(admin.ModelAdmin): - list_per_page = 20 - search_fields = ('body', 'title') - form = ArticleForm - list_display = ( +class ArticlelAdmin(admin.ModelAdmin): #ZNY 文章模型管理类 + list_per_page = 20 #ZNY 每页显示20条记录 + search_fields = ('body', 'title') #ZNY 设置搜索字段 + form = ArticleForm #ZNY 指定使用的表单类 + list_display = ( #ZNY 设置列表页显示的字段 'id', 'title', 'author', @@ -53,60 +53,60 @@ class ArticlelAdmin(admin.ModelAdmin): 'status', 'type', 'article_order') - list_display_links = ('id', 'title') - list_filter = ('status', 'type', 'category') - filter_horizontal = ('tags',) - exclude = ('creation_time', 'last_modify_time') - view_on_site = True - actions = [ + list_display_links = ('id', 'title') #ZNY 设置可点击进入编辑页的字段 + list_filter = ('status', 'type', 'category') #ZNY 设置右侧过滤器字段 + filter_horizontal = ('tags',) #ZNY 设置标签字段使用水平过滤器 + exclude = ('creation_time', 'last_modify_time') #ZNY 排除不需要在表单中显示的字段 + view_on_site = True #ZNY 启用"在站点查看"功能 + actions = [ #ZNY 注册管理动作 makr_article_publish, draft_article, close_article_commentstatus, open_article_commentstatus] - def link_to_category(self, obj): - 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)) + def link_to_category(self, obj): #ZNY 自定义方法:生成分类链接 + info = (obj.category._meta.app_label, obj.category._meta.model_name) #ZNY 获取分类模型的app和模型名称 + link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) #ZNY 生成分类编辑页链接 + return format_html(u'%s' % (link, obj.category.name)) #ZNY 返回HTML格式的链接 - link_to_category.short_description = _('category') + link_to_category.short_description = _('category') #ZNY 设置自定义列显示名称 - def get_form(self, request, obj=None, **kwargs): - form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) - form.base_fields['author'].queryset = get_user_model( + def get_form(self, request, obj=None, **kwargs): #ZNY 重写获取表单方法 + form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) #ZNY 调用父类方法 + form.base_fields['author'].queryset = get_user_model( #ZNY 限制作者字段只能选择超级用户 ).objects.filter(is_superuser=True) - return form + return form #ZNY 返回修改后的表单 - def save_model(self, request, obj, form, change): - super(ArticlelAdmin, self).save_model(request, obj, form, change) + def save_model(self, request, obj, form, change): #ZNY 重写保存模型方法 + super(ArticlelAdmin, self).save_model(request, obj, form, change) #ZNY 调用父类保存方法 - def get_view_on_site_url(self, obj=None): - if obj: - url = obj.get_full_url() - return url - else: - from djangoblog.utils import get_current_site - site = get_current_site().domain - return site + def get_view_on_site_url(self, obj=None): #ZNY 重写获取站点查看URL方法 + if obj: #ZNY 如果有具体对象 + url = obj.get_full_url() #ZNY 获取文章的完整URL + return url #ZNY 返回文章URL + else: #ZNY 如果没有对象(列表页) + from djangoblog.utils import get_current_site #ZNY 导入获取当前站点函数 + site = get_current_site().domain #ZNY 获取当前站点域名 + return site #ZNY 返回站点域名 -class TagAdmin(admin.ModelAdmin): - exclude = ('slug', 'last_mod_time', 'creation_time') +class TagAdmin(admin.ModelAdmin): #ZNY 标签模型管理类 + exclude = ('slug', 'last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段 -class CategoryAdmin(admin.ModelAdmin): - list_display = ('name', 'parent_category', 'index') - exclude = ('slug', 'last_mod_time', 'creation_time') +class CategoryAdmin(admin.ModelAdmin): #ZNY 分类模型管理类 + list_display = ('name', 'parent_category', 'index') #ZNY 设置列表页显示的字段 + exclude = ('slug', 'last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段 -class LinksAdmin(admin.ModelAdmin): - exclude = ('last_mod_time', 'creation_time') +class LinksAdmin(admin.ModelAdmin): #ZNY 链接模型管理类 + exclude = ('last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段 -class SideBarAdmin(admin.ModelAdmin): - list_display = ('name', 'content', 'is_enable', 'sequence') - exclude = ('last_mod_time', 'creation_time') +class SideBarAdmin(admin.ModelAdmin): #ZNY 侧边栏模型管理类 + list_display = ('name', 'content', 'is_enable', 'sequence') #ZNY 设置列表页显示的字段 + exclude = ('last_modify_time', 'creation_time') #ZNY 排除不需要在表单中显示的字段 -class BlogSettingsAdmin(admin.ModelAdmin): - pass +class BlogSettingsAdmin(admin.ModelAdmin): #ZNY 博客设置模型管理类 + pass #ZNY 使用默认管理配置 diff --git a/src/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/blog/apps.py index 7930587..bdf6b45 100644 --- a/src/DjangoBlog-master/blog/apps.py +++ b/src/DjangoBlog-master/blog/apps.py @@ -1,5 +1,5 @@ -from django.apps import AppConfig +from django.apps import AppConfig #ZNY 导入Django应用配置基类 -class BlogConfig(AppConfig): - name = 'blog' +class BlogConfig(AppConfig): #ZNY 定义博客应用的配置类 + name = 'blog' #ZNY 指定应用的Python路径为'blog' \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/blog/context_processors.py index 73e3088..5d35a45 100644 --- a/src/DjangoBlog-master/blog/context_processors.py +++ b/src/DjangoBlog-master/blog/context_processors.py @@ -1,43 +1,43 @@ -import logging +import logging #ZNY 导入日志模块 -from django.utils import timezone +from django.utils import timezone #ZNY 导入Django时区工具 -from djangoblog.utils import cache, get_blog_setting -from .models import Category, Article +from djangoblog.utils import cache, get_blog_setting #ZNY 导入缓存工具和博客设置获取函数 +from .models import Category, Article #ZNY 导入分类和文章模型 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -def seo_processor(requests): - key = 'seo_processor' - value = cache.get(key) - if value: - return value - else: - logger.info('set processor cache.') - setting = get_blog_setting() - value = { - 'SITE_NAME': setting.site_name, - 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, - 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, - 'SITE_SEO_DESCRIPTION': setting.site_seo_description, - 'SITE_DESCRIPTION': setting.site_description, - 'SITE_KEYWORDS': setting.site_keywords, - 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', - 'ARTICLE_SUB_LENGTH': setting.article_sub_length, - 'nav_category_list': Category.objects.all(), - 'nav_pages': Article.objects.filter( - type='p', - status='p'), - 'OPEN_SITE_COMMENT': setting.open_site_comment, - 'BEIAN_CODE': setting.beian_code, - 'ANALYTICS_CODE': setting.analytics_code, - "BEIAN_CODE_GONGAN": setting.gongan_beiancode, - "SHOW_GONGAN_CODE": setting.show_gongan_code, - "CURRENT_YEAR": timezone.now().year, - "GLOBAL_HEADER": setting.global_header, - "GLOBAL_FOOTER": setting.global_footer, - "COMMENT_NEED_REVIEW": setting.comment_need_review, +def seo_processor(requests): #ZNY 定义SEO上下文处理器函数 + key = 'seo_processor' #ZNY 设置缓存键名 + value = cache.get(key) #ZNY 尝试从缓存获取数据 + if value: #ZNY 如果缓存存在 + return value #ZNY 直接返回缓存数据 + else: #ZNY 如果缓存不存在 + logger.info('set processor cache.') #ZNY 记录设置缓存日志 + setting = get_blog_setting() #ZNY 获取博客设置 + value = { #ZNY 构建上下文数据字典 + 'SITE_NAME': setting.site_name, #ZNY 网站名称 + 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, #ZNY 是否显示谷歌广告 + 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, #ZNY 谷歌广告代码 + 'SITE_SEO_DESCRIPTION': setting.site_seo_description, #ZNY 网站SEO描述 + 'SITE_DESCRIPTION': setting.site_description, #ZNY 网站描述 + 'SITE_KEYWORDS': setting.site_keywords, #ZNY 网站关键词 + 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', #ZNY 网站基础URL + 'ARTICLE_SUB_LENGTH': setting.article_sub_length, #ZNY 文章摘要长度 + 'nav_category_list': Category.objects.all(), #ZNY 导航分类列表 + 'nav_pages': Article.objects.filter( #ZNY 导航页面列表 + type='p', #ZNY 过滤类型为页面 + status='p'), #ZNY 过滤状态为发布 + 'OPEN_SITE_COMMENT': setting.open_site_comment, #ZNY 是否开启全站评论 + 'BEIAN_CODE': setting.beian_code, #ZNY ICP备案号 + 'ANALYTICS_CODE': setting.analytics_code, #ZNY 网站统计代码 + "BEIAN_CODE_GONGAN": setting.gongan_beiancode, #ZNY 公安备案号 + "SHOW_GONGAN_CODE": setting.show_gongan_code, #ZNY 是否显示公安备案 + "CURRENT_YEAR": timezone.now().year, #ZNY 当前年份 + "GLOBAL_HEADER": setting.global_header, #ZNY 全局头部内容 + "GLOBAL_FOOTER": setting.global_footer, #ZNY 全局尾部内容 + "COMMENT_NEED_REVIEW": setting.comment_need_review, #ZNY 评论是否需要审核 } - cache.set(key, value, 60 * 60 * 10) - return value + cache.set(key, value, 60 * 60 * 10) #ZNY 将数据存入缓存,有效期10小时 + return value #ZNY 返回上下文数据 diff --git a/src/DjangoBlog-master/blog/documents.py b/src/DjangoBlog-master/blog/documents.py index 0f1db7b..96cfb4a 100644 --- a/src/DjangoBlog-master/blog/documents.py +++ b/src/DjangoBlog-master/blog/documents.py @@ -7,21 +7,21 @@ from elasticsearch_dsl.connections import connections from blog.models import Article -ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') +ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') #ZNY 检查是否配置了Elasticsearch -if ELASTICSEARCH_ENABLED: - connections.create_connection( - hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) - from elasticsearch import Elasticsearch +if ELASTICSEARCH_ENABLED: #ZNY 如果Elasticsearch已启用 + connections.create_connection( #ZNY 创建Elasticsearch连接 + hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) #ZNY 从配置中获取主机地址 + from elasticsearch import Elasticsearch #ZNY 导入Elasticsearch客户端 - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - from elasticsearch.client import IngestClient + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) #ZNY 创建Elasticsearch实例 + from elasticsearch.client import IngestClient #ZNY 导入Ingest客户端 - c = IngestClient(es) + c = IngestClient(es) #ZNY 创建Ingest客户端实例 try: - c.get_pipeline('geoip') - except elasticsearch.exceptions.NotFoundError: - c.put_pipeline('geoip', body='''{ + c.get_pipeline('geoip') #ZNY 尝试获取geoip管道 + except elasticsearch.exceptions.NotFoundError: #ZNY 如果geoip管道不存在 + c.put_pipeline('geoip', body='''{ #ZNY 创建geoip管道 "description" : "Add geoip info", "processors" : [ { @@ -30,184 +30,184 @@ if ELASTICSEARCH_ENABLED: } } ] - }''') + }''') #ZNY 定义geoip处理管道配置 -class GeoIp(InnerDoc): - continent_name = Keyword() - country_iso_code = Keyword() - country_name = Keyword() - location = GeoPoint() +class GeoIp(InnerDoc): #ZNY 定义GeoIP内嵌文档类 + continent_name = Keyword() #ZNY 大洲名称 + country_iso_code = Keyword() #ZNY 国家ISO代码 + country_name = Keyword() #ZNY 国家名称 + location = GeoPoint() #ZNY 地理位置坐标 -class UserAgentBrowser(InnerDoc): - Family = Keyword() - Version = Keyword() +class UserAgentBrowser(InnerDoc): #ZNY 定义用户代理浏览器信息类 + Family = Keyword() #ZNY 浏览器家族 + Version = Keyword() #ZNY 浏览器版本 -class UserAgentOS(UserAgentBrowser): - pass +class UserAgentOS(UserAgentBrowser): #ZNY 定义用户代理操作系统信息类,继承自浏览器类 + pass #ZNY 继承父类字段 -class UserAgentDevice(InnerDoc): - Family = Keyword() - Brand = Keyword() - Model = Keyword() +class UserAgentDevice(InnerDoc): #ZNY 定义用户代理设备信息类 + Family = Keyword() #ZNY 设备家族 + Brand = Keyword() #ZNY 设备品牌 + Model = Keyword() #ZNY 设备型号 -class UserAgent(InnerDoc): - browser = Object(UserAgentBrowser, required=False) - os = Object(UserAgentOS, required=False) - device = Object(UserAgentDevice, required=False) - string = Text() - is_bot = Boolean() +class UserAgent(InnerDoc): #ZNY 定义完整的用户代理信息类 + browser = Object(UserAgentBrowser, required=False) #ZNY 浏览器信息对象 + os = Object(UserAgentOS, required=False) #ZNY 操作系统信息对象 + device = Object(UserAgentDevice, required=False) #ZNY 设备信息对象 + string = Text() #ZNY 原始用户代理字符串 + is_bot = Boolean() #ZNY 是否为机器人 -class ElapsedTimeDocument(Document): - url = Keyword() - time_taken = Long() - log_datetime = Date() - ip = Keyword() - geoip = Object(GeoIp, required=False) - useragent = Object(UserAgent, required=False) +class ElapsedTimeDocument(Document): #ZNY 定义性能耗时文档类 + url = Keyword() #ZNY 请求URL + time_taken = Long() #ZNY 耗时(毫秒) + log_datetime = Date() #ZNY 日志时间 + ip = Keyword() #ZNY IP地址 + geoip = Object(GeoIp, required=False) #ZNY GeoIP地理位置信息 + useragent = Object(UserAgent, required=False) #ZNY 用户代理信息 - class Index: - name = 'performance' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 + class Index: #ZNY 定义索引配置 + name = 'performance' #ZNY 索引名称 + settings = { #ZNY 索引设置 + "number_of_shards": 1, #ZNY 分片数量 + "number_of_replicas": 0 #ZNY 副本数量 } - class Meta: - doc_type = 'ElapsedTime' + class Meta: #ZNY 元数据配置 + doc_type = 'ElapsedTime' #ZNY 文档类型 -class ElaspedTimeDocumentManager: +class ElaspedTimeDocumentManager: #ZNY 性能耗时文档管理器类 @staticmethod - def build_index(): - from elasticsearch import Elasticsearch - client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - res = client.indices.exists(index="performance") - if not res: - ElapsedTimeDocument.init() + def build_index(): #ZNY 构建索引静态方法 + from elasticsearch import Elasticsearch #ZNY 导入Elasticsearch + client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) #ZNY 创建客户端 + res = client.indices.exists(index="performance") #ZNY 检查索引是否存在 + if not res: #ZNY 如果索引不存在 + ElapsedTimeDocument.init() #ZNY 初始化性能耗时文档索引 @staticmethod - def delete_index(): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='performance', ignore=[400, 404]) + def delete_index(): #ZNY 删除索引静态方法 + from elasticsearch import Elasticsearch #ZNY 导入Elasticsearch + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) #ZNY 创建客户端 + es.indices.delete(index='performance', ignore=[400, 404]) #ZNY 删除性能索引,忽略特定错误 @staticmethod - def create(url, time_taken, log_datetime, useragent, ip): - ElaspedTimeDocumentManager.build_index() - ua = UserAgent() - ua.browser = UserAgentBrowser() - ua.browser.Family = useragent.browser.family - ua.browser.Version = useragent.browser.version_string - - ua.os = UserAgentOS() - ua.os.Family = useragent.os.family - ua.os.Version = useragent.os.version_string - - ua.device = UserAgentDevice() - ua.device.Family = useragent.device.family - ua.device.Brand = useragent.device.brand - ua.device.Model = useragent.device.model - ua.string = useragent.ua_string - ua.is_bot = useragent.is_bot - - doc = ElapsedTimeDocument( - meta={ - 'id': int( + def create(url, time_taken, log_datetime, useragent, ip): #ZNY 创建性能记录静态方法 + ElaspedTimeDocumentManager.build_index() #ZNY 确保索引存在 + ua = UserAgent() #ZNY 创建用户代理对象 + ua.browser = UserAgentBrowser() #ZNY 创建浏览器信息对象 + ua.browser.Family = useragent.browser.family #ZNY 设置浏览器家族 + ua.browser.Version = useragent.browser.version_string #ZNY 设置浏览器版本 + + ua.os = UserAgentOS() #ZNY 创建操作系统信息对象 + ua.os.Family = useragent.os.family #ZNY 设置操作系统家族 + ua.os.Version = useragent.os.version_string #ZNY 设置操作系统版本 + + ua.device = UserAgentDevice() #ZNY 创建设备信息对象 + ua.device.Family = useragent.device.family #ZNY 设置设备家族 + ua.device.Brand = useragent.device.brand #ZNY 设置设备品牌 + ua.device.Model = useragent.device.model #ZNY 设置设备型号 + ua.string = useragent.ua_string #ZNY 设置原始用户代理字符串 + ua.is_bot = useragent.is_bot #ZNY 设置是否为机器人 + + doc = ElapsedTimeDocument( #ZNY 创建性能耗时文档 + meta={ #ZNY 文档元数据 + 'id': int( #ZNY 使用时间戳作为文档ID round( time.time() * 1000)) }, - url=url, - time_taken=time_taken, - log_datetime=log_datetime, - useragent=ua, ip=ip) - doc.save(pipeline="geoip") - - -class ArticleDocument(Document): - body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') - author = Object(properties={ - 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() + url=url, #ZNY 设置URL + time_taken=time_taken, #ZNY 设置耗时 + log_datetime=log_datetime, #ZNY 设置日志时间 + useragent=ua, ip=ip) #ZNY 设置用户代理和IP + doc.save(pipeline="geoip") #ZNY 保存文档并使用geoip管道处理 + + +class ArticleDocument(Document): #ZNY 定义文章文档类 + body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') #ZNY 正文,使用IK中文分词器 + title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') #ZNY 标题,使用IK中文分词器 + author = Object(properties={ #ZNY 作者对象 + 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), #ZNY 作者昵称 + 'id': Integer() #ZNY 作者ID }) - category = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() + category = Object(properties={ #ZNY 分类对象 + 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), #ZNY 分类名称 + 'id': Integer() #ZNY 分类ID }) - tags = Object(properties={ - 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), - 'id': Integer() + tags = Object(properties={ #ZNY 标签对象 + 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), #ZNY 标签名称 + 'id': Integer() #ZNY 标签ID }) - pub_time = Date() - status = Text() - comment_status = Text() - type = Text() - views = Integer() - article_order = Integer() - - class Index: - name = 'blog' - settings = { - "number_of_shards": 1, - "number_of_replicas": 0 + pub_time = Date() #ZNY 发布时间 + status = Text() #ZNY 文章状态 + comment_status = Text() #ZNY 评论状态 + type = Text() #ZNY 文章类型 + views = Integer() #ZNY 浏览量 + article_order = Integer() #ZNY 文章排序 + + class Index: #ZNY 定义索引配置 + name = 'blog' #ZNY 索引名称 + settings = { #ZNY 索引设置 + "number_of_shards": 1, #ZNY 分片数量 + "number_of_replicas": 0 #ZNY 副本数量 } - class Meta: - doc_type = 'Article' + class Meta: #ZNY 元数据配置 + doc_type = 'Article' #ZNY 文档类型 -class ArticleDocumentManager(): +class ArticleDocumentManager(): #ZNY 文章文档管理器类 - def __init__(self): - self.create_index() + def __init__(self): #ZNY 初始化方法 + self.create_index() #ZNY 创建索引 - def create_index(self): - ArticleDocument.init() + def create_index(self): #ZNY 创建索引方法 + ArticleDocument.init() #ZNY 初始化文章文档索引 - def delete_index(self): - from elasticsearch import Elasticsearch - es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) - es.indices.delete(index='blog', ignore=[400, 404]) + def delete_index(self): #ZNY 删除索引方法 + from elasticsearch import Elasticsearch #ZNY 导入Elasticsearch + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) #ZNY 创建客户端 + es.indices.delete(index='blog', ignore=[400, 404]) #ZNY 删除博客索引,忽略特定错误 - def convert_to_doc(self, articles): - return [ - ArticleDocument( + def convert_to_doc(self, articles): #ZNY 将文章转换为文档方法 + return [ #ZNY 返回文档列表 + ArticleDocument( #ZNY 创建文章文档 meta={ - 'id': article.id}, - body=article.body, - title=article.title, - author={ - 'nickname': article.author.username, - 'id': article.author.id}, - category={ - 'name': article.category.name, - 'id': article.category.id}, - tags=[ + 'id': article.id}, #ZNY 使用文章ID作为文档ID + body=article.body, #ZNY 设置正文 + title=article.title, #ZNY 设置标题 + author={ #ZNY 设置作者信息 + 'nickname': article.author.username, #ZNY 作者用户名 + 'id': article.author.id}, #ZNY 作者ID + category={ #ZNY 设置分类信息 + 'name': article.category.name, #ZNY 分类名称 + 'id': article.category.id}, #ZNY 分类ID + tags=[ #ZNY 设置标签列表 { - 'name': t.name, - 'id': t.id} for t in article.tags.all()], - pub_time=article.pub_time, - status=article.status, - comment_status=article.comment_status, - type=article.type, - views=article.views, - article_order=article.article_order) for article in articles] - - def rebuild(self, articles=None): - ArticleDocument.init() - articles = articles if articles else Article.objects.all() - docs = self.convert_to_doc(articles) - for doc in docs: - doc.save() - - def update_docs(self, docs): - for doc in docs: - doc.save() + 'name': t.name, #ZNY 标签名称 + 'id': t.id} for t in article.tags.all()], #ZNY 遍历所有标签 + pub_time=article.pub_time, #ZNY 设置发布时间 + status=article.status, #ZNY 设置文章状态 + comment_status=article.comment_status, #ZNY 设置评论状态 + type=article.type, #ZNY 设置文章类型 + views=article.views, #ZNY 设置浏览量 + article_order=article.article_order) for article in articles] #ZNY 设置文章排序 + + def rebuild(self, articles=None): #ZNY 重建索引方法 + ArticleDocument.init() #ZNY 重新初始化索引 + articles = articles if articles else Article.objects.all() #ZNY 获取所有文章或指定文章 + docs = self.convert_to_doc(articles) #ZNY 转换为文档格式 + for doc in docs: #ZNY 遍历所有文档 + doc.save() #ZNY 保存文档到Elasticsearch + + def update_docs(self, docs): #ZNY 更新文档方法 + for doc in docs: #ZNY 遍历文档列表 + doc.save() #ZNY 保存更新后的文档 diff --git a/src/DjangoBlog-master/blog/forms.py b/src/DjangoBlog-master/blog/forms.py index 715be76..a8e9ef6 100644 --- a/src/DjangoBlog-master/blog/forms.py +++ b/src/DjangoBlog-master/blog/forms.py @@ -1,19 +1,19 @@ -import logging +import logging #ZNY 导入日志模块 -from django import forms -from haystack.forms import SearchForm +from django import forms #ZNY 导入Django表单模块 +from haystack.forms import SearchForm #ZNY 导入Haystack搜索表单基类 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -class BlogSearchForm(SearchForm): - querydata = forms.CharField(required=True) +class BlogSearchForm(SearchForm): #ZNY 定义博客搜索表单类,继承自Haystack搜索表单 + querydata = forms.CharField(required=True) #ZNY 定义查询数据字段,设置为必填 - def search(self): - datas = super(BlogSearchForm, self).search() - if not self.is_valid(): - return self.no_query_found() + def search(self): #ZNY 重写搜索方法 + datas = super(BlogSearchForm, self).search() #ZNY 调用父类的搜索方法获取数据 + if not self.is_valid(): #ZNY 如果表单验证失败 + return self.no_query_found() #ZNY 返回无查询结果 - if self.cleaned_data['querydata']: - logger.info(self.cleaned_data['querydata']) - return datas + if self.cleaned_data['querydata']: #ZNY 如果查询数据存在且已清洗 + logger.info(self.cleaned_data['querydata']) #ZNY 记录查询关键词到日志 + return datas #ZNY 返回搜索结果数据 diff --git a/src/DjangoBlog-master/blog/management/commands/build_index.py b/src/DjangoBlog-master/blog/management/commands/build_index.py index 3c4acd7..6a725a5 100644 --- a/src/DjangoBlog-master/blog/management/commands/build_index.py +++ b/src/DjangoBlog-master/blog/management/commands/build_index.py @@ -1,18 +1,18 @@ -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand #ZNY 导入Django管理命令基类 from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \ - ELASTICSEARCH_ENABLED + ELASTICSEARCH_ENABLED #ZNY 导入Elasticsearch相关文档和管理器 -# TODO 参数化 -class Command(BaseCommand): - help = 'build search index' +# TODO 参数化 #ZNY 待办事项:将命令参数化 +class Command(BaseCommand): #ZNY 定义构建搜索索引命令类,继承BaseCommand + help = 'build search index' #ZNY 命令帮助信息 - def handle(self, *args, **options): - if ELASTICSEARCH_ENABLED: - ElaspedTimeDocumentManager.build_index() - manager = ElapsedTimeDocument() - manager.init() - manager = ArticleDocumentManager() - manager.delete_index() - manager.rebuild() + def handle(self, *args, **options): #ZNY 命令处理主方法 + if ELASTICSEARCH_ENABLED: #ZNY 检查Elasticsearch是否启用 + ElaspedTimeDocumentManager.build_index() #ZNY 构建性能耗时文档索引 + manager = ElapsedTimeDocument() #ZNY 创建性能耗时文档管理器实例 + manager.init() #ZNY 初始化性能耗时文档索引 + manager = ArticleDocumentManager() #ZNY 创建文章文档管理器实例 + manager.delete_index() #ZNY 删除现有文章索引 + manager.rebuild() #ZNY 重新构建文章索引 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/management/commands/build_search_words.py b/src/DjangoBlog-master/blog/management/commands/build_search_words.py index cfe7e0d..6a31431 100644 --- a/src/DjangoBlog-master/blog/management/commands/build_search_words.py +++ b/src/DjangoBlog-master/blog/management/commands/build_search_words.py @@ -3,11 +3,11 @@ from django.core.management.base import BaseCommand from blog.models import Tag, Category -# TODO 参数化 -class Command(BaseCommand): - help = 'build search words' +# TODO 参数化 #ZNY 待办事项:将命令参数化 +class Command(BaseCommand): #ZNY 定义构建搜索词命令类,继承BaseCommand + help = 'build search words' #ZNY 命令帮助信息:构建搜索词 - def handle(self, *args, **options): - datas = set([t.name for t in Tag.objects.all()] + + def handle(self, *args, **options): #ZNY 命令处理主方法 + datas = set([t.name for t in Tag.objects.all()] + #ZNY 获取所有标签名称和分类名称,合并并去重 [t.name for t in Category.objects.all()]) - print('\n'.join(datas)) + print('\n'.join(datas)) #ZNY 将数据用换行符连接并打印输出 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/management/commands/clear_cache.py b/src/DjangoBlog-master/blog/management/commands/clear_cache.py index 0d66172..cd8b6da 100644 --- a/src/DjangoBlog-master/blog/management/commands/clear_cache.py +++ b/src/DjangoBlog-master/blog/management/commands/clear_cache.py @@ -3,9 +3,9 @@ from django.core.management.base import BaseCommand from djangoblog.utils import cache -class Command(BaseCommand): - help = 'clear the whole cache' +class Command(BaseCommand): #ZNY 定义清除缓存命令类,继承BaseCommand + help = 'clear the whole cache' #ZNY 命令帮助信息:清除整个缓存 - def handle(self, *args, **options): - cache.clear() - self.stdout.write(self.style.SUCCESS('Cleared cache\n')) + def handle(self, *args, **options): #ZNY 命令处理主方法 + cache.clear() #ZNY 调用Django缓存接口清除所有缓存 + self.stdout.write(self.style.SUCCESS('Cleared cache\n')) #ZNY 输出成功信息到标准输出 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/management/commands/create_testdata.py b/src/DjangoBlog-master/blog/management/commands/create_testdata.py index 675d2ba..ca7140b 100644 --- a/src/DjangoBlog-master/blog/management/commands/create_testdata.py +++ b/src/DjangoBlog-master/blog/management/commands/create_testdata.py @@ -5,36 +5,36 @@ from django.core.management.base import BaseCommand from blog.models import Article, Tag, Category -class Command(BaseCommand): - help = 'create test datas' - - def handle(self, *args, **options): - user = get_user_model().objects.get_or_create( - email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] - - pcategory = Category.objects.get_or_create( - name='我是父类目', parent_category=None)[0] - - category = Category.objects.get_or_create( - name='子类目', parent_category=pcategory)[0] - - category.save() - basetag = Tag() - basetag.name = "标签" - basetag.save() - for i in range(1, 20): - article = Article.objects.get_or_create( - category=category, - title='nice title ' + str(i), - body='nice content ' + str(i), - author=user)[0] - tag = Tag() - tag.name = "标签" + str(i) - tag.save() - article.tags.add(tag) - article.tags.add(basetag) - article.save() - - from djangoblog.utils import cache - cache.clear() - self.stdout.write(self.style.SUCCESS('created test datas \n')) +class Command(BaseCommand): #ZNY 定义创建测试数据命令类,继承BaseCommand + help = 'create test datas' #ZNY 命令帮助信息:创建测试数据 + + def handle(self, *args, **options): #ZNY 命令处理主方法 + user = get_user_model().objects.get_or_create( #ZNY 获取或创建测试用户 + email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] #ZNY 设置用户邮箱、用户名和加密密码 + + pcategory = Category.objects.get_or_create( #ZNY 获取或创建父分类 + name='我是父类目', parent_category=None)[0] #ZNY 设置父分类名称,无父级分类 + + category = Category.objects.get_or_create( #ZNY 获取或创建子分类 + name='子类目', parent_category=pcategory)[0] #ZNY 设置子分类名称,指定父级分类 + + category.save() #ZNY 保存子分类 + basetag = Tag() #ZNY 创建基础标签实例 + basetag.name = "标签" #ZNY 设置基础标签名称 + basetag.save() #ZNY 保存基础标签 + for i in range(1, 20): #ZNY 循环创建19篇文章 + article = Article.objects.get_or_create( #ZNY 获取或创建文章 + category=category, #ZNY 设置文章分类 + title='nice title ' + str(i), #ZNY 设置带编号的文章标题 + body='nice content ' + str(i), #ZNY 设置带编号的文章正文 + author=user)[0] #ZNY 设置文章作者 + tag = Tag() #ZNY 创建新标签实例 + tag.name = "标签" + str(i) #ZNY 设置带编号的标签名称 + tag.save() #ZNY 保存标签 + article.tags.add(tag) #ZNY 将新标签添加到文章 + article.tags.add(basetag) #ZNY 将基础标签添加到文章 + article.save() #ZNY 保存文章 + + from djangoblog.utils import cache #ZNY 导入缓存工具 + cache.clear() #ZNY 清除所有缓存 + self.stdout.write(self.style.SUCCESS('created test datas \n')) #ZNY 输出成功信息到标准输出 diff --git a/src/DjangoBlog-master/blog/management/commands/ping_baidu.py b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py index 2c7fbdd..c6427a6 100644 --- a/src/DjangoBlog-master/blog/management/commands/ping_baidu.py +++ b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py @@ -4,47 +4,47 @@ from djangoblog.spider_notify import SpiderNotify from djangoblog.utils import get_current_site from blog.models import Article, Tag, Category -site = get_current_site().domain - - -class Command(BaseCommand): - help = 'notify baidu url' - - def add_arguments(self, parser): - parser.add_argument( - 'data_type', - type=str, - choices=[ - 'all', - 'article', - 'tag', - 'category'], - help='article : all article,tag : all tag,category: all category,all: All of these') - - def get_full_url(self, path): - url = "https://{site}{path}".format(site=site, path=path) - return url - - def handle(self, *args, **options): - type = options['data_type'] - self.stdout.write('start get %s' % type) - - urls = [] - if type == 'article' or type == 'all': - for article in Article.objects.filter(status='p'): - urls.append(article.get_full_url()) - if type == 'tag' or type == 'all': - for tag in Tag.objects.all(): - url = tag.get_absolute_url() - urls.append(self.get_full_url(url)) - if type == 'category' or type == 'all': - for category in Category.objects.all(): - url = category.get_absolute_url() - urls.append(self.get_full_url(url)) - - self.stdout.write( - self.style.SUCCESS( - 'start notify %d urls' % +site = get_current_site().domain #ZNY 获取当前站点的域名 + + +class Command(BaseCommand): #ZNY 定义通知百度URL命令类,继承BaseCommand + help = 'notify baidu url' #ZNY 命令帮助信息:通知百度URL + + def add_arguments(self, parser): #ZNY 添加命令行参数方法 + parser.add_argument( #ZNY 添加数据类型参数 + 'data_type', #ZNY 参数名称 + type=str, #ZNY 参数类型为字符串 + choices=[ #ZNY 参数可选值 + 'all', #ZNY 所有类型 + 'article', #ZNY 仅文章 + 'tag', #ZNY 仅标签 + 'category'], #ZNY 仅分类 + help='article : all article,tag : all tag,category: all category,all: All of these') #ZNY 参数帮助信息 + + def get_full_url(self, path): #ZNY 获取完整URL方法 + url = "https://{site}{path}".format(site=site, path=path) #ZNY 构建完整的HTTPS URL + return url #ZNY 返回完整URL + + def handle(self, *args, **options): #ZNY 命令处理主方法 + type = options['data_type'] #ZNY 从参数获取数据类型 + self.stdout.write('start get %s' % type) #ZNY 输出开始获取数据的提示 + + urls = [] #ZNY 初始化URL列表 + if type == 'article' or type == 'all': #ZNY 如果类型是文章或全部 + for article in Article.objects.filter(status='p'): #ZNY 遍历所有已发布的文章 + urls.append(article.get_full_url()) #ZNY 将文章完整URL添加到列表 + if type == 'tag' or type == 'all': #ZNY 如果类型是标签或全部 + for tag in Tag.objects.all(): #ZNY 遍历所有标签 + url = tag.get_absolute_url() #ZNY 获取标签的相对URL + urls.append(self.get_full_url(url)) #ZNY 转换为完整URL并添加到列表 + if type == 'category' or type == 'all': #ZNY 如果类型是分类或全部 + for category in Category.objects.all(): #ZNY 遍历所有分类 + url = category.get_absolute_url() #ZNY 获取分类的相对URL + urls.append(self.get_full_url(url)) #ZNY 转换为完整URL并添加到列表 + + self.stdout.write( #ZNY 输出开始通知的提示 + self.style.SUCCESS( #ZNY 使用成功样式 + 'start notify %d urls' % #ZNY 显示要通知的URL数量 len(urls))) - SpiderNotify.baidu_notify(urls) - self.stdout.write(self.style.SUCCESS('finish notify')) + SpiderNotify.baidu_notify(urls) #ZNY 调用百度通知功能推送URL + self.stdout.write(self.style.SUCCESS('finish notify')) #ZNY 输出完成通知的提示 diff --git a/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py index d0f4612..57e65bd 100644 --- a/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py +++ b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py @@ -7,41 +7,41 @@ from oauth.models import OAuthUser from oauth.oauthmanager import get_manager_by_type -class Command(BaseCommand): - help = 'sync user avatar' +class Command(BaseCommand): #ZNY 定义同步用户头像命令类,继承BaseCommand + help = 'sync user avatar' #ZNY 命令帮助信息:同步用户头像 - def test_picture(self, url): + def test_picture(self, url): #ZNY 测试图片URL是否可访问方法 try: - if requests.get(url, timeout=2).status_code == 200: - return True - except: - pass + if requests.get(url, timeout=2).status_code == 200: #ZNY 发送HTTP请求测试图片URL,设置2秒超时 + return True #ZNY 如果返回200状态码,返回True + except: #ZNY 捕获所有异常 + pass #ZNY 发生异常时忽略 - def handle(self, *args, **options): - static_url = static("../") - users = OAuthUser.objects.all() - self.stdout.write(f'开始同步{len(users)}个用户头像') - for u in users: - self.stdout.write(f'开始同步:{u.nickname}') - url = u.picture - if url: - if url.startswith(static_url): - if self.test_picture(url): - continue - else: - if u.metadata: - manage = get_manager_by_type(u.type) - url = manage.get_picture(u.metadata) - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - else: - url = save_user_avatar(url) - else: - url = static('blog/img/avatar.png') - if url: - self.stdout.write( - f'结束同步:{u.nickname}.url:{url}') - u.picture = url - u.save() - self.stdout.write('结束同步') + def handle(self, *args, **options): #ZNY 命令处理主方法 + static_url = static("../") #ZNY 获取静态文件基础URL + users = OAuthUser.objects.all() #ZNY 获取所有OAuth用户 + self.stdout.write(f'开始同步{len(users)}个用户头像') #ZNY 输出开始同步的提示,显示用户数量 + for u in users: #ZNY 遍历所有用户 + self.stdout.write(f'开始同步:{u.nickname}') #ZNY 输出开始同步当前用户的提示 + url = u.picture #ZNY 获取用户当前头像URL + if url: #ZNY 如果头像URL存在 + if url.startswith(static_url): #ZNY 如果头像URL是静态文件路径 + if self.test_picture(url): #ZNY 测试静态图片是否可访问 + continue #ZNY 如果可访问则跳过当前用户 + else: #ZNY 如果静态图片不可访问 + if u.metadata: #ZNY 如果用户有元数据 + manage = get_manager_by_type(u.type) #ZNY 根据OAuth类型获取对应的管理器 + url = manage.get_picture(u.metadata) #ZNY 从元数据中获取头像URL + url = save_user_avatar(url) #ZNY 保存用户头像并返回新的URL + else: #ZNY 如果没有元数据 + url = static('blog/img/avatar.png') #ZNY 使用默认头像 + else: #ZNY 如果头像URL不是静态文件路径 + url = save_user_avatar(url) #ZNY 保存用户头像并返回新的URL + else: #ZNY 如果没有头像URL + url = static('blog/img/avatar.png') #ZNY 使用默认头像 + if url: #ZNY 如果最终头像URL存在 + self.stdout.write( #ZNY 输出同步完成提示 + f'结束同步:{u.nickname}.url:{url}') #ZNY 显示用户名和最终头像URL + u.picture = url #ZNY 更新用户头像URL + u.save() #ZNY 保存用户信息 + self.stdout.write('结束同步') #ZNY 输出同步结束提示 diff --git a/src/DjangoBlog-master/blog/middleware.py b/src/DjangoBlog-master/blog/middleware.py index 94dd70c..4f427ee 100644 --- a/src/DjangoBlog-master/blog/middleware.py +++ b/src/DjangoBlog-master/blog/middleware.py @@ -1,42 +1,42 @@ -import logging -import time +import logging #ZNY 导入日志模块 +import time #ZNY 导入时间模块 -from ipware import get_client_ip -from user_agents import parse +from ipware import get_client_ip #ZNY 导入获取客户端IP的工具 +from user_agents import parse #ZNY 导入解析用户代理的工具 -from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager +from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager #ZNY 导入Elasticsearch配置和性能文档管理器 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -class OnlineMiddleware(object): - def __init__(self, get_response=None): - self.get_response = get_response - super().__init__() +class OnlineMiddleware(object): #ZNY 定义在线中间件类 + def __init__(self, get_response=None): #ZNY 初始化方法 + self.get_response = get_response #ZNY 存储get_response函数 + super().__init__() #ZNY 调用父类初始化 - def __call__(self, request): - ''' 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: + def __call__(self, request): #ZNY 使实例可调用,处理请求 + ''' page render time ''' #ZNY 页面渲染时间统计 + start_time = time.time() #ZNY 记录请求开始时间 + response = self.get_response(request) #ZNY 调用后续中间件和视图处理请求 + http_user_agent = request.META.get('HTTP_USER_AGENT', '') #ZNY 获取用户代理字符串 + ip, _ = get_client_ip(request) #ZNY 获取客户端IP地址 + user_agent = parse(http_user_agent) #ZNY 解析用户代理信息 + if not response.streaming: #ZNY 如果不是流式响应 try: - cast_time = time.time() - start_time - if ELASTICSEARCH_ENABLED: - time_taken = round((cast_time) * 1000, 2) - url = request.path - from django.utils import timezone - ElaspedTimeDocumentManager.create( - url=url, - time_taken=time_taken, - log_datetime=timezone.now(), - useragent=user_agent, - ip=ip) - response.content = response.content.replace( - b'', str.encode(str(cast_time)[:5])) - except Exception as e: - logger.error("Error OnlineMiddleware: %s" % e) + cast_time = time.time() - start_time #ZNY 计算总处理时间 + if ELASTICSEARCH_ENABLED: #ZNY 如果Elasticsearch已启用 + time_taken = round((cast_time) * 1000, 2) #ZNY 转换为毫秒并保留两位小数 + url = request.path #ZNY 获取请求路径 + from django.utils import timezone #ZNY 导入时区工具 + ElaspedTimeDocumentManager.create( #ZNY 创建性能记录文档 + url=url, #ZNY 请求URL + time_taken=time_taken, #ZNY 耗时 + log_datetime=timezone.now(), #ZNY 当前时间 + useragent=user_agent, #ZNY 用户代理信息 + ip=ip) #ZNY IP地址 + response.content = response.content.replace( #ZNY 在响应内容中替换占位符 + b'', str.encode(str(cast_time)[:5])) #ZNY 将加载时间插入到指定位置 + except Exception as e: #ZNY 捕获异常 + logger.error("Error OnlineMiddleware: %s" % e) #ZNY 记录错误日志 - return response + return response #ZNY 返回响应对象 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/migrations/0001_initial.py b/src/DjangoBlog-master/blog/migrations/0001_initial.py index 3d391b6..23a943e 100644 --- a/src/DjangoBlog-master/blog/migrations/0001_initial.py +++ b/src/DjangoBlog-master/blog/migrations/0001_initial.py @@ -12,126 +12,126 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), #ZNY 依赖Django自带的用户模型 ] operations = [ migrations.CreateModel( name='BlogSettings', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), - ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), - ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), - ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), - ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), - ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), - ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), - ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), - ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), - ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), - ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), - ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), - ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), - ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), - ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), #ZNY 主键,使用BigAutoField支持大数据量 + ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), #ZNY 网站名称 + ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), #ZNY 网站描述 + ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), #ZNY SEO描述 + ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), #ZNY 网站关键词 + ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), #ZNY 文章摘要长度 + ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), #ZNY 侧边栏文章数量 + ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), #ZNY 侧边栏评论数量 + ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), #ZNY 文章页评论显示数量 + ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), #ZNY 谷歌广告开关 + ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), #ZNY 广告代码 + ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), #ZNY 全站评论功能开关 + ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), #ZNY ICP备案号 + ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), #ZNY 网站统计代码 + ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), #ZNY 公安备案显示开关 + ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), #ZNY 公安备案号 ], options={ - 'verbose_name': '网站配置', - 'verbose_name_plural': '网站配置', + 'verbose_name': '网站配置', #ZNY 单数名称 + 'verbose_name_plural': '网站配置', #ZNY 复数名称 }, ), migrations.CreateModel( name='Links', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), - ('link', models.URLField(verbose_name='链接地址')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), #ZNY 主键 + ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), #ZNY 链接名称,唯一 + ('link', models.URLField(verbose_name='链接地址')), #ZNY 链接地址 + ('sequence', models.IntegerField(unique=True, verbose_name='排序')), #ZNY 排序序号,唯一 + ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), #ZNY 是否启用显示 + ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), #ZNY 链接显示位置 + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZNY 创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZNY 最后修改时间 ], options={ - 'verbose_name': '友情链接', - 'verbose_name_plural': '友情链接', - 'ordering': ['sequence'], + 'verbose_name': '友情链接', #ZNY 单数名称 + 'verbose_name_plural': '友情链接', #ZNY 复数名称 + 'ordering': ['sequence'], #ZNY 按排序字段升序排列 }, ), migrations.CreateModel( name='SideBar', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='标题')), - ('content', models.TextField(verbose_name='内容')), - ('sequence', models.IntegerField(unique=True, verbose_name='排序')), - ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), #ZNY 主键 + ('name', models.CharField(max_length=100, verbose_name='标题')), #ZNY 侧边栏标题 + ('content', models.TextField(verbose_name='内容')), #ZNY 侧边栏内容 + ('sequence', models.IntegerField(unique=True, verbose_name='排序')), #ZNY 排序序号,唯一 + ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), #ZNY 是否启用 + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZNY 创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZNY 最后修改时间 ], options={ - 'verbose_name': '侧边栏', - 'verbose_name_plural': '侧边栏', - 'ordering': ['sequence'], + 'verbose_name': '侧边栏', #ZNY 单数名称 + 'verbose_name_plural': '侧边栏', #ZNY 复数名称 + 'ordering': ['sequence'], #ZNY 按排序字段升序排列 }, ), migrations.CreateModel( name='Tag', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), + ('id', models.AutoField(primary_key=True, serialize=False)), #ZNY 主键,使用AutoField + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZNY 创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZNY 最后修改时间 + ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), #ZNY 标签名称,唯一 + ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), #ZNY SEO友好的URL片段 ], options={ - 'verbose_name': '标签', - 'verbose_name_plural': '标签', - 'ordering': ['name'], + 'verbose_name': '标签', #ZNY 单数名称 + 'verbose_name_plural': '标签', #ZNY 复数名称 + 'ordering': ['name'], #ZNY 按标签名升序排列 }, ), migrations.CreateModel( name='Category', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), - ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), - ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), - ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), + ('id', models.AutoField(primary_key=True, serialize=False)), #ZNY 主键,使用AutoField + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZNY 创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZNY 最后修改时间 + ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), #ZNY 分类名称,唯一 + ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), #ZNY SEO友好的URL片段 + ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), #ZNY 排序权重 + ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), #ZNY 父级分类,支持多级分类 ], options={ - 'verbose_name': '分类', - 'verbose_name_plural': '分类', - 'ordering': ['-index'], + 'verbose_name': '分类', #ZNY 单数名称 + 'verbose_name_plural': '分类', #ZNY 复数名称 + 'ordering': ['-index'], #ZNY 按权重降序排列 }, ), migrations.CreateModel( name='Article', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), - ('body', mdeditor.fields.MDTextField(verbose_name='正文')), - ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), - ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), - ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), - ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), - ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), - ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), - ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), - ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), + ('id', models.AutoField(primary_key=True, serialize=False)), #ZNY 主键,使用AutoField + ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), #ZNY 创建时间 + ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), #ZNY 最后修改时间 + ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), #ZNY 文章标题,唯一 + ('body', mdeditor.fields.MDTextField(verbose_name='正文')), #ZNY 正文,支持Markdown格式 + ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), #ZNY 发布时间 + ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), #ZNY 文章状态:草稿或发表 + ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), #ZNY 评论状态:打开或关闭 + ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), #ZNY 内容类型:文章或页面 + ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), #ZNY 浏览量统计 + ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), #ZNY 文章排序权重 + ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), #ZNY 是否显示目录 + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), #ZNY 作者,关联用户模型 + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), #ZNY 分类,关联分类表 + ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), #ZNY 标签,多对多关系 ], options={ - 'verbose_name': '文章', - 'verbose_name_plural': '文章', - 'ordering': ['-article_order', '-pub_time'], - 'get_latest_by': 'id', + 'verbose_name': '文章', #ZNY 单数名称 + 'verbose_name_plural': '文章', #ZNY 复数名称 + 'ordering': ['-article_order', '-pub_time'], #ZNY 先按排序权重降序,再按发布时间降序 + 'get_latest_by': 'id', #ZNY 指定获取最新记录的依据字段 }, ), ] diff --git a/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py index adbaa36..8de8ad5 100644 --- a/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py @@ -3,21 +3,24 @@ from django.db import migrations, models +from django.db import migrations, models + + class Migration(migrations.Migration): dependencies = [ - ('blog', '0001_initial'), + ('blog', '0001_initial'), #ZNY 依赖于blog应用的初始迁移文件0001_initial ] operations = [ migrations.AddField( - model_name='blogsettings', - name='global_footer', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), + model_name='blogsettings', #ZNY 向BlogSettings模型添加字段 + name='global_footer', #ZNY 字段名称为global_footer + field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), #ZNY 公共尾部字段,用于存储全站公共底部内容 ), migrations.AddField( - model_name='blogsettings', - name='global_header', - field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), + model_name='blogsettings', #ZNY 向BlogSettings模型添加字段 + name='global_header', #ZNY 字段名称为global_header + field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), #ZNY 公共头部字段,用于存储全站公共头部内容 ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py index e9f5502..40241f4 100644 --- a/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py +++ b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py @@ -5,13 +5,13 @@ 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'), #ZNY 依赖于blog应用的第二个迁移文件0002 ] operations = [ migrations.AddField( - model_name='blogsettings', - name='comment_need_review', - field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), + model_name='blogsettings', #ZNY 向BlogSettings模型添加新字段 + name='comment_need_review', #ZNY 字段名称为comment_need_review + field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), #ZNY 评论审核开关字段,控制评论是否需要审核才能显示 ), ] diff --git a/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py index ceb1398..88f4d5d 100644 --- a/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py @@ -5,23 +5,23 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('blog', '0003_blogsettings_comment_need_review'), + ('blog', '0003_blogsettings_comment_need_review'), #ZNY 依赖于blog应用的第三个迁移文件0003 ] operations = [ migrations.RenameField( - model_name='blogsettings', - old_name='analyticscode', - new_name='analytics_code', + model_name='blogsettings', #ZNY 指定要修改的模型为BlogSettings + old_name='analyticscode', #ZNY 原字段名称为analyticscode + new_name='analytics_code', #ZNY 新字段名称为analytics_code,修改为更规范的命名 ), migrations.RenameField( - model_name='blogsettings', - old_name='beiancode', - new_name='beian_code', + model_name='blogsettings', #ZNY 指定要修改的模型为BlogSettings + old_name='beiancode', #ZNY 原字段名称为beiancode + new_name='beian_code', #ZNY 新字段名称为beian_code,修改为更规范的命名 ), migrations.RenameField( - model_name='blogsettings', - old_name='sitename', - new_name='site_name', + model_name='blogsettings', #ZNY 指定要修改的模型为BlogSettings + old_name='sitename', #ZNY 原字段名称为sitename + new_name='site_name', #ZNY 新字段名称为site_name,修改为更规范的命名 ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py index d08e853..a227e17 100644 --- a/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py @@ -10,291 +10,291 @@ import mdeditor.fields class Migration(migrations.Migration): dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), #ZNY 依赖Django用户模型 + ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), #ZNY 依赖于blog应用的第四个迁移文件 ] operations = [ migrations.AlterModelOptions( name='article', - options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, + options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, #ZNY 修改Article模型的选项,将中文改为英文 ), migrations.AlterModelOptions( name='category', - options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, + options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, #ZNY 修改Category模型的选项,将中文改为英文 ), migrations.AlterModelOptions( name='links', - options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, + options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, #ZNY 修改Links模型的选项,将中文改为英文 ), migrations.AlterModelOptions( name='sidebar', - options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, + options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, #ZNY 修改Sidebar模型的选项,将中文改为英文 ), migrations.AlterModelOptions( name='tag', - options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, + options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, #ZNY 修改Tag模型的选项,将中文改为英文 ), migrations.RemoveField( model_name='article', - name='created_time', + name='created_time', #ZNY 删除Article模型的created_time字段 ), migrations.RemoveField( model_name='article', - name='last_mod_time', + name='last_mod_time', #ZNY 删除Article模型的last_mod_time字段 ), migrations.RemoveField( model_name='category', - name='created_time', + name='created_time', #ZNY 删除Category模型的created_time字段 ), migrations.RemoveField( model_name='category', - name='last_mod_time', + name='last_mod_time', #ZNY 删除Category模型的last_mod_time字段 ), migrations.RemoveField( model_name='links', - name='created_time', + name='created_time', #ZNY 删除Links模型的created_time字段 ), migrations.RemoveField( model_name='sidebar', - name='created_time', + name='created_time', #ZNY 删除Sidebar模型的created_time字段 ), migrations.RemoveField( model_name='tag', - name='created_time', + name='created_time', #ZNY 删除Tag模型的created_time字段 ), migrations.RemoveField( model_name='tag', - name='last_mod_time', + name='last_mod_time', #ZNY 删除Tag模型的last_mod_time字段 ), migrations.AddField( model_name='article', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + name='creation_time', #ZNY 向Article模型添加creation_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), #ZNY 创建时间字段,英文标签 ), migrations.AddField( model_name='article', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + name='last_modify_time', #ZNY 向Article模型添加last_modify_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), #ZNY 最后修改时间字段,英文标签 ), migrations.AddField( model_name='category', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + name='creation_time', #ZNY 向Category模型添加creation_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), #ZNY 创建时间字段,英文标签 ), migrations.AddField( model_name='category', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + name='last_modify_time', #ZNY 向Category模型添加last_modify_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), #ZNY 最后修改时间字段,英文标签 ), migrations.AddField( model_name='links', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + name='creation_time', #ZNY 向Links模型添加creation_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), #ZNY 创建时间字段,英文标签 ), migrations.AddField( model_name='sidebar', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + name='creation_time', #ZNY 向Sidebar模型添加creation_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), #ZNY 创建时间字段,英文标签 ), migrations.AddField( model_name='tag', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), + name='creation_time', #ZNY 向Tag模型添加creation_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), #ZNY 创建时间字段,英文标签 ), migrations.AddField( model_name='tag', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + name='last_modify_time', #ZNY 向Tag模型添加last_modify_time字段 + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), #ZNY 最后修改时间字段,英文标签 ), migrations.AlterField( model_name='article', name='article_order', - field=models.IntegerField(default=0, verbose_name='order'), + field=models.IntegerField(default=0, verbose_name='order'), #ZNY 修改Article模型的article_order字段标签为英文 ), migrations.AlterField( model_name='article', name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), #ZNY 修改Article模型的author字段标签为英文 ), migrations.AlterField( model_name='article', name='body', - field=mdeditor.fields.MDTextField(verbose_name='body'), + field=mdeditor.fields.MDTextField(verbose_name='body'), #ZNY 修改Article模型的body字段标签为英文 ), migrations.AlterField( model_name='article', name='category', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), #ZNY 修改Article模型的category字段标签为英文 ), migrations.AlterField( model_name='article', name='comment_status', - field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), + field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), #ZNY 修改Article模型的comment_status字段选项和标签为英文 ), migrations.AlterField( model_name='article', name='pub_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), #ZNY 修改Article模型的pub_time字段标签为英文 ), migrations.AlterField( model_name='article', name='show_toc', - field=models.BooleanField(default=False, verbose_name='show toc'), + field=models.BooleanField(default=False, verbose_name='show toc'), #ZNY 修改Article模型的show_toc字段标签为英文 ), migrations.AlterField( model_name='article', name='status', - field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), + field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), #ZNY 修改Article模型的status字段选项和标签为英文 ), migrations.AlterField( model_name='article', name='tags', - field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), + field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), #ZNY 修改Article模型的tags字段标签为英文 ), migrations.AlterField( model_name='article', name='title', - field=models.CharField(max_length=200, unique=True, verbose_name='title'), + field=models.CharField(max_length=200, unique=True, verbose_name='title'), #ZNY 修改Article模型的title字段标签为英文 ), migrations.AlterField( model_name='article', name='type', - field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), + field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), #ZNY 修改Article模型的type字段选项和标签为英文 ), migrations.AlterField( model_name='article', name='views', - field=models.PositiveIntegerField(default=0, verbose_name='views'), + field=models.PositiveIntegerField(default=0, verbose_name='views'), #ZNY 修改Article模型的views字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='article_comment_count', - field=models.IntegerField(default=5, verbose_name='article comment count'), + field=models.IntegerField(default=5, verbose_name='article comment count'), #ZNY 修改BlogSettings模型的article_comment_count字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='article_sub_length', - field=models.IntegerField(default=300, verbose_name='article sub length'), + field=models.IntegerField(default=300, verbose_name='article sub length'), #ZNY 修改BlogSettings模型的article_sub_length字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='google_adsense_codes', - field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), + field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), #ZNY 修改BlogSettings模型的google_adsense_codes字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='open_site_comment', - field=models.BooleanField(default=True, verbose_name='open site comment'), + field=models.BooleanField(default=True, verbose_name='open site comment'), #ZNY 修改BlogSettings模型的open_site_comment字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='show_google_adsense', - field=models.BooleanField(default=False, verbose_name='show adsense'), + field=models.BooleanField(default=False, verbose_name='show adsense'), #ZNY 修改BlogSettings模型的show_google_adsense字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='sidebar_article_count', - field=models.IntegerField(default=10, verbose_name='sidebar article count'), + field=models.IntegerField(default=10, verbose_name='sidebar article count'), #ZNY 修改BlogSettings模型的sidebar_article_count字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='sidebar_comment_count', - field=models.IntegerField(default=5, verbose_name='sidebar comment count'), + field=models.IntegerField(default=5, verbose_name='sidebar comment count'), #ZNY 修改BlogSettings模型的sidebar_comment_count字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='site_description', - field=models.TextField(default='', max_length=1000, verbose_name='site description'), + field=models.TextField(default='', max_length=1000, verbose_name='site description'), #ZNY 修改BlogSettings模型的site_description字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='site_keywords', - field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), + field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), #ZNY 修改BlogSettings模型的site_keywords字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='site_name', - field=models.CharField(default='', max_length=200, verbose_name='site name'), + field=models.CharField(default='', max_length=200, verbose_name='site name'), #ZNY 修改BlogSettings模型的site_name字段标签为英文 ), migrations.AlterField( model_name='blogsettings', name='site_seo_description', - field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), + field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), #ZNY 修改BlogSettings模型的site_seo_description字段标签为英文 ), migrations.AlterField( model_name='category', name='index', - field=models.IntegerField(default=0, verbose_name='index'), + field=models.IntegerField(default=0, verbose_name='index'), #ZNY 修改Category模型的index字段标签为英文 ), migrations.AlterField( model_name='category', name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='category name'), + field=models.CharField(max_length=30, unique=True, verbose_name='category name'), #ZNY 修改Category模型的name字段标签为英文 ), migrations.AlterField( model_name='category', name='parent_category', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'), #ZNY 修改Category模型的parent_category字段标签为英文 ), migrations.AlterField( model_name='links', name='is_enable', - field=models.BooleanField(default=True, verbose_name='is show'), + field=models.BooleanField(default=True, verbose_name='is show'), #ZNY 修改Links模型的is_enable字段标签为英文 ), migrations.AlterField( model_name='links', name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), #ZNY 修改Links模型的last_mod_time字段标签为英文 ), migrations.AlterField( model_name='links', name='link', - field=models.URLField(verbose_name='link'), + field=models.URLField(verbose_name='link'), #ZNY 修改Links模型的link字段标签为英文 ), migrations.AlterField( model_name='links', name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='link name'), + field=models.CharField(max_length=30, unique=True, verbose_name='link name'), #ZNY 修改Links模型的name字段标签为英文 ), migrations.AlterField( model_name='links', name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), + field=models.IntegerField(unique=True, verbose_name='order'), #ZNY 修改Links模型的sequence字段标签为英文 ), 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'), + field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'), #ZNY 修改Links模型的show_type字段选项和标签为英文 ), migrations.AlterField( model_name='sidebar', name='content', - field=models.TextField(verbose_name='content'), + field=models.TextField(verbose_name='content'), #ZNY 修改Sidebar模型的content字段标签为英文 ), migrations.AlterField( model_name='sidebar', name='is_enable', - field=models.BooleanField(default=True, verbose_name='is enable'), + field=models.BooleanField(default=True, verbose_name='is enable'), #ZNY 修改Sidebar模型的is_enable字段标签为英文 ), migrations.AlterField( model_name='sidebar', name='last_mod_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), #ZNY 修改Sidebar模型的last_mod_time字段标签为英文 ), migrations.AlterField( model_name='sidebar', name='name', - field=models.CharField(max_length=100, verbose_name='title'), + field=models.CharField(max_length=100, verbose_name='title'), #ZNY 修改Sidebar模型的name字段标签为英文 ), migrations.AlterField( model_name='sidebar', name='sequence', - field=models.IntegerField(unique=True, verbose_name='order'), + field=models.IntegerField(unique=True, verbose_name='order'), #ZNY 修改Sidebar模型的sequence字段标签为英文 ), migrations.AlterField( model_name='tag', name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='tag name'), + field=models.CharField(max_length=30, unique=True, verbose_name='tag name'), #ZNY 修改Tag模型的name字段标签为英文 ), ] diff --git a/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py index e36feb4..3cc9c73 100644 --- a/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py +++ b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('blog', '0005_alter_article_options_alter_category_options_and_more'), + ('blog', '0005_alter_article_options_alter_category_options_and_more'), #ZNY 依赖于blog应用的第五个迁移文件0005 ] operations = [ migrations.AlterModelOptions( - name='blogsettings', - options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, + name='blogsettings', #ZNY 修改BlogSettings模型的选项配置 + options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'}, #ZNY 将BlogSettings模型的显示名称从中文改为英文 ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/blog/models.py index 083788b..3f63aad 100644 --- a/src/DjangoBlog-master/blog/models.py +++ b/src/DjangoBlog-master/blog/models.py @@ -14,363 +14,363 @@ from uuslug import slugify from djangoblog.utils import cache_decorator, cache from djangoblog.utils import get_current_site -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -class LinkShowType(models.TextChoices): - I = ('i', _('index')) - L = ('l', _('list')) - P = ('p', _('post')) - A = ('a', _('all')) - S = ('s', _('slide')) +class LinkShowType(models.TextChoices): #ZNY 定义链接显示类型枚举类 + I = ('i', _('index')) #ZNY 首页显示 + L = ('l', _('list')) #ZNY 列表页显示 + P = ('p', _('post')) #ZNY 文章页面显示 + A = ('a', _('all')) #ZNY 全站显示 + S = ('s', _('slide')) #ZNY 友情链接页面显示 -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) +class BaseModel(models.Model): #ZNY 定义基础模型抽象类 + id = models.AutoField(primary_key=True) #ZNY 自增主键字段 + creation_time = models.DateTimeField(_('creation time'), default=now) #ZNY 创建时间字段 + last_modify_time = models.DateTimeField(_('modify time'), default=now) #ZNY 最后修改时间字段 - def save(self, *args, **kwargs): - is_update_views = isinstance( + def save(self, *args, **kwargs): #ZNY 重写保存方法 + is_update_views = isinstance( #ZNY 检查是否是更新文章浏览量的操作 self, 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) - else: - if 'slug' in self.__dict__: - slug = getattr( + if is_update_views: #ZNY 如果是更新浏览量 + Article.objects.filter(pk=self.pk).update(views=self.views) #ZNY 直接更新浏览量字段 + else: #ZNY 如果不是更新浏览量 + if 'slug' in self.__dict__: #ZNY 如果模型有slug字段 + slug = getattr( #ZNY 获取标题或名称作为slug源 self, 'title') if 'title' in self.__dict__ else getattr( self, 'name') - setattr(self, 'slug', slugify(slug)) - super().save(*args, **kwargs) + setattr(self, 'slug', slugify(slug)) #ZNY 生成并设置slug字段 + super().save(*args, **kwargs) #ZNY 调用父类保存方法 - def get_full_url(self): - site = get_current_site().domain - url = "https://{site}{path}".format(site=site, + def get_full_url(self): #ZNY 获取完整URL方法 + site = get_current_site().domain #ZNY 获取当前站点域名 + url = "https://{site}{path}".format(site=site, #ZNY 构建完整URL path=self.get_absolute_url()) - return url + return url #ZNY 返回完整URL class Meta: - abstract = True + abstract = True #ZNY 设置为抽象基类,不创建数据库表 - @abstractmethod - def get_absolute_url(self): - pass + @abstractmethod #ZNY 抽象方法,子类必须实现 + def get_absolute_url(self): #ZNY 获取绝对URL方法 + pass #ZNY 由子类具体实现 -class Article(BaseModel): - """文章""" - STATUS_CHOICES = ( - ('d', _('Draft')), - ('p', _('Published')), +class Article(BaseModel): #ZNY 定义文章模型,继承基础模型 + """文章""" #ZNY 文章模型文档字符串 + STATUS_CHOICES = ( #ZNY 文章状态选项 + ('d', _('Draft')), #ZNY 草稿状态 + ('p', _('Published')), #ZNY 发布状态 ) - COMMENT_STATUS = ( - ('o', _('Open')), - ('c', _('Close')), + COMMENT_STATUS = ( #ZNY 评论状态选项 + ('o', _('Open')), #ZNY 开放评论 + ('c', _('Close')), #ZNY 关闭评论 ) - TYPE = ( - ('a', _('Article')), - ('p', _('Page')), + TYPE = ( #ZNY 文章类型选项 + ('a', _('Article')), #ZNY 普通文章 + ('p', _('Page')), #ZNY 页面类型 ) - title = models.CharField(_('title'), max_length=200, unique=True) - body = MDTextField(_('body')) - pub_time = models.DateTimeField( + title = models.CharField(_('title'), max_length=200, unique=True) #ZNY 文章标题,唯一 + body = MDTextField(_('body')) #ZNY 文章正文,使用Markdown字段 + pub_time = models.DateTimeField( #ZNY 发布时间字段 _('publish time'), blank=False, null=False, default=now) - status = models.CharField( + status = models.CharField( #ZNY 文章状态字段 _('status'), max_length=1, choices=STATUS_CHOICES, - default='p') - comment_status = models.CharField( + default='p') #ZNY 默认发布状态 + comment_status = models.CharField( #ZNY 评论状态字段 _('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) - author = models.ForeignKey( + default='o') #ZNY 默认开放评论 + type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') #ZNY 文章类型,默认普通文章 + views = models.PositiveIntegerField(_('views'), default=0) #ZNY 浏览量统计字段 + author = models.ForeignKey( #ZNY 作者外键字段 settings.AUTH_USER_MODEL, verbose_name=_('author'), blank=False, null=False, - 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) - category = models.ForeignKey( + on_delete=models.CASCADE) #ZNY 级联删除 + article_order = models.IntegerField( #ZNY 文章排序字段 + _('order'), blank=False, null=False, default=0) #ZNY 数字越大越靠前 + show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) #ZNY 是否显示目录 + category = models.ForeignKey( #ZNY 分类外键字段 'Category', verbose_name=_('category'), on_delete=models.CASCADE, blank=False, - null=False) - tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) + null=False) #ZNY 必须选择分类 + tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) #ZNY 标签多对多关系 - def body_to_string(self): - return self.body + def body_to_string(self): #ZNY 获取正文字符串方法 + return self.body #ZNY 返回正文内容 - def __str__(self): - return self.title + def __str__(self): #ZNY 字符串表示方法 + return self.title #ZNY 返回文章标题 class Meta: - ordering = ['-article_order', '-pub_time'] - verbose_name = _('article') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-article_order', '-pub_time'] #ZNY 默认按排序和发布时间降序排列 + verbose_name = _('article') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 + get_latest_by = 'id' #ZNY 获取最新记录的依据字段 - def get_absolute_url(self): - return reverse('blog:detailbyid', kwargs={ + def get_absolute_url(self): #ZNY 实现获取绝对URL方法 + return reverse('blog:detailbyid', kwargs={ #ZNY 生成文章详情页URL 'article_id': self.id, 'year': self.creation_time.year, 'month': self.creation_time.month, 'day': self.creation_time.day }) - @cache_decorator(60 * 60 * 10) - def get_category_tree(self): - tree = self.category.get_category_tree() - names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) - - return names - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - - def viewed(self): - self.views += 1 - self.save(update_fields=['views']) - - def comment_list(self): - 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)) - 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)) - 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,)) - - @cache_decorator(expiration=60 * 100) - def next_article(self): + @cache_decorator(60 * 60 * 10) #ZNY 缓存装饰器,缓存10小时 + def get_category_tree(self): #ZNY 获取分类树方法 + tree = self.category.get_category_tree() #ZNY 获取分类的层级树 + names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) #ZNY 转换为名称和URL的元组列表 + + return names #ZNY 返回分类树列表 + + def save(self, *args, **kwargs): #ZNY 重写保存方法 + super().save(*args, **kwargs) #ZNY 调用父类保存方法 + + def viewed(self): #ZNY 增加浏览量方法 + self.views += 1 #ZNY 浏览量加1 + self.save(update_fields=['views']) #ZNY 只更新浏览量字段 + + def comment_list(self): #ZNY 获取评论列表方法 + cache_key = 'article_comments_{id}'.format(id=self.id) #ZNY 生成评论缓存键 + value = cache.get(cache_key) #ZNY 尝试从缓存获取评论 + if value: #ZNY 如果缓存存在 + logger.info('get article comments:{id}'.format(id=self.id)) #ZNY 记录获取缓存日志 + return value #ZNY 返回缓存评论 + else: #ZNY 如果缓存不存在 + comments = self.comment_set.filter(is_enable=True).order_by('-id') #ZNY 从数据库获取启用的评论 + cache.set(cache_key, comments, 60 * 100) #ZNY 设置缓存,有效期100分钟 + logger.info('set article comments:{id}'.format(id=self.id)) #ZNY 记录设置缓存日志 + return comments #ZNY 返回评论列表 + + def get_admin_url(self): #ZNY 获取管理后台URL方法 + info = (self._meta.app_label, self._meta.model_name) #ZNY 获取应用和模型名称 + return reverse('admin:%s_%s_change' % info, args=(self.pk,)) #ZNY 生成管理后台编辑URL + + @cache_decorator(expiration=60 * 100) #ZNY 缓存装饰器,缓存100分钟 + def next_article(self): #ZNY 获取下一篇文章方法 # 下一篇 - return Article.objects.filter( - id__gt=self.id, status='p').order_by('id').first() + return Article.objects.filter( #ZNY 查询ID大于当前文章且已发布的文章 + id__gt=self.id, status='p').order_by('id').first() #ZNY 按ID升序取第一篇 - @cache_decorator(expiration=60 * 100) - def prev_article(self): + @cache_decorator(expiration=60 * 100) #ZNY 缓存装饰器,缓存100分钟 + def prev_article(self): #ZNY 获取上一篇文章方法 # 前一篇 - return Article.objects.filter(id__lt=self.id, status='p').first() + return Article.objects.filter(id__lt=self.id, status='p').first() #ZNY 查询ID小于当前文章且已发布的文章 - def get_first_image_url(self): + def get_first_image_url(self): #ZNY 获取文章首张图片URL方法 """ Get the first image url from article.body. :return: """ - match = re.search(r'!\[.*?\]\((.+?)\)', self.body) - if match: - return match.group(1) - return "" + match = re.search(r'!\[.*?\]\((.+?)\)', self.body) #ZNY 使用正则匹配Markdown图片语法 + if match: #ZNY 如果匹配到图片 + return match.group(1) #ZNY 返回图片URL + return "" #ZNY 没有图片返回空字符串 -class Category(BaseModel): - """文章分类""" - name = models.CharField(_('category name'), max_length=30, unique=True) - parent_category = models.ForeignKey( +class Category(BaseModel): #ZNY 定义分类模型,继承基础模型 + """文章分类""" #ZNY 分类模型文档字符串 + name = models.CharField(_('category name'), max_length=30, unique=True) #ZNY 分类名称,唯一 + parent_category = models.ForeignKey( #ZNY 父级分类自关联字段 '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) #ZNY 支持多级分类 + slug = models.SlugField(default='no-slug', max_length=60, blank=True) #ZNY URL友好的分类名 + index = models.IntegerField(default=0, verbose_name=_('index')) #ZNY 排序索引,越大越靠前 class Meta: - ordering = ['-index'] - verbose_name = _('category') - verbose_name_plural = verbose_name + ordering = ['-index'] #ZNY 按索引降序排列 + verbose_name = _('category') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 - def get_absolute_url(self): - return reverse( + def get_absolute_url(self): #ZNY 实现获取绝对URL方法 + return reverse( #ZNY 生成分类详情页URL 'blog:category_detail', kwargs={ 'category_name': self.slug}) - def __str__(self): - return self.name + def __str__(self): #ZNY 字符串表示方法 + return self.name #ZNY 返回分类名称 - @cache_decorator(60 * 60 * 10) - def get_category_tree(self): + @cache_decorator(60 * 60 * 10) #ZNY 缓存装饰器,缓存10小时 + def get_category_tree(self): #ZNY 获取分类树方法 """ 递归获得分类目录的父级 :return: """ - categorys = [] + categorys = [] #ZNY 初始化分类列表 - def parse(category): - categorys.append(category) - if category.parent_category: - parse(category.parent_category) + def parse(category): #ZNY 递归解析函数 + categorys.append(category) #ZNY 添加当前分类到列表 + if category.parent_category: #ZNY 如果存在父级分类 + parse(category.parent_category) #ZNY 递归解析父级分类 - parse(self) - return categorys + parse(self) #ZNY 从当前分类开始解析 + return categorys #ZNY 返回分类树列表 - @cache_decorator(60 * 60 * 10) - def get_sub_categorys(self): + @cache_decorator(60 * 60 * 10) #ZNY 缓存装饰器,缓存10小时 + def get_sub_categorys(self): #ZNY 获取所有子分类方法 """ 获得当前分类目录所有子集 :return: """ - categorys = [] - all_categorys = Category.objects.all() + categorys = [] #ZNY 初始化子分类列表 + all_categorys = Category.objects.all() #ZNY 获取所有分类 - def parse(category): - if category not in categorys: - categorys.append(category) - childs = all_categorys.filter(parent_category=category) - for child in childs: - if category not in categorys: - categorys.append(child) - parse(child) + def parse(category): #ZNY 递归解析函数 + if category not in categorys: #ZNY 如果分类不在列表中 + categorys.append(category) #ZNY 添加当前分类 + childs = all_categorys.filter(parent_category=category) #ZNY 获取直接子分类 + for child in childs: #ZNY 遍历子分类 + if category not in categorys: #ZNY 如果子分类不在列表中 + categorys.append(child) #ZNY 添加子分类 + parse(child) #ZNY 递归解析子分类的子分类 - parse(self) - return categorys + parse(self) #ZNY 从当前分类开始解析 + return categorys #ZNY 返回所有子分类列表 -class Tag(BaseModel): - """文章标签""" - name = models.CharField(_('tag name'), max_length=30, unique=True) - slug = models.SlugField(default='no-slug', max_length=60, blank=True) +class Tag(BaseModel): #ZNY 定义标签模型,继承基础模型 + """文章标签""" #ZNY 标签模型文档字符串 + name = models.CharField(_('tag name'), max_length=30, unique=True) #ZNY 标签名称,唯一 + slug = models.SlugField(default='no-slug', max_length=60, blank=True) #ZNY URL友好的标签名 - def __str__(self): - return self.name + def __str__(self): #ZNY 字符串表示方法 + return self.name #ZNY 返回标签名称 - def get_absolute_url(self): - return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) + def get_absolute_url(self): #ZNY 实现获取绝对URL方法 + return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) #ZNY 生成标签详情页URL - @cache_decorator(60 * 60 * 10) - def get_article_count(self): - return Article.objects.filter(tags__name=self.name).distinct().count() + @cache_decorator(60 * 60 * 10) #ZNY 缓存装饰器,缓存10小时 + def get_article_count(self): #ZNY 获取标签下文章数量方法 + return Article.objects.filter(tags__name=self.name).distinct().count() #ZNY 统计使用该标签的文章数量 class Meta: - ordering = ['name'] - verbose_name = _('tag') - verbose_name_plural = verbose_name + ordering = ['name'] #ZNY 按名称升序排列 + verbose_name = _('tag') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 -class Links(models.Model): - """友情链接""" +class Links(models.Model): #ZNY 定义友情链接模型 + """友情链接""" #ZNY 友情链接模型文档字符串 - 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) - show_type = models.CharField( + name = models.CharField(_('link name'), max_length=30, unique=True) #ZNY 链接名称,唯一 + link = models.URLField(_('link')) #ZNY 链接地址 + sequence = models.IntegerField(_('order'), unique=True) #ZNY 排序序号,唯一 + is_enable = models.BooleanField( #ZNY 是否启用显示字段 + _('is show'), default=True, blank=False, null=False) #ZNY 默认启用 + show_type = models.CharField( #ZNY 显示类型字段 _('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) #ZNY 默认在首页显示 + creation_time = models.DateTimeField(_('creation time'), default=now) #ZNY 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) #ZNY 最后修改时间 class Meta: - ordering = ['sequence'] - verbose_name = _('link') - verbose_name_plural = verbose_name + ordering = ['sequence'] #ZNY 按排序序号升序排列 + verbose_name = _('link') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 - def __str__(self): - return self.name + def __str__(self): #ZNY 字符串表示方法 + return self.name #ZNY 返回链接名称 -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) +class SideBar(models.Model): #ZNY 定义侧边栏模型 + """侧边栏,可以展示一些html内容""" #ZNY 侧边栏模型文档字符串 + name = models.CharField(_('title'), max_length=100) #ZNY 侧边栏标题 + content = models.TextField(_('content')) #ZNY 侧边栏内容 + sequence = models.IntegerField(_('order'), unique=True) #ZNY 排序序号,唯一 + is_enable = models.BooleanField(_('is enable'), default=True) #ZNY 是否启用,默认启用 + creation_time = models.DateTimeField(_('creation time'), default=now) #ZNY 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) #ZNY 最后修改时间 class Meta: - ordering = ['sequence'] - verbose_name = _('sidebar') - verbose_name_plural = verbose_name + ordering = ['sequence'] #ZNY 按排序序号升序排列 + verbose_name = _('sidebar') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 - def __str__(self): - return self.name + def __str__(self): #ZNY 字符串表示方法 + return self.name #ZNY 返回侧边栏名称 -class BlogSettings(models.Model): - """blog的配置""" - site_name = models.CharField( +class BlogSettings(models.Model): #ZNY 定义博客设置模型 + """blog的配置""" #ZNY 博客设置模型文档字符串 + site_name = models.CharField( #ZNY 网站名称字段 _('site name'), max_length=200, null=False, blank=False, default='') - site_description = models.TextField( + site_description = models.TextField( #ZNY 网站描述字段 _('site description'), max_length=1000, null=False, blank=False, default='') - site_seo_description = models.TextField( + site_seo_description = models.TextField( #ZNY 网站SEO描述字段 _('site seo description'), max_length=1000, null=False, blank=False, default='') - site_keywords = models.TextField( + site_keywords = models.TextField( #ZNY 网站关键词字段 _('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) - google_adsense_codes = models.TextField( + article_sub_length = models.IntegerField(_('article sub length'), default=300) #ZNY 文章摘要长度 + sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) #ZNY 侧边栏文章数量 + sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) #ZNY 侧边栏评论数量 + article_comment_count = models.IntegerField(_('article comment count'), default=5) #ZNY 文章页评论数量 + show_google_adsense = models.BooleanField(_('show adsense'), default=False) #ZNY 是否显示谷歌广告 + google_adsense_codes = models.TextField( #ZNY 谷歌广告代码字段 _('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='') - beian_code = models.CharField( + open_site_comment = models.BooleanField(_('open site comment'), default=True) #ZNY 是否开启全站评论 + global_header = models.TextField("公共头部", null=True, blank=True, default='') #ZNY 全局头部内容 + global_footer = models.TextField("公共尾部", null=True, blank=True, default='') #ZNY 全局尾部内容 + beian_code = models.CharField( #ZNY ICP备案号字段 '备案号', max_length=2000, null=True, blank=True, default='') - analytics_code = models.TextField( + analytics_code = models.TextField( #ZNY 网站统计代码字段 "网站统计代码", max_length=1000, null=False, blank=False, default='') - show_gongan_code = models.BooleanField( + show_gongan_code = models.BooleanField( #ZNY 是否显示公安备案号字段 '是否显示公安备案号', default=False, null=False) - gongan_beiancode = models.TextField( + gongan_beiancode = models.TextField( #ZNY 公安备案号字段 '公安备案号', max_length=2000, null=True, blank=True, default='') - comment_need_review = models.BooleanField( + comment_need_review = models.BooleanField( #ZNY 评论是否需要审核字段 '评论是否需要审核', default=False, null=False) class Meta: - verbose_name = _('Website configuration') - verbose_name_plural = verbose_name + verbose_name = _('Website configuration') #ZNY 单数显示名称 + verbose_name_plural = verbose_name #ZNY 复数显示名称 - def __str__(self): - return self.site_name + def __str__(self): #ZNY 字符串表示方法 + return self.site_name #ZNY 返回网站名称 - def clean(self): - if BlogSettings.objects.exclude(id=self.id).count(): - raise ValidationError(_('There can only be one configuration')) + def clean(self): #ZNY 数据清洗验证方法 + if BlogSettings.objects.exclude(id=self.id).count(): #ZNY 检查是否已存在其他配置 + raise ValidationError(_('There can only be one configuration')) #ZNY 只能有一个配置,抛出验证错误 - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - from djangoblog.utils import cache - cache.clear() + def save(self, *args, **kwargs): #ZNY 重写保存方法 + super().save(*args, **kwargs) #ZNY 调用父类保存方法 + from djangoblog.utils import cache #ZNY 导入缓存工具 + cache.clear() #ZNY 保存配置后清除所有缓存 diff --git a/src/DjangoBlog-master/blog/search_indexes.py b/src/DjangoBlog-master/blog/search_indexes.py index 7f1dfac..5eb13d9 100644 --- a/src/DjangoBlog-master/blog/search_indexes.py +++ b/src/DjangoBlog-master/blog/search_indexes.py @@ -1,13 +1,13 @@ -from haystack import indexes +from haystack import indexes #ZNY 导入Haystack搜索索引模块 -from blog.models import Article +from blog.models import Article #ZNY 导入文章模型 -class ArticleIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) +class ArticleIndex(indexes.SearchIndex, indexes.Indexable): #ZNY 定义文章搜索索引类,继承搜索索引和可索引接口 + text = indexes.CharField(document=True, use_template=True) #ZNY 定义主搜索字段,使用模板构建索引内容 - def get_model(self): - return Article + def get_model(self): #ZNY 获取索引对应的模型方法 + return Article #ZNY 返回文章模型类 - def index_queryset(self, using=None): - return self.get_model().objects.filter(status='p') + def index_queryset(self, using=None): #ZNY 定义索引查询集方法 + return self.get_model().objects.filter(status='p') #ZNY 只索引已发布状态的文章 diff --git a/src/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/blog/templatetags/blog_tags.py index d6cd5d5..830a248 100644 --- a/src/DjangoBlog-master/blog/templatetags/blog_tags.py +++ b/src/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -20,325 +20,325 @@ from djangoblog.utils import get_current_site from oauth.models import OAuthUser from djangoblog.plugin_manage import hooks -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -register = template.Library() +register = template.Library() #ZNY 创建Django模板标签库实例 -@register.simple_tag(takes_context=True) -def head_meta(context): - return mark_safe(hooks.apply_filters('head_meta', '', context)) +@register.simple_tag(takes_context=True) #ZNY 注册简单模板标签,接收模板上下文 +def head_meta(context): #ZNY 生成头部meta信息的模板标签 + return mark_safe(hooks.apply_filters('head_meta', '', context)) #ZNY 应用过滤器并返回安全的HTML字符串 -@register.simple_tag -def timeformat(data): +@register.simple_tag #ZNY 注册简单模板标签 +def timeformat(data): #ZNY 时间格式化模板标签 try: - return data.strftime(settings.TIME_FORMAT) + return data.strftime(settings.TIME_FORMAT) #ZNY 按照设置的时间格式格式化时间 except Exception as e: - logger.error(e) - return "" + logger.error(e) #ZNY 记录错误日志 + return "" #ZNY 出错时返回空字符串 -@register.simple_tag -def datetimeformat(data): +@register.simple_tag #ZNY 注册简单模板标签 +def datetimeformat(data): #ZNY 日期时间格式化模板标签 try: - return data.strftime(settings.DATE_TIME_FORMAT) + return data.strftime(settings.DATE_TIME_FORMAT) #ZNY 按照设置的日期时间格式格式化 except Exception as e: - logger.error(e) - return "" + logger.error(e) #ZNY 记录错误日志 + return "" #ZNY 出错时返回空字符串 -@register.filter() -@stringfilter -def custom_markdown(content): - return mark_safe(CommonMarkdown.get_markdown(content)) +@register.filter() #ZNY 注册模板过滤器 +@stringfilter #ZNY 确保输入被转换为字符串 +def custom_markdown(content): #ZNY 自定义Markdown转换过滤器 + return mark_safe(CommonMarkdown.get_markdown(content)) #ZNY 将Markdown内容转换为HTML并标记为安全 -@register.simple_tag -def get_markdown_toc(content): - from djangoblog.utils import CommonMarkdown - body, toc = CommonMarkdown.get_markdown_with_toc(content) - return mark_safe(toc) +@register.simple_tag #ZNY 注册简单模板标签 +def get_markdown_toc(content): #ZNY 获取Markdown目录模板标签 + from djangoblog.utils import CommonMarkdown #ZNY 导入Markdown工具类 + body, toc = CommonMarkdown.get_markdown_with_toc(content) #ZNY 获取带目录的Markdown内容 + return mark_safe(toc) #ZNY 返回目录HTML并标记为安全 -@register.filter() -@stringfilter -def comment_markdown(content): - content = CommonMarkdown.get_markdown(content) - return mark_safe(sanitize_html(content)) +@register.filter() #ZNY 注册模板过滤器 +@stringfilter #ZNY 确保输入被转换为字符串 +def comment_markdown(content): #ZNY 评论Markdown转换过滤器 + content = CommonMarkdown.get_markdown(content) #ZNY 将Markdown转换为HTML + return mark_safe(sanitize_html(content)) #ZNY 对HTML进行安全过滤后返回 -@register.filter(is_safe=True) -@stringfilter -def truncatechars_content(content): +@register.filter(is_safe=True) #ZNY 注册安全模板过滤器 +@stringfilter #ZNY 确保输入被转换为字符串 +def truncatechars_content(content): #ZNY 截断文章内容过滤器 """ 获得文章内容的摘要 :param content: :return: """ - 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) + from django.template.defaultfilters import truncatechars_html #ZNY 导入HTML截断函数 + from djangoblog.utils import get_blog_setting #ZNY 导入博客设置获取函数 + blogsetting = get_blog_setting() #ZNY 获取博客设置 + return truncatechars_html(content, blogsetting.article_sub_length) #ZNY 按设置的文章摘要长度截断HTML内容 -@register.filter(is_safe=True) -@stringfilter -def truncate(content): - from django.utils.html import strip_tags +@register.filter(is_safe=True) #ZNY 注册安全模板过滤器 +@stringfilter #ZNY 确保输入被转换为字符串 +def truncate(content): #ZNY 简单截断过滤器 + from django.utils.html import strip_tags #ZNY 导入HTML标签去除函数 - return strip_tags(content)[:150] + return strip_tags(content)[:150] #ZNY 去除HTML标签后截取前150个字符 -@register.inclusion_tag('blog/tags/breadcrumb.html') -def load_breadcrumb(article): +@register.inclusion_tag('blog/tags/breadcrumb.html') #ZNY 注册包含标签,指定模板 +def load_breadcrumb(article): #ZNY 加载面包屑导航标签 """ 获得文章面包屑 :param article: :return: """ - 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 = article.get_category_tree() #ZNY 获取文章分类树 + from djangoblog.utils import get_blog_setting #ZNY 导入博客设置获取函数 + blogsetting = get_blog_setting() #ZNY 获取博客设置 + site = get_current_site().domain #ZNY 获取当前站点域名 + names.append((blogsetting.site_name, '/')) #ZNY 添加首页链接 + names = names[::-1] #ZNY 反转列表顺序 return { - 'names': names, - 'title': article.title, - 'count': len(names) + 1 + 'names': names, #ZNY 面包屑名称和URL列表 + 'title': article.title, #ZNY 文章标题 + 'count': len(names) + 1 #ZNY 面包屑数量 } -@register.inclusion_tag('blog/tags/article_tag_list.html') -def load_articletags(article): +@register.inclusion_tag('blog/tags/article_tag_list.html') #ZNY 注册包含标签,指定模板 +def load_articletags(article): #ZNY 加载文章标签列表标签 """ 文章标签 :param article: :return: """ - tags = article.tags.all() - tags_list = [] - for tag in tags: - url = tag.get_absolute_url() - count = tag.get_article_count() - tags_list.append(( - url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) + tags = article.tags.all() #ZNY 获取文章的所有标签 + tags_list = [] #ZNY 初始化标签列表 + for tag in tags: #ZNY 遍历每个标签 + url = tag.get_absolute_url() #ZNY 获取标签URL + count = tag.get_article_count() #ZNY 获取标签下文章数量 + tags_list.append(( #ZNY 添加标签信息到列表 + url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) #ZNY URL、数量、标签对象、随机颜色 )) return { - 'article_tags_list': tags_list + 'article_tags_list': tags_list #ZNY 返回标签列表 } -@register.inclusion_tag('blog/tags/sidebar.html') -def load_sidebar(user, linktype): +@register.inclusion_tag('blog/tags/sidebar.html') #ZNY 注册包含标签,指定模板 +def load_sidebar(user, linktype): #ZNY 加载侧边栏标签 """ 加载侧边栏 :return: """ - value = cache.get("sidebar" + linktype) - if value: - value['user'] = user - return value - else: - logger.info('load sidebar') - 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() - extra_sidebars = SideBar.objects.filter( - 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') - links = Links.objects.filter(is_enable=True).filter( - 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] + value = cache.get("sidebar" + linktype) #ZNY 尝试从缓存获取侧边栏数据 + if value: #ZNY 如果缓存存在 + value['user'] = user #ZNY 添加用户信息 + return value #ZNY 返回缓存数据 + else: #ZNY 如果缓存不存在 + logger.info('load sidebar') #ZNY 记录加载侧边栏日志 + from djangoblog.utils import get_blog_setting #ZNY 导入博客设置获取函数 + blogsetting = get_blog_setting() #ZNY 获取博客设置 + recent_articles = Article.objects.filter( #ZNY 获取最近文章 + status='p')[:blogsetting.sidebar_article_count] #ZNY 只取已发布文章,按设置数量限制 + sidebar_categorys = Category.objects.all() #ZNY 获取所有分类 + extra_sidebars = SideBar.objects.filter( #ZNY 获取额外侧边栏 + is_enable=True).order_by('sequence') #ZNY 只取启用的,按顺序排序 + most_read_articles = Article.objects.filter(status='p').order_by( #ZNY 获取最多阅读文章 + '-views')[:blogsetting.sidebar_article_count] #ZNY 按浏览量降序排列,按设置数量限制 + dates = Article.objects.datetimes('creation_time', 'month', order='DESC') #ZNY 获取文章按月归档日期 + links = Links.objects.filter(is_enable=True).filter( #ZNY 获取友情链接 + Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) #ZNY 按显示类型过滤 + commment_list = Comment.objects.filter(is_enable=True).order_by( #ZNY 获取最新评论 + '-id')[:blogsetting.sidebar_comment_count] #ZNY 按ID降序排列,按设置数量限制 # 标签云 计算字体大小 # 根据总数计算出平均值 大小为 (数目/平均值)*步长 - 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) - import random - sidebar_tags = list( - map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) - random.shuffle(sidebar_tags) - - value = { - 'recent_articles': recent_articles, - 'sidebar_categorys': sidebar_categorys, - 'most_read_articles': most_read_articles, - 'article_dates': dates, - 'sidebar_comments': commment_list, - 'sidabar_links': links, - 'show_google_adsense': blogsetting.show_google_adsense, - 'google_adsense_codes': blogsetting.google_adsense_codes, - 'open_site_comment': blogsetting.open_site_comment, - 'show_gongan_code': blogsetting.show_gongan_code, - 'sidebar_tags': sidebar_tags, - 'extra_sidebars': extra_sidebars + increment = 5 #ZNY 字体大小增量 + tags = Tag.objects.all() #ZNY 获取所有标签 + sidebar_tags = None #ZNY 初始化侧边栏标签 + if tags and len(tags) > 0: #ZNY 如果有标签 + s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]] #ZNY 生成标签和文章数量列表 + count = sum([t[1] for t in s]) #ZNY 计算总文章数 + dd = 1 if (count == 0 or not len(tags)) else count / len(tags) #ZNY 计算平均值 + import random #ZNY 导入随机模块 + sidebar_tags = list( #ZNY 生成侧边栏标签列表 + map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) #ZNY 计算每个标签的字体大小 + random.shuffle(sidebar_tags) #ZNY 随机打乱标签顺序 + + value = { #ZNY 构建侧边栏数据字典 + 'recent_articles': recent_articles, #ZNY 最近文章 + 'sidebar_categorys': sidebar_categorys, #ZNY 分类列表 + 'most_read_articles': most_read_articles, #ZNY 最多阅读文章 + 'article_dates': dates, #ZNY 文章归档日期 + 'sidebar_comments': commment_list, #ZNY 侧边栏评论 + 'sidabar_links': links, #ZNY 侧边栏链接 + 'show_google_adsense': blogsetting.show_google_adsense, #ZNY 是否显示谷歌广告 + 'google_adsense_codes': blogsetting.google_adsense_codes, #ZNY 谷歌广告代码 + 'open_site_comment': blogsetting.open_site_comment, #ZNY 是否开启全站评论 + 'show_gongan_code': blogsetting.show_gongan_code, #ZNY 是否显示公安备案 + 'sidebar_tags': sidebar_tags, #ZNY 侧边栏标签云 + 'extra_sidebars': extra_sidebars #ZNY 额外侧边栏 } - cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) - logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) - value['user'] = user - return value + cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) #ZNY 将侧边栏数据存入缓存,有效期3小时 + logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) #ZNY 记录缓存设置日志 + value['user'] = user #ZNY 添加用户信息 + return value #ZNY 返回侧边栏数据 -@register.inclusion_tag('blog/tags/article_meta_info.html') -def load_article_metas(article, user): +@register.inclusion_tag('blog/tags/article_meta_info.html') #ZNY 注册包含标签,指定模板 +def load_article_metas(article, user): #ZNY 加载文章meta信息标签 """ 获得文章meta信息 :param article: :return: """ return { - 'article': article, - 'user': user + 'article': article, #ZNY 文章对象 + 'user': user #ZNY 用户对象 } -@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_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse('blog:index_page', kwargs={'page': next_number}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( +@register.inclusion_tag('blog/tags/article_pagination.html') #ZNY 注册包含标签,指定模板 +def load_pagination_info(page_obj, page_type, tag_name): #ZNY 加载分页信息标签 + previous_url = '' #ZNY 初始化上一页URL + next_url = '' #ZNY 初始化下一页URL + if page_type == '': #ZNY 如果是首页分页 + if page_obj.has_next(): #ZNY 如果有下一页 + next_number = page_obj.next_page_number() #ZNY 获取下一页页码 + next_url = reverse('blog:index_page', kwargs={'page': next_number}) #ZNY 生成下一页URL + if page_obj.has_previous(): #ZNY 如果有上一页 + previous_number = page_obj.previous_page_number() #ZNY 获取上一页页码 + previous_url = reverse( #ZNY 生成上一页URL 'blog:index_page', kwargs={ 'page': previous_number}) - if page_type == '分类标签归档': - tag = get_object_or_404(Tag, name=tag_name) - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( + if page_type == '分类标签归档': #ZNY 如果是标签归档分页 + tag = get_object_or_404(Tag, name=tag_name) #ZNY 获取标签对象 + if page_obj.has_next(): #ZNY 如果有下一页 + next_number = page_obj.next_page_number() #ZNY 获取下一页页码 + next_url = reverse( #ZNY 生成标签下一页URL 'blog:tag_detail_page', kwargs={ 'page': next_number, 'tag_name': tag.slug}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( + if page_obj.has_previous(): #ZNY 如果有上一页 + previous_number = page_obj.previous_page_number() #ZNY 获取上一页页码 + previous_url = reverse( #ZNY 生成标签上一页URL 'blog:tag_detail_page', kwargs={ 'page': previous_number, 'tag_name': tag.slug}) - if page_type == '作者文章归档': - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( + if page_type == '作者文章归档': #ZNY 如果是作者归档分页 + if page_obj.has_next(): #ZNY 如果有下一页 + next_number = page_obj.next_page_number() #ZNY 获取下一页页码 + next_url = reverse( #ZNY 生成作者下一页URL 'blog:author_detail_page', kwargs={ 'page': next_number, 'author_name': tag_name}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( + if page_obj.has_previous(): #ZNY 如果有上一页 + previous_number = page_obj.previous_page_number() #ZNY 获取上一页页码 + previous_url = reverse( #ZNY 生成作者上一页URL 'blog:author_detail_page', kwargs={ 'page': previous_number, 'author_name': tag_name}) - if page_type == '分类目录归档': - category = get_object_or_404(Category, name=tag_name) - if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( + if page_type == '分类目录归档': #ZNY 如果是分类归档分页 + category = get_object_or_404(Category, name=tag_name) #ZNY 获取分类对象 + if page_obj.has_next(): #ZNY 如果有下一页 + next_number = page_obj.next_page_number() #ZNY 获取下一页页码 + next_url = reverse( #ZNY 生成分类下一页URL 'blog:category_detail_page', kwargs={ 'page': next_number, 'category_name': category.slug}) - if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( + if page_obj.has_previous(): #ZNY 如果有上一页 + previous_number = page_obj.previous_page_number() #ZNY 获取上一页页码 + previous_url = reverse( #ZNY 生成分类上一页URL 'blog:category_detail_page', kwargs={ 'page': previous_number, 'category_name': category.slug}) return { - 'previous_url': previous_url, - 'next_url': next_url, - 'page_obj': page_obj + 'previous_url': previous_url, #ZNY 上一页URL + 'next_url': next_url, #ZNY 下一页URL + 'page_obj': page_obj #ZNY 分页对象 } -@register.inclusion_tag('blog/tags/article_info.html') -def load_article_detail(article, isindex, user): +@register.inclusion_tag('blog/tags/article_info.html') #ZNY 注册包含标签,指定模板 +def load_article_detail(article, isindex, user): #ZNY 加载文章详情标签 """ 加载文章详情 :param article: :param isindex:是否列表页,若是列表页只显示摘要 :return: """ - from djangoblog.utils import get_blog_setting - blogsetting = get_blog_setting() + from djangoblog.utils import get_blog_setting #ZNY 导入博客设置获取函数 + blogsetting = get_blog_setting() #ZNY 获取博客设置 return { - 'article': article, - 'isindex': isindex, - 'user': user, - 'open_site_comment': blogsetting.open_site_comment, + 'article': article, #ZNY 文章对象 + 'isindex': isindex, #ZNY 是否是列表页 + 'user': user, #ZNY 用户对象 + 'open_site_comment': blogsetting.open_site_comment, #ZNY 是否开启全站评论 } # return only the URL of the gravatar # TEMPLATE USE: {{ email|gravatar_url:150 }} -@register.filter -def gravatar_url(email, size=40): +@register.filter #ZNY 注册模板过滤器 +def gravatar_url(email, size=40): #ZNY 获取Gravatar头像URL过滤器 """获得gravatar头像""" - cachekey = 'gravatat/' + email - url = cache.get(cachekey) - if url: - return url - else: - usermodels = OAuthUser.objects.filter(email=email) - if usermodels: - o = list(filter(lambda x: x.picture is not None, usermodels)) - if o: - return o[0].picture - email = email.encode('utf-8') - - default = static('blog/img/avatar.png') - - url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( - email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) - cache.set(cachekey, url, 60 * 60 * 10) - logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) - return url - - -@register.filter -def gravatar(email, size=40): + cachekey = 'gravatat/' + email #ZNY 生成缓存键 + url = cache.get(cachekey) #ZNY 尝试从缓存获取头像URL + if url: #ZNY 如果缓存存在 + return url #ZNY 返回缓存URL + else: #ZNY 如果缓存不存在 + usermodels = OAuthUser.objects.filter(email=email) #ZNY 查询OAuth用户 + if usermodels: #ZNY 如果找到用户 + o = list(filter(lambda x: x.picture is not None, usermodels)) #ZNY 过滤有头像的用户 + if o: #ZNY 如果有头像的用户存在 + return o[0].picture #ZNY 返回用户自定义头像 + email = email.encode('utf-8') #ZNY 编码邮箱 + + default = static('blog/img/avatar.png') #ZNY 设置默认头像路径 + + url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( #ZNY 生成Gravatar URL + email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) #ZNY 使用MD5哈希和URL参数 + cache.set(cachekey, url, 60 * 60 * 10) #ZNY 将URL存入缓存,有效期10小时 + logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) #ZNY 记录缓存设置日志 + return url #ZNY 返回头像URL + + +@register.filter #ZNY 注册模板过滤器 +def gravatar(email, size=40): #ZNY 获取Gravatar头像HTML过滤器 """获得gravatar头像""" - url = gravatar_url(email, size) - return mark_safe( + url = gravatar_url(email, size) #ZNY 获取头像URL + return mark_safe( #ZNY 返回安全的HTML图像标签 '' % (url, size, size)) -@register.simple_tag -def query(qs, **kwargs): +@register.simple_tag #ZNY 注册简单模板标签 +def query(qs, **kwargs): #ZNY 查询集过滤模板标签 """ template tag which allows queryset filtering. Usage: {% query books author=author as mybooks %} {% for book in mybooks %} ... {% endfor %} """ - return qs.filter(**kwargs) + return qs.filter(**kwargs) #ZNY 应用过滤条件并返回查询集 -@register.filter -def addstr(arg1, arg2): +@register.filter #ZNY 注册模板过滤器 +def addstr(arg1, arg2): #ZNY 字符串连接过滤器 """concatenate arg1 & arg2""" - return str(arg1) + str(arg2) + return str(arg1) + str(arg2) #ZNY 将两个参数转换为字符串后连接 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/tests.py b/src/DjangoBlog-master/blog/tests.py index ee13505..7a204ea 100644 --- a/src/DjangoBlog-master/blog/tests.py +++ b/src/DjangoBlog-master/blog/tests.py @@ -19,214 +19,214 @@ from oauth.models import OAuthUser, OAuthConfig # Create your tests here. -class ArticleTest(TestCase): - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - - def test_validate_article(self): - site = get_current_site().domain - user = BlogUser.objects.get_or_create( +class ArticleTest(TestCase): #ZNY 定义文章测试类,继承Django测试用例 + def setUp(self): #ZNY 测试初始化方法 + self.client = Client() #ZNY 创建测试客户端 + self.factory = RequestFactory() #ZNY 创建请求工厂 + + def test_validate_article(self): #ZNY 测试文章验证方法 + site = get_current_site().domain #ZNY 获取当前站点域名 + user = BlogUser.objects.get_or_create( #ZNY 获取或创建测试用户 email="liangliangyy@gmail.com", 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 - s.save() - - category = Category() - category.name = "category" - category.creation_time = timezone.now() - category.last_mod_time = timezone.now() - category.save() - - 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.save() - self.assertEqual(0, article.tags.count()) - article.tags.add(tag) - article.save() - self.assertEqual(1, article.tags.count()) - - for i in range(20): - article = Article() - article.title = "nicetitle" + str(i) - article.body = "nicetitle" + str(i) - article.author = user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - article.tags.add(tag) - 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) - - 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) - - 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) - - self.client.login(username='liangliangyy', password='liangliangyy') - - 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.filter(tags=tag), settings.PAGINATE_BY) - self.check_pagination(p, '分类标签归档', tag.slug) - - p = Paginator( + user.set_password("liangliangyy") #ZNY 设置用户密码 + user.is_staff = True #ZNY 设置为管理员 + user.is_superuser = True #ZNY 设置为超级用户 + user.save() #ZNY 保存用户 + response = self.client.get(user.get_absolute_url()) #ZNY 访问用户详情页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + response = self.client.get('/admin/servermanager/emailsendlog/') #ZNY 访问邮件发送日志管理页 + response = self.client.get('admin/admin/logentry/') #ZNY 访问日志条目管理页 + s = SideBar() #ZNY 创建侧边栏实例 + s.sequence = 1 #ZNY 设置排序序号 + s.name = 'test' #ZNY 设置侧边栏名称 + s.content = 'test content' #ZNY 设置侧边栏内容 + s.is_enable = True #ZNY 启用侧边栏 + s.save() #ZNY 保存侧边栏 + + category = Category() #ZNY 创建分类实例 + category.name = "category" #ZNY 设置分类名称 + category.creation_time = timezone.now() #ZNY 设置创建时间 + category.last_mod_time = timezone.now() #ZNY 设置修改时间 + category.save() #ZNY 保存分类 + + tag = Tag() #ZNY 创建标签实例 + tag.name = "nicetag" #ZNY 设置标签名称 + tag.save() #ZNY 保存标签 + + article = Article() #ZNY 创建文章实例 + article.title = "nicetitle" #ZNY 设置文章标题 + article.body = "nicecontent" #ZNY 设置文章正文 + article.author = user #ZNY 设置文章作者 + article.category = category #ZNY 设置文章分类 + article.type = 'a' #ZNY 设置文章类型为普通文章 + article.status = 'p' #ZNY 设置文章状态为发布 + + article.save() #ZNY 保存文章 + self.assertEqual(0, article.tags.count()) #ZNY 断言初始标签数量为0 + article.tags.add(tag) #ZNY 添加标签到文章 + article.save() #ZNY 保存文章 + self.assertEqual(1, article.tags.count()) #ZNY 断言标签数量为1 + + for i in range(20): #ZNY 循环创建20篇文章 + article = Article() #ZNY 创建文章实例 + article.title = "nicetitle" + str(i) #ZNY 设置带编号的文章标题 + article.body = "nicetitle" + str(i) #ZNY 设置带编号的文章正文 + article.author = user #ZNY 设置文章作者 + article.category = category #ZNY 设置文章分类 + article.type = 'a' #ZNY 设置文章类型为普通文章 + article.status = 'p' #ZNY 设置文章状态为发布 + article.save() #ZNY 保存文章 + article.tags.add(tag) #ZNY 添加标签到文章 + article.save() #ZNY 保存文章 + from blog.documents import ELASTICSEARCH_ENABLED #ZNY 导入Elasticsearch启用标志 + if ELASTICSEARCH_ENABLED: #ZNY 如果Elasticsearch已启用 + call_command("build_index") #ZNY 调用构建索引命令 + response = self.client.get('/search', {'q': 'nicetitle'}) #ZNY 搜索文章 + self.assertEqual(response.status_code, 200) #ZNY 断言搜索响应状态码为200 + + response = self.client.get(article.get_absolute_url()) #ZNY 访问文章详情页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + from djangoblog.spider_notify import SpiderNotify #ZNY 导入蜘蛛通知模块 + SpiderNotify.notify(article.get_absolute_url()) #ZNY 通知蜘蛛文章URL + response = self.client.get(tag.get_absolute_url()) #ZNY 访问标签详情页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + response = self.client.get(category.get_absolute_url()) #ZNY 访问分类详情页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + response = self.client.get('/search', {'q': 'django'}) #ZNY 搜索django相关内容 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + s = load_articletags(article) #ZNY 加载文章标签 + self.assertIsNotNone(s) #ZNY 断言标签不为空 + + self.client.login(username='liangliangyy', password='liangliangyy') #ZNY 登录用户 + + response = self.client.get(reverse('blog:archives')) #ZNY 访问文章归档页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + p = Paginator(Article.objects.all(), settings.PAGINATE_BY) #ZNY 创建文章分页器 + self.check_pagination(p, '', '') #ZNY 检查首页分页 + + p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY) #ZNY 创建标签文章分页器 + self.check_pagination(p, '分类标签归档', tag.slug) #ZNY 检查标签分页 + + p = Paginator( #ZNY 创建作者文章分页器 Article.objects.filter( author__username='liangliangyy'), settings.PAGINATE_BY) - self.check_pagination(p, '作者文章归档', 'liangliangyy') + self.check_pagination(p, '作者文章归档', 'liangliangyy') #ZNY 检查作者分页 - 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) #ZNY 创建分类文章分页器 + self.check_pagination(p, '分类目录归档', category.slug) #ZNY 检查分类分页 - f = BlogSearchForm() - f.search() - # self.client.login(username='liangliangyy', password='liangliangyy') - from djangoblog.spider_notify import SpiderNotify - SpiderNotify.baidu_notify([article.get_full_url()]) + f = BlogSearchForm() #ZNY 创建搜索表单实例 + f.search() #ZNY 执行搜索 + # self.client.login(username='liangliangyy', password='liangliangyy') #ZNY 注释掉的登录代码 + from djangoblog.spider_notify import SpiderNotify #ZNY 导入蜘蛛通知模块 + SpiderNotify.baidu_notify([article.get_full_url()]) #ZNY 通知百度文章完整URL - from blog.templatetags.blog_tags import gravatar_url, gravatar - u = gravatar_url('liangliangyy@gmail.com') - u = gravatar('liangliangyy@gmail.com') + from blog.templatetags.blog_tags import gravatar_url, gravatar #ZNY 导入Gravatar相关函数 + u = gravatar_url('liangliangyy@gmail.com') #ZNY 获取Gravatar头像URL + u = gravatar('liangliangyy@gmail.com') #ZNY 获取Gravatar头像HTML - link = Links( + link = Links( #ZNY 创建友情链接实例 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('/feed/') - 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/') - - 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) - - def test_image(self): - import requests - rsp = requests.get( + link.save() #ZNY 保存链接 + response = self.client.get('/links.html') #ZNY 访问友情链接页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + response = self.client.get('/feed/') #ZNY 访问RSS订阅 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + response = self.client.get('/sitemap.xml') #ZNY 访问站点地图 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + self.client.get("/admin/blog/article/1/delete/") #ZNY 访问文章删除页 + self.client.get('/admin/servermanager/emailsendlog/') #ZNY 访问邮件发送日志管理页 + self.client.get('/admin/admin/logentry/') #ZNY 访问日志条目管理页 + self.client.get('/admin/admin/logentry/1/change/') #ZNY 访问日志条目编辑页 + + def check_pagination(self, p, type, value): #ZNY 检查分页方法 + for page in range(1, p.num_pages + 1): #ZNY 遍历所有分页 + s = load_pagination_info(p.page(page), type, value) #ZNY 加载分页信息 + self.assertIsNotNone(s) #ZNY 断言分页信息不为空 + if s['previous_url']: #ZNY 如果存在上一页URL + response = self.client.get(s['previous_url']) #ZNY 访问上一页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + if s['next_url']: #ZNY 如果存在下一页URL + response = self.client.get(s['next_url']) #ZNY 访问下一页 + self.assertEqual(response.status_code, 200) #ZNY 断言响应状态码为200 + + def test_image(self): #ZNY 测试图片相关功能 + import requests #ZNY 导入requests库 + rsp = requests.get( #ZNY 下载图片 'https://www.python.org/static/img/python-logo.png') - imagepath = os.path.join(settings.BASE_DIR, 'python.png') - with open(imagepath, 'wb') as file: + imagepath = os.path.join(settings.BASE_DIR, 'python.png') #ZNY 构建图片保存路径 + with open(imagepath, 'wb') as file: #ZNY 保存图片到本地 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( + rsp = self.client.post('/upload') #ZNY 测试未授权上传 + self.assertEqual(rsp.status_code, 403) #ZNY 断言返回403禁止访问 + sign = get_sha256(get_sha256(settings.SECRET_KEY)) #ZNY 生成上传签名 + with open(imagepath, 'rb') as file: #ZNY 打开图片文件 + imgfile = SimpleUploadedFile( #ZNY 创建上传文件对象 'python.png', file.read(), content_type='image/jpg') - form_data = {'python.png': imgfile} - rsp = self.client.post( + form_data = {'python.png': imgfile} #ZNY 构建表单数据 + rsp = self.client.post( #ZNY 执行上传请求 '/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') - save_user_avatar( + self.assertEqual(rsp.status_code, 200) #ZNY 断言上传成功 + os.remove(imagepath) #ZNY 删除临时图片文件 + from djangoblog.utils import save_user_avatar, send_email #ZNY 导入工具函数 + send_email(['qq@qq.com'], 'testTitle', 'testContent') #ZNY 测试发送邮件 + save_user_avatar( #ZNY 测试保存用户头像 'https://www.python.org/static/img/python-logo.png') - def test_errorpage(self): - rsp = self.client.get('/eee') - self.assertEqual(rsp.status_code, 404) + def test_errorpage(self): #ZNY 测试错误页面 + rsp = self.client.get('/eee') #ZNY 访问不存在的页面 + self.assertEqual(rsp.status_code, 404) #ZNY 断言返回404错误 - def test_commands(self): - user = BlogUser.objects.get_or_create( + def test_commands(self): #ZNY 测试管理命令 + user = BlogUser.objects.get_or_create( #ZNY 获取或创建测试用户 email="liangliangyy@gmail.com", 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.save() - - u = OAuthUser() - u.type = 'qq' - u.openid = 'openid' - u.user = user - u.picture = static("/blog/img/avatar.png") - u.metadata = ''' + user.set_password("liangliangyy") #ZNY 设置用户密码 + user.is_staff = True #ZNY 设置为管理员 + user.is_superuser = True #ZNY 设置为超级用户 + user.save() #ZNY 保存用户 + + c = OAuthConfig() #ZNY 创建OAuth配置实例 + c.type = 'qq' #ZNY 设置OAuth类型为QQ + c.appkey = 'appkey' #ZNY 设置应用密钥 + c.appsecret = 'appsecret' #ZNY 设置应用密钥 + c.save() #ZNY 保存配置 + + u = OAuthUser() #ZNY 创建OAuth用户实例 + u.type = 'qq' #ZNY 设置OAuth类型为QQ + u.openid = 'openid' #ZNY 设置OpenID + u.user = user #ZNY 关联用户 + u.picture = static("/blog/img/avatar.png") #ZNY 设置头像路径 + u.metadata = ''' #ZNY 设置元数据 { "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" }''' - u.save() + u.save() #ZNY 保存OAuth用户 - u = OAuthUser() - u.type = 'qq' - u.openid = 'openid1' - u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30' - u.metadata = ''' + u = OAuthUser() #ZNY 创建另一个OAuth用户实例 + u.type = 'qq' #ZNY 设置OAuth类型为QQ + u.openid = 'openid1' #ZNY 设置不同的OpenID + u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30' #ZNY 设置头像URL + u.metadata = ''' #ZNY 设置元数据 { "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") + u.save() #ZNY 保存OAuth用户 + + from blog.documents import ELASTICSEARCH_ENABLED #ZNY 导入Elasticsearch启用标志 + if ELASTICSEARCH_ENABLED: #ZNY 如果Elasticsearch已启用 + call_command("build_index") #ZNY 调用构建索引命令 + call_command("ping_baidu", "all") #ZNY 调用百度推送命令 + call_command("create_testdata") #ZNY 调用创建测试数据命令 + call_command("clear_cache") #ZNY 调用清除缓存命令 + call_command("sync_user_avatar") #ZNY 调用同步用户头像命令 + call_command("build_search_words") #ZNY 调用构建搜索词命令 diff --git a/src/DjangoBlog-master/blog/urls.py b/src/DjangoBlog-master/blog/urls.py index adf2703..39f0007 100644 --- a/src/DjangoBlog-master/blog/urls.py +++ b/src/DjangoBlog-master/blog/urls.py @@ -1,62 +1,62 @@ -from django.urls import path -from django.views.decorators.cache import cache_page +from django.urls import path #ZNY 导入Django URL路径模块 +from django.views.decorators.cache import cache_page #ZNY 导入缓存页面装饰器 -from . import views +from . import views #ZNY 导入当前应用的视图模块 -app_name = "blog" -urlpatterns = [ - path( +app_name = "blog" #ZNY 定义应用命名空间为blog +urlpatterns = [ #ZNY 定义URL模式列表 + path( #ZNY 首页路由 r'', - views.IndexView.as_view(), - name='index'), - path( - r'page//', - views.IndexView.as_view(), - name='index_page'), - path( - r'article////.html', - views.ArticleDetailView.as_view(), - name='detailbyid'), - path( - r'category/.html', - views.CategoryDetailView.as_view(), - name='category_detail'), - path( - r'category//.html', - views.CategoryDetailView.as_view(), - name='category_detail_page'), - path( - r'author/.html', - views.AuthorDetailView.as_view(), - name='author_detail'), - path( - r'author//.html', - views.AuthorDetailView.as_view(), - name='author_detail_page'), - path( - r'tag/.html', - views.TagDetailView.as_view(), - name='tag_detail'), - path( - r'tag//.html', - views.TagDetailView.as_view(), - name='tag_detail_page'), - path( + views.IndexView.as_view(), #ZNY 使用类视图处理首页 + name='index'), #ZNY URL名称为index + path( #ZNY 首页分页路由 + r'page//', #ZNY 带页码参数的URL + views.IndexView.as_view(), #ZNY 使用相同的类视图处理分页 + name='index_page'), #ZNY URL名称为index_page + path( #ZNY 文章详情页路由 + r'article////.html', #ZNY 包含年月日和文章ID的URL + views.ArticleDetailView.as_view(), #ZNY 使用文章详情类视图 + name='detailbyid'), #ZNY URL名称为detailbyid + path( #ZNY 分类详情页路由 + r'category/.html', #ZNY 包含分类名称的URL + views.CategoryDetailView.as_view(), #ZNY 使用分类详情类视图 + name='category_detail'), #ZNY URL名称为category_detail + path( #ZNY 分类分页路由 + r'category//.html', #ZNY 包含分类名称和页码的URL + views.CategoryDetailView.as_view(), #ZNY 使用相同的类视图处理分页 + name='category_detail_page'), #ZNY URL名称为category_detail_page + path( #ZNY 作者详情页路由 + r'author/.html', #ZNY 包含作者名称的URL + views.AuthorDetailView.as_view(), #ZNY 使用作者详情类视图 + name='author_detail'), #ZNY URL名称为author_detail + path( #ZNY 作者分页路由 + r'author//.html', #ZNY 包含作者名称和页码的URL + views.AuthorDetailView.as_view(), #ZNY 使用相同的类视图处理分页 + name='author_detail_page'), #ZNY URL名称为author_detail_page + path( #ZNY 标签详情页路由 + r'tag/.html', #ZNY 包含标签名称的URL + views.TagDetailView.as_view(), #ZNY 使用标签详情类视图 + name='tag_detail'), #ZNY URL名称为tag_detail + path( #ZNY 标签分页路由 + r'tag//.html', #ZNY 包含标签名称和页码的URL + views.TagDetailView.as_view(), #ZNY 使用相同的类视图处理分页 + name='tag_detail_page'), #ZNY URL名称为tag_detail_page + path( #ZNY 文章归档页路由 'archives.html', - cache_page( - 60 * 60)( - views.ArchivesView.as_view()), - name='archives'), - path( + cache_page( #ZNY 使用缓存装饰器缓存页面 + 60 * 60)( #ZNY 缓存1小时 + views.ArchivesView.as_view()), #ZNY 使用归档视图 + name='archives'), #ZNY URL名称为archives + path( #ZNY 友情链接页路由 'links.html', - views.LinkListView.as_view(), - name='links'), - path( + views.LinkListView.as_view(), #ZNY 使用链接列表视图 + name='links'), #ZNY URL名称为links + path( #ZNY 文件上传路由 r'upload', - views.fileupload, - name='upload'), - path( + views.fileupload, #ZNY 使用函数视图处理文件上传 + name='upload'), #ZNY URL名称为upload + path( #ZNY 清理缓存路由 r'clean', - views.clean_cache_view, - name='clean'), + views.clean_cache_view, #ZNY 使用函数视图清理缓存 + name='clean'), #ZNY URL名称为clean ] diff --git a/src/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/blog/views.py index d5dc7ec..29041e3 100644 --- a/src/DjangoBlog-master/blog/views.py +++ b/src/DjangoBlog-master/blog/views.py @@ -21,359 +21,359 @@ from djangoblog.plugin_manage import hooks from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME from djangoblog.utils import cache, get_blog_setting, get_sha256 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #ZNY 获取当前模块的日志记录器 -class ArticleListView(ListView): +class ArticleListView(ListView): #ZNY 文章列表视图基类,继承Django通用列表视图 # template_name属性用于指定使用哪个模板进行渲染 - template_name = 'blog/article_index.html' + template_name = 'blog/article_index.html' #ZNY 指定使用的模板文件 # context_object_name属性用于给上下文变量取名(在模板中使用该名字) - context_object_name = 'article_list' + context_object_name = 'article_list' #ZNY 模板中使用的上下文变量名 # 页面类型,分类目录或标签列表等 - page_type = '' - paginate_by = settings.PAGINATE_BY - page_kwarg = 'page' - link_type = LinkShowType.L + page_type = '' #ZNY 页面类型标识 + paginate_by = settings.PAGINATE_BY #ZNY 每页显示的文章数量 + page_kwarg = 'page' #ZNY 分页参数名称 + link_type = LinkShowType.L #ZNY 链接显示类型,默认为列表页 - def get_view_cache_key(self): - return self.request.get['pages'] + def get_view_cache_key(self): #ZNY 获取视图缓存键方法 + return self.request.get['pages'] #ZNY 从请求中获取页面参数作为缓存键 @property - def page_number(self): - page_kwarg = self.page_kwarg - page = self.kwargs.get( + def page_number(self): #ZNY 页码属性,计算当前页码 + page_kwarg = self.page_kwarg #ZNY 获取分页参数名 + page = self.kwargs.get( #ZNY 从URL参数、GET参数或默认值获取页码 page_kwarg) or self.request.GET.get(page_kwarg) or 1 - return page + return page #ZNY 返回当前页码 - def get_queryset_cache_key(self): + def get_queryset_cache_key(self): #ZNY 获取查询集缓存键方法 """ 子类重写.获得queryset的缓存key """ - raise NotImplementedError() + raise NotImplementedError() #ZNY 要求子类必须实现此方法 - def get_queryset_data(self): + def get_queryset_data(self): #ZNY 获取查询集数据方法 """ 子类重写.获取queryset的数据 """ - raise NotImplementedError() + raise NotImplementedError() #ZNY 要求子类必须实现此方法 - def get_queryset_from_cache(self, cache_key): + def get_queryset_from_cache(self, cache_key): #ZNY 从缓存获取查询集方法 ''' 缓存页面数据 :param cache_key: 缓存key :return: ''' - value = cache.get(cache_key) - if value: - 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)) - return article_list - - def get_queryset(self): + value = cache.get(cache_key) #ZNY 尝试从缓存获取数据 + if value: #ZNY 如果缓存存在 + logger.info('get view cache.key:{key}'.format(key=cache_key)) #ZNY 记录缓存命中日志 + return value #ZNY 返回缓存数据 + else: #ZNY 如果缓存不存在 + article_list = self.get_queryset_data() #ZNY 从数据库获取数据 + cache.set(cache_key, article_list) #ZNY 将数据存入缓存 + logger.info('set view cache.key:{key}'.format(key=cache_key)) #ZNY 记录缓存设置日志 + return article_list #ZNY 返回数据 + + def get_queryset(self): #ZNY 重写获取查询集方法 ''' 重写默认,从缓存获取数据 :return: ''' - key = self.get_queryset_cache_key() - value = self.get_queryset_from_cache(key) - return value + key = self.get_queryset_cache_key() #ZNY 获取缓存键 + value = self.get_queryset_from_cache(key) #ZNY 从缓存获取数据 + return value #ZNY 返回查询集 - def get_context_data(self, **kwargs): - kwargs['linktype'] = self.link_type - return super(ArticleListView, self).get_context_data(**kwargs) + def get_context_data(self, **kwargs): #ZNY 获取上下文数据方法 + kwargs['linktype'] = self.link_type #ZNY 添加链接类型到上下文 + return super(ArticleListView, self).get_context_data(**kwargs) #ZNY 调用父类方法 -class IndexView(ArticleListView): +class IndexView(ArticleListView): #ZNY 首页视图,继承文章列表视图 ''' 首页 ''' # 友情链接类型 - link_type = LinkShowType.I + link_type = LinkShowType.I #ZNY 设置链接显示类型为首页 - def get_queryset_data(self): - article_list = Article.objects.filter(type='a', status='p') - return article_list + def get_queryset_data(self): #ZNY 实现获取查询集数据方法 + article_list = Article.objects.filter(type='a', status='p') #ZNY 获取所有已发布的普通文章 + return article_list #ZNY 返回文章列表 - def get_queryset_cache_key(self): - cache_key = 'index_{page}'.format(page=self.page_number) - return cache_key + def get_queryset_cache_key(self): #ZNY 实现获取查询集缓存键方法 + cache_key = 'index_{page}'.format(page=self.page_number) #ZNY 生成首页缓存键,包含页码 + return cache_key #ZNY 返回缓存键 -class ArticleDetailView(DetailView): +class ArticleDetailView(DetailView): #ZNY 文章详情视图,继承Django通用详情视图 ''' 文章详情页面 ''' - template_name = 'blog/article_detail.html' - model = Article - pk_url_kwarg = 'article_id' - context_object_name = "article" - - def get_context_data(self, **kwargs): - comment_form = CommentForm() - - article_comments = self.object.comment_list() - 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: - page = 1 - 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 - - if next_page: - kwargs[ + template_name = 'blog/article_detail.html' #ZNY 指定详情页模板 + model = Article #ZNY 指定模型类 + pk_url_kwarg = 'article_id' #ZNY URL中的主键参数名 + context_object_name = "article" #ZNY 模板中使用的上下文变量名 + + def get_context_data(self, **kwargs): #ZNY 重写获取上下文数据方法 + comment_form = CommentForm() #ZNY 创建评论表单实例 + + article_comments = self.object.comment_list() #ZNY 获取文章评论列表 + parent_comments = article_comments.filter(parent_comment=None) #ZNY 获取顶级评论(无父评论) + blog_setting = get_blog_setting() #ZNY 获取博客设置 + paginator = Paginator(parent_comments, blog_setting.article_comment_count) #ZNY 创建评论分页器 + page = self.request.GET.get('comment_page', '1') #ZNY 获取评论页码,默认为1 + if not page.isnumeric(): #ZNY 如果页码不是数字 + page = 1 #ZNY 设置为第一页 + else: #ZNY 如果是数字 + page = int(page) #ZNY 转换为整数 + if page < 1: #ZNY 如果页码小于1 + page = 1 #ZNY 设置为第一页 + if page > paginator.num_pages: #ZNY 如果页码大于总页数 + page = paginator.num_pages #ZNY 设置为最后一页 + + p_comments = paginator.page(page) #ZNY 获取指定页的评论 + next_page = p_comments.next_page_number() if p_comments.has_next() else None #ZNY 计算下一页页码 + prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None #ZNY 计算上一页页码 + + if next_page: #ZNY 如果存在下一页 + kwargs[ #ZNY 设置下一页评论URL 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container' - if prev_page: - kwargs[ + if prev_page: #ZNY 如果存在上一页 + kwargs[ #ZNY 设置上一页评论URL '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 - kwargs['comment_count'] = len( + kwargs['form'] = comment_form #ZNY 添加评论表单到上下文 + kwargs['article_comments'] = article_comments #ZNY 添加所有评论到上下文 + kwargs['p_comments'] = p_comments #ZNY 添加分页评论到上下文 + kwargs['comment_count'] = len( #ZNY 添加评论总数到上下文 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 #ZNY 添加下一篇文章到上下文 + kwargs['prev_article'] = self.object.prev_article #ZNY 添加上一篇文章到上下文 - context = super(ArticleDetailView, self).get_context_data(**kwargs) - article = self.object + context = super(ArticleDetailView, self).get_context_data(**kwargs) #ZNY 调用父类方法获取基础上下文 + article = self.object #ZNY 获取当前文章对象 # 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) #ZNY 执行文章内容获取后的动作钩子 # # Filter Hook, 允许插件修改文章正文 - article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article, + article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article, #ZNY 应用文章内容过滤器 request=self.request) - return context + return context #ZNY 返回完整的上下文数据 -class CategoryDetailView(ArticleListView): +class CategoryDetailView(ArticleListView): #ZNY 分类详情视图,继承文章列表视图 ''' 分类目录列表 ''' - page_type = "分类目录归档" + page_type = "分类目录归档" #ZNY 设置页面类型 - def get_queryset_data(self): - slug = self.kwargs['category_name'] - category = get_object_or_404(Category, slug=slug) + def get_queryset_data(self): #ZNY 实现获取查询集数据方法 + slug = self.kwargs['category_name'] #ZNY 从URL参数获取分类slug + category = get_object_or_404(Category, slug=slug) #ZNY 获取分类对象,不存在则404 - categoryname = category.name - self.categoryname = categoryname - categorynames = list( + categoryname = category.name #ZNY 获取分类名称 + self.categoryname = categoryname #ZNY 保存分类名称到实例变量 + categorynames = list( #ZNY 获取所有子分类名称列表 map(lambda c: c.name, category.get_sub_categorys())) - article_list = Article.objects.filter( + article_list = Article.objects.filter( #ZNY 获取该分类及其子分类下的所有已发布文章 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 - cache_key = 'category_list_{categoryname}_{page}'.format( + return article_list #ZNY 返回文章列表 + + def get_queryset_cache_key(self): #ZNY 实现获取查询集缓存键方法 + slug = self.kwargs['category_name'] #ZNY 从URL参数获取分类slug + category = get_object_or_404(Category, slug=slug) #ZNY 获取分类对象 + categoryname = category.name #ZNY 获取分类名称 + self.categoryname = categoryname #ZNY 保存分类名称到实例变量 + cache_key = 'category_list_{categoryname}_{page}'.format( #ZNY 生成分类缓存键 categoryname=categoryname, page=self.page_number) - return cache_key + return cache_key #ZNY 返回缓存键 - def get_context_data(self, **kwargs): + def get_context_data(self, **kwargs): #ZNY 重写获取上下文数据方法 - categoryname = self.categoryname + categoryname = self.categoryname #ZNY 获取分类名称 try: - categoryname = categoryname.split('/')[-1] + categoryname = categoryname.split('/')[-1] #ZNY 尝试获取分类路径的最后一部分 except BaseException: - pass - kwargs['page_type'] = CategoryDetailView.page_type - kwargs['tag_name'] = categoryname - return super(CategoryDetailView, self).get_context_data(**kwargs) + pass #ZNY 如果出错则忽略 + kwargs['page_type'] = CategoryDetailView.page_type #ZNY 添加页面类型到上下文 + kwargs['tag_name'] = categoryname #ZNY 添加分类名称到上下文 + return super(CategoryDetailView, self).get_context_data(**kwargs) #ZNY 调用父类方法 -class AuthorDetailView(ArticleListView): +class AuthorDetailView(ArticleListView): #ZNY 作者详情视图,继承文章列表视图 ''' 作者详情页 ''' - page_type = '作者文章归档' + page_type = '作者文章归档' #ZNY 设置页面类型 - def get_queryset_cache_key(self): - from uuslug import slugify - author_name = slugify(self.kwargs['author_name']) - cache_key = 'author_{author_name}_{page}'.format( + def get_queryset_cache_key(self): #ZNY 实现获取查询集缓存键方法 + from uuslug import slugify #ZNY 导入slugify函数 + author_name = slugify(self.kwargs['author_name']) #ZNY 对作者名称进行slugify处理 + cache_key = 'author_{author_name}_{page}'.format( #ZNY 生成作者缓存键 author_name=author_name, page=self.page_number) - return cache_key + return cache_key #ZNY 返回缓存键 - def get_queryset_data(self): - author_name = self.kwargs['author_name'] - article_list = Article.objects.filter( + def get_queryset_data(self): #ZNY 实现获取查询集数据方法 + author_name = self.kwargs['author_name'] #ZNY 从URL参数获取作者名称 + article_list = Article.objects.filter( #ZNY 获取该作者的所有已发布普通文章 author__username=author_name, type='a', status='p') - return article_list + return article_list #ZNY 返回文章列表 - 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) + def get_context_data(self, **kwargs): #ZNY 重写获取上下文数据方法 + author_name = self.kwargs['author_name'] #ZNY 获取作者名称 + kwargs['page_type'] = AuthorDetailView.page_type #ZNY 添加页面类型到上下文 + kwargs['tag_name'] = author_name #ZNY 添加作者名称到上下文 + return super(AuthorDetailView, self).get_context_data(**kwargs) #ZNY 调用父类方法 -class TagDetailView(ArticleListView): +class TagDetailView(ArticleListView): #ZNY 标签详情视图,继承文章列表视图 ''' 标签列表页面 ''' - 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 - article_list = Article.objects.filter( + page_type = '分类标签归档' #ZNY 设置页面类型 + + def get_queryset_data(self): #ZNY 实现获取查询集数据方法 + slug = self.kwargs['tag_name'] #ZNY 从URL参数获取标签slug + tag = get_object_or_404(Tag, slug=slug) #ZNY 获取标签对象,不存在则404 + tag_name = tag.name #ZNY 获取标签名称 + self.name = tag_name #ZNY 保存标签名称到实例变量 + article_list = Article.objects.filter( #ZNY 获取该标签下的所有已发布普通文章 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 - cache_key = 'tag_{tag_name}_{page}'.format( + return article_list #ZNY 返回文章列表 + + def get_queryset_cache_key(self): #ZNY 实现获取查询集缓存键方法 + slug = self.kwargs['tag_name'] #ZNY 从URL参数获取标签slug + tag = get_object_or_404(Tag, slug=slug) #ZNY 获取标签对象 + tag_name = tag.name #ZNY 获取标签名称 + self.name = tag_name #ZNY 保存标签名称到实例变量 + cache_key = 'tag_{tag_name}_{page}'.format( #ZNY 生成标签缓存键 tag_name=tag_name, page=self.page_number) - return cache_key + return cache_key #ZNY 返回缓存键 - 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) + def get_context_data(self, **kwargs): #ZNY 重写获取上下文数据方法 + # tag_name = self.kwargs['tag_name'] #ZNY 注释掉的获取标签名称方式 + tag_name = self.name #ZNY 从实例变量获取标签名称 + kwargs['page_type'] = TagDetailView.page_type #ZNY 添加页面类型到上下文 + kwargs['tag_name'] = tag_name #ZNY 添加标签名称到上下文 + return super(TagDetailView, self).get_context_data(**kwargs) #ZNY 调用父类方法 -class ArchivesView(ArticleListView): +class ArchivesView(ArticleListView): #ZNY 文章归档视图,继承文章列表视图 ''' 文章归档页面 ''' - page_type = '文章归档' - paginate_by = None - page_kwarg = None - template_name = 'blog/article_archives.html' + page_type = '文章归档' #ZNY 设置页面类型 + paginate_by = None #ZNY 不分页 + page_kwarg = None #ZNY 无分页参数 + template_name = 'blog/article_archives.html' #ZNY 指定归档页面模板 - def get_queryset_data(self): - return Article.objects.filter(status='p').all() + def get_queryset_data(self): #ZNY 实现获取查询集数据方法 + return Article.objects.filter(status='p').all() #ZNY 获取所有已发布文章 - def get_queryset_cache_key(self): - cache_key = 'archives' - return cache_key + def get_queryset_cache_key(self): #ZNY 实现获取查询集缓存键方法 + cache_key = 'archives' #ZNY 归档页面使用固定缓存键 + return cache_key #ZNY 返回缓存键 -class LinkListView(ListView): - model = Links - template_name = 'blog/links_list.html' +class LinkListView(ListView): #ZNY 友情链接列表视图,继承Django通用列表视图 + model = Links #ZNY 指定模型类 + template_name = 'blog/links_list.html' #ZNY 指定模板文件 - def get_queryset(self): - return Links.objects.filter(is_enable=True) + def get_queryset(self): #ZNY 重写获取查询集方法 + return Links.objects.filter(is_enable=True) #ZNY 只返回启用的友情链接 -class EsSearchView(SearchView): - def get_context(self): - paginator, page = self.build_page() - context = { - "query": self.query, - "form": self.form, - "page": page, - "paginator": paginator, - "suggestion": None, +class EsSearchView(SearchView): #ZNY Elasticsearch搜索视图,继承Haystack搜索视图 + def get_context(self): #ZNY 重写获取上下文方法 + paginator, page = self.build_page() #ZNY 构建分页 + context = { #ZNY 构建上下文字典 + "query": self.query, #ZNY 搜索查询词 + "form": self.form, #ZNY 搜索表单 + "page": page, #ZNY 当前页 + "paginator": paginator, #ZNY 分页器 + "suggestion": None, #ZNY 搜索建议,初始为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()) + if hasattr(self.results, "query") and self.results.query.backend.include_spelling: #ZNY 如果支持拼写建议 + context["suggestion"] = self.results.query.get_spelling_suggestion() #ZNY 获取拼写建议 + context.update(self.extra_context()) #ZNY 更新额外上下文 - return context + return context #ZNY 返回完整上下文 -@csrf_exempt -def fileupload(request): +@csrf_exempt #ZNY 免除CSRF保护 +def fileupload(request): #ZNY 文件上传视图函数 """ 该方法需自己写调用端来上传图片,该方法仅提供图床功能 :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): - return HttpResponse("only for post") - with open(savepath, 'wb+') as wfile: - for chunk in request.FILES[filename].chunks(): + if request.method == 'POST': #ZNY 只处理POST请求 + sign = request.GET.get('sign', None) #ZNY 从GET参数获取签名 + if not sign: #ZNY 如果没有签名 + return HttpResponseForbidden() #ZNY 返回403禁止访问 + if not sign == get_sha256(get_sha256(settings.SECRET_KEY)): #ZNY 验证签名是否正确 + return HttpResponseForbidden() #ZNY 签名错误返回403 + response = [] #ZNY 初始化响应列表 + for filename in request.FILES: #ZNY 遍历所有上传的文件 + timestr = timezone.now().strftime('%Y/%m/%d') #ZNY 生成时间目录字符串 + imgextensions = ['jpg', 'png', 'jpeg', 'bmp'] #ZNY 图片扩展名列表 + fname = u''.join(str(filename)) #ZNY 获取文件名 + isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0 #ZNY 判断是否为图片文件 + base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr) #ZNY 构建保存目录 + if not os.path.exists(base_dir): #ZNY 如果目录不存在 + os.makedirs(base_dir) #ZNY 创建目录 + savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) #ZNY 生成保存路径,使用UUID作为文件名 + if not savepath.startswith(base_dir): #ZNY 安全检查,确保路径在指定目录内 + return HttpResponse("only for post") #ZNY 路径非法返回错误 + with open(savepath, 'wb+') as wfile: #ZNY 打开文件进行写入 + for chunk in request.FILES[filename].chunks(): #ZNY 分块写入文件 wfile.write(chunk) - 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) + if isimage: #ZNY 如果是图片文件 + from PIL import Image #ZNY 导入PIL库 + image = Image.open(savepath) #ZNY 打开图片 + image.save(savepath, quality=20, optimize=True) #ZNY 压缩图片并保存 + url = static(savepath) #ZNY 生成静态文件URL + response.append(url) #ZNY 将URL添加到响应列表 + return HttpResponse(response) #ZNY 返回URL列表 - else: - return HttpResponse("only for post") + else: #ZNY 非POST请求 + return HttpResponse("only for post") #ZNY 返回只支持POST请求 -def page_not_found_view( +def page_not_found_view( #ZNY 404页面未找到视图 request, exception, template_name='blog/error_page.html'): - if exception: - logger.error(exception) - url = request.get_full_path() - return render(request, + if exception: #ZNY 如果有异常信息 + logger.error(exception) #ZNY 记录错误日志 + url = request.get_full_path() #ZNY 获取请求的完整路径 + return render(request, #ZNY 渲染错误页面 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'}, #ZNY 错误消息和状态码 + status=404) #ZNY 返回404状态码 -def server_error_view(request, template_name='blog/error_page.html'): - return render(request, +def server_error_view(request, template_name='blog/error_page.html'): #ZNY 500服务器错误视图 + return render(request, #ZNY 渲染错误页面 template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), - 'statuscode': '500'}, - status=500) + 'statuscode': '500'}, #ZNY 错误消息和状态码 + status=500) #ZNY 返回500状态码 -def permission_denied_view( +def permission_denied_view( #ZNY 403权限拒绝视图 request, exception, template_name='blog/error_page.html'): - if exception: - logger.error(exception) - return render( + if exception: #ZNY 如果有异常信息 + logger.error(exception) #ZNY 记录错误日志 + return render( #ZNY 渲染错误页面 request, template_name, { 'message': _('Sorry, you do not have permission to access this page?'), - 'statuscode': '403'}, status=403) + 'statuscode': '403'}, status=403) #ZNY 错误消息和状态码,返回403状态码 -def clean_cache_view(request): - cache.clear() - return HttpResponse('ok') +def clean_cache_view(request): #ZNY 清理缓存视图 + cache.clear() #ZNY 清除所有缓存 + return HttpResponse('ok') #ZNY 返回成功响应 \ No newline at end of file