diff --git a/src/django-master/accounts/admin.py b/src/django-master/accounts/admin.py index 7409b10..5989ae5 100644 --- a/src/django-master/accounts/admin.py +++ b/src/django-master/accounts/admin.py @@ -49,13 +49,18 @@ class BlogUserCreationForm(forms.ModelForm): from .models import BlogUser# 导入当前应用下的BlogUser模型(自定义用户模型) +<<<<<<< HEAD class BlogUserCreationForm(forms.ModelForm): # 定义两个密码字段,使用PasswordInput小部件隐藏输入 +>>>>>>> LXY_branch +======= +class BlogUserCreationForm(forms.ModelForm): # lxy定义两个密码字段,使用PasswordInput小部件隐藏输入 >>>>>>> LXY_branch password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) # 确认密码字段,用于验证两次输入的密码是否一致 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: +<<<<<<< HEAD <<<<<<< HEAD # 指定关联的模型 model = BlogUser @@ -74,6 +79,12 @@ class BlogUserCreationForm(forms.ModelForm): # 定义两个密码字段,使用 fields = ('email',) # 表单中显示的字段(仅邮箱,密码单独定义) def clean_password2(self):# 验证两个密码是否一致 +>>>>>>> LXY_branch +======= + model = BlogUser# lxy关联的模型是BlogUser + fields = ('email',) # lxy表单中显示的字段(仅邮箱,密码单独定义) + + def clean_password2(self):# lxy验证两个密码是否一致 >>>>>>> LXY_branch # Check that the two password entries match password1 = self.cleaned_data.get("password1")# 获取第一次输入的密码 @@ -87,6 +98,7 @@ class BlogUserCreationForm(forms.ModelForm): # 定义两个密码字段,使用 # 检查密码是否存在且不一致 >>>>>>> JYN_branch if password1 and password2 and password1 != password2: +<<<<<<< HEAD <<<<<<< HEAD raise forms.ValidationError(_("passwords do not match"))# 错误信息(支持国际化) return password2# 返回验证后的值 @@ -118,15 +130,23 @@ class BlogUserCreationForm(forms.ModelForm): # 定义两个密码字段,使用 user.save() ======= raise forms.ValidationError(_("passwords do not match"))# 密码不一致时抛出错误 +======= + raise forms.ValidationError(_("passwords do not match"))# lxy密码不一致时抛出错误 +>>>>>>> LXY_branch return password2 - def save(self, commit=True):# 保存用户时,对密码进行哈希处理后存储 + def save(self, commit=True):# lxy保存用户时,对密码进行哈希处理后存储 # Save the provided password in hashed format - user = super().save(commit=False)# 先不提交到数据库 - user.set_password(self.cleaned_data["password1"]) # 哈希处理密码 + user = super().save(commit=False)# lxy先不提交到数据库 + user.set_password(self.cleaned_data["password1"]) # lxy哈希处理密码 if commit: +<<<<<<< HEAD user.source = 'adminsite'# 标记用户来源为“后台管理” user.save()# 提交到数据库 +>>>>>>> LXY_branch +======= + user.source = 'adminsite'# lxy标记用户来源为“后台管理” + user.save()# lxy提交到数据库 >>>>>>> LXY_branch return user >>>>>>> JYN_branch @@ -140,6 +160,7 @@ class BlogUserChangeForm(UserChangeForm): """ class Meta: +<<<<<<< HEAD <<<<<<< HEAD model = BlogUser # 关联的模型类 fields = '__all__'# 显示所有字段 @@ -210,14 +231,24 @@ class BlogUserAdmin(UserAdmin): model = BlogUser # 关联的模型是BlogUser fields = '__all__'# 显示模型的所有字段 field_classes = {'username': UsernameField}# 为用户名字段指定类(保持Django原生逻辑) +======= + model = BlogUser # lxy关联的模型是BlogUser + fields = '__all__'# lxy显示模型的所有字段 + field_classes = {'username': UsernameField}# lxy为用户名字段指定类(保持Django原生逻辑) +>>>>>>> LXY_branch def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs)# 调用父类的初始化方法 + super().__init__(*args, **kwargs)#lxy调用父类的初始化方法 class BlogUserAdmin(UserAdmin): +<<<<<<< HEAD form = BlogUserChangeForm# 指定修改用户时使用的表单 add_form = BlogUserCreationForm# 指定创建用户时使用的表单 +>>>>>>> LXY_branch +======= + form = BlogUserChangeForm#lxy指定修改用户时使用的表单 + add_form = BlogUserCreationForm# lxy指定创建用户时使用的表单 >>>>>>> LXY_branch list_display = ( 'id', @@ -236,6 +267,11 @@ class BlogUserAdmin(UserAdmin): >>>>>>> JYN_branch ======= 'source') +<<<<<<< HEAD list_display_links = ('id', 'username')# 列表页中可点击跳转的字段 ordering = ('-id',)# 列表页的排序方式(按ID倒序) >>>>>>> LXY_branch +======= + list_display_links = ('id', 'username')#lxy列表页中可点击跳转的字段 + ordering = ('-id',)#lxy列表页的排序方式(按ID倒序) +>>>>>>> LXY_branch diff --git a/src/django-master/accounts/apps.py b/src/django-master/accounts/apps.py index 5129d43..4cba08b 100644 --- a/src/django-master/accounts/apps.py +++ b/src/django-master/accounts/apps.py @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD from django.apps import AppConfig @@ -28,3 +29,10 @@ from django.apps import AppConfig#导入 Django 框架中用于应用配置的 A class AccountsConfig(AppConfig):#定义 AccountsConfig 类,继承自 AppConfig,用于对 accounts 应用进行自定义配置 name = 'accounts'#指定该应用的名称为 accounts,Django 会通过这个名称来识别和管理该应用 >>>>>>> LXY_branch +======= +from django.apps import AppConfig#lxy导入 Django 框架中用于应用配置的 AppConfig 类,这是 Django 应用配置的核心类 + + +class AccountsConfig(AppConfig):#lxy定义 AccountsConfig 类,继承自 AppConfig,用于对 accounts 应用进行自定义配置 + name = 'accounts'#lxy指定该应用的名称为 accounts,Django 会通过这个名称来识别和管理该应用 +>>>>>>> LXY_branch diff --git a/src/django-master/accounts/forms.py b/src/django-master/accounts/forms.py index df3e0f8..8578521 100644 --- a/src/django-master/accounts/forms.py +++ b/src/django-master/accounts/forms.py @@ -61,8 +61,12 @@ class LoginForm(AuthenticationForm): <<<<<<< HEAD ======= attrs={'placeholder': "password", "class": "form-control"}) +<<<<<<< HEAD #自定义登录表单,在__init__方法中设置username(文本输入,占位符、form-control样式)和password(密码输入,占位符、form-control样式)字段的前端显示样式。 >>>>>>> LXY_branch +======= +#lxy自定义登录表单,在__init__方法中设置username(文本输入,占位符、form-control样式)和password(密码输入,占位符、form-control样式)字段的前端显示样式。 +>>>>>>> LXY_branch <<<<<<< HEAD @@ -98,10 +102,14 @@ class RegisterForm(UserCreationForm): attrs={'placeholder': "password", "class": "form-control"}) self.fields['password2'].widget = widgets.PasswordInput( attrs={'placeholder': "repeat password", "class": "form-control"}) +<<<<<<< HEAD <<<<<<< HEAD # 验证邮箱唯一性 ======= #__init__方法中设置username(文本输入)、email(邮箱输入)、password1和password2(密码输入)字段的占位符与form-control样式 +>>>>>>> LXY_branch +======= +#lxy__init__方法中设置username(文本输入)、email(邮箱输入)、password1和password2(密码输入)字段的占位符与form-control样式 >>>>>>> LXY_branch def clean_email(self): """ @@ -113,6 +121,7 @@ class RegisterForm(UserCreationForm): if get_user_model().objects.filter(email=email).exists(): raise ValidationError(_("email already exists")) # 抛出验证错误 return email +<<<<<<< HEAD <<<<<<< HEAD # 指定关联的用户模型和表单字段 class Meta: @@ -125,6 +134,13 @@ class RegisterForm(UserCreationForm): fields = ("username", "email") #Meta类指定关联模型为自定义用户模型,表单字段包含username和email >>>>>>> LXY_branch +======= +#lxyclean_email方法验证邮箱是否已被注册,若存在则抛出“邮箱已存在”的验证错误 + class Meta: + model = get_user_model() + fields = ("username", "email") +#lxy Meta类指定关联模型为自定义用户模型,表单字段包含username和email +>>>>>>> LXY_branch # 忘记密码表单(验证邮箱和验证码) class ForgetPasswordForm(forms.Form): @@ -187,10 +203,14 @@ class ForgetPasswordForm(forms.Form): } ), ) +<<<<<<< HEAD <<<<<<< HEAD # 验证两次输入的密码是否一致,并检查密码强度 ======= #定义new_password1(新密码,密码输入)、new_password2(确认密码,密码输入)、email(邮箱,文本输入)、code(验证码,文本输入)字段,均设置form-control样式和占位符。 +>>>>>>> LXY_branch +======= +#lxy定义new_password1(新密码,密码输入)、new_password2(确认密码,密码输入)、email(邮箱,文本输入)、code(验证码,文本输入)字段,均设置form-control样式和占位符。 >>>>>>> LXY_branch def clean_new_password2(self): """验证两次输入的密码是否一致,并验证密码强度""" @@ -210,10 +230,14 @@ class ForgetPasswordForm(forms.Form): >>>>>>> JYN_branch return password2 +<<<<<<< HEAD <<<<<<< HEAD # 验证邮箱是否已注册 ======= # clean_new_password2方法验证两次新密码是否一致,并对密码进行有效性校验 +>>>>>>> LXY_branch +======= +#lxyclean_new_password2方法验证两次新密码是否一致,并对密码进行有效性校验 >>>>>>> LXY_branch def clean_email(self): """验证邮箱是否已注册""" @@ -227,7 +251,11 @@ class ForgetPasswordForm(forms.Form): # 验证用户输入的验证码是否正确 ======= +<<<<<<< HEAD # clean_email方法验证邮箱是否已注册(基于BlogUser模型),未注册则抛出“邮箱不存在”的验证错误 +>>>>>>> LXY_branch +======= +#lxyclean_email方法验证邮箱是否已注册(基于BlogUser模型),未注册则抛出“邮箱不存在”的验证错误 >>>>>>> LXY_branch def clean_code(self): """验证验证码是否有效""" @@ -245,10 +273,14 @@ class ForgetPasswordForm(forms.Form): raise ValidationError(error) # 验证码无效时抛出错误 return code <<<<<<< HEAD +<<<<<<< HEAD # 忘记密码功能中的验证码发送表单(仅需邮箱字段) ======= #clean_code方法调用工具方法utils.verify验证验证码有效性,无效则抛出错误 +======= +#lxy clean_code方法调用工具方法utils.verify验证验证码有效性,无效则抛出错误 +>>>>>>> LXY_branch >>>>>>> LXY_branch class ForgetPasswordCodeForm(forms.Form): @@ -260,7 +292,11 @@ class ForgetPasswordCodeForm(forms.Form): label=_('Email'), <<<<<<< HEAD ) +<<<<<<< HEAD ======= ) #仅包含email字段(邮箱输入),用于忘记密码流程中验证邮箱的步骤 >>>>>>> LXY_branch +======= +#lxy仅包含email字段(邮箱输入),用于忘记密码流程中验证邮箱的步骤 +>>>>>>> LXY_branch diff --git a/src/django-master/accounts/models.py b/src/django-master/accounts/models.py index 0662c2b..5529d12 100644 --- a/src/django-master/accounts/models.py +++ b/src/django-master/accounts/models.py @@ -35,11 +35,11 @@ class BlogUser(AbstractUser): # 获取用户详情页的绝对URL(用于模板中的{% url %}反向解析) ======= -class BlogUser(AbstractUser):#自定义用户模型BlogUser,继承自Django内置的AbstractUser(可扩展的用户抽象类)。 - nickname = models.CharField(_('nick name'), max_length=100, blank=True)#定义nickname字段,字符类型,支持国际化翻译,最大长度100,可为空。 - creation_time = models.DateTimeField(_('creation time'), default=now)#定义creation_time字段,日期时间类型,默认值为当前时间(now方法)。 - last_modify_time = models.DateTimeField(_('last modify time'), default=now)#定义last_modify_time字段,日期时间类型,默认值为当前时间。 - source = models.CharField(_('create source'), max_length=100, blank=True)#定义source字段,字符类型,记录用户创建来源,最大长度100,可为空。 +class BlogUser(AbstractUser):#lxy自定义用户模型BlogUser,继承自Django内置的AbstractUser(可扩展的用户抽象类)。 + nickname = models.CharField(_('nick name'), max_length=100, blank=True)#lxy定义nickname字段,字符类型,支持国际化翻译,最大长度100,可为空。 + creation_time = models.DateTimeField(_('creation time'), default=now)#lxy定义creation_time字段,日期时间类型,默认值为当前时间(now方法)。 + last_modify_time = models.DateTimeField(_('last modify time'), default=now)#lxy定义last_modify_time字段,日期时间类型,默认值为当前时间。 + source = models.CharField(_('create source'), max_length=100, blank=True)#lxy定义source字段,字符类型,记录用户创建来源,最大长度100,可为空。 >>>>>>> LXY_branch @@ -51,6 +51,7 @@ class BlogUser(AbstractUser):#自定义用户模型BlogUser,继承自Django内 return reverse( <<<<<<< HEAD 'blog:author_detail', kwargs={ +<<<<<<< HEAD <<<<<<< HEAD 'author_name': self.username}) # 定义对象的字符串表示(Admin后台和shell中显示) @@ -61,15 +62,22 @@ class BlogUser(AbstractUser):#自定义用户模型BlogUser,继承自Django内 ======= 'author_name': self.username})#定义获取用户详情页绝对URL的方法,通过reverse反向解析路由blog:author_detail,传递username参数。 >>>>>>> LXY_branch +======= + 'author_name': self.username})#lxy定义获取用户详情页绝对URL的方法,通过reverse反向解析路由blog:author_detail,传递username参数。 +>>>>>>> LXY_branch >>>>>>> JYN_branch def __str__(self): +<<<<<<< HEAD <<<<<<< HEAD """模型的字符串表示,这里返回用户的邮箱""" return self.email # 获取用户详情页的完整URL(包含域名,用于分享链接) ======= return self.email#定义对象的字符串表示方法,返回用户的email +======= + return self.email#lxy定义对象的字符串表示方法,返回用户的email +>>>>>>> LXY_branch >>>>>>> LXY_branch def get_full_url(self): @@ -78,6 +86,7 @@ class BlogUser(AbstractUser):#自定义用户模型BlogUser,继承自Django内 url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) <<<<<<< HEAD +<<<<<<< HEAD ======= """获取用户详情页的完整URL(包含域名)""" # 获取当前站点的域名 @@ -112,3 +121,12 @@ class BlogUser(AbstractUser):#自定义用户模型BlogUser,继承自Django内 verbose_name_plural = verbose_name#模型的复数显示名称与单数一致。 get_latest_by = 'id'#指定按id获取最新记录 >>>>>>> LXY_branch +======= + return url#lxy定义获取带域名的完整URL方法,结合当前站点域名和get_absolute_url生成完整链接 + + class Meta: + ordering = ['-id']#lxy查询结果按id倒序排列 + verbose_name = _('user')#lxy模型的单数显示名称(支持国际化) + verbose_name_plural = verbose_name#lxy模型的复数显示名称与单数一致。 + get_latest_by = 'id'#lxy指定按id获取最新记录 +>>>>>>> LXY_branch diff --git a/src/django-master/accounts/tests.py b/src/django-master/accounts/tests.py index 32e5973..47bffd0 100644 --- a/src/django-master/accounts/tests.py +++ b/src/django-master/accounts/tests.py @@ -117,12 +117,16 @@ class AccountTest(TestCase): # 测试访问文章管理页面 response = self.client.get(article.get_admin_url()) +<<<<<<< HEAD <<<<<<< HEAD self.assertEqual(response.status_code, 200) # 断言页面访问成功 >>>>>>> JYN_branch ======= self.assertEqual(response.status_code, 200)#测试管理员账号登录后台功能:创建超级用户,验证登录状态和后台页面访问状态 >>>>>>> LXY_branch +======= + self.assertEqual(response.status_code, 200)#lxy测试管理员账号登录后台功能:创建超级用户,验证登录状态和后台页面访问状态 +>>>>>>> LXY_branch # 测试用户注册功能 def test_validate_register(self): @@ -263,11 +267,15 @@ class AccountTest(TestCase): # 错误登录后访问管理页面 >>>>>>> JYN_branch response = self.client.get(article.get_admin_url()) +<<<<<<< HEAD <<<<<<< HEAD self.assertIn(response.status_code, [301, 302, 200]) # 测试邮箱验证码验证 ======= self.assertIn(response.status_code, [301, 302, 200])#测试用户注册流程:验证注册前后用户数量变化,邮箱验证链接的有效性,以及注册后用户权限、文章发布等功能 +======= + self.assertIn(response.status_code, [301, 302, 200])#lxy测试用户注册流程:验证注册前后用户数量变化,邮箱验证链接的有效性,以及注册后用户权限、文章发布等功能 +>>>>>>> LXY_branch >>>>>>> LXY_branch def test_verify_email_code(self): @@ -282,6 +290,7 @@ class AccountTest(TestCase): self.assertEqual(err, None) # 测试错误邮箱 err = utils.verify("admin@123.com", code) +<<<<<<< HEAD <<<<<<< HEAD self.assertEqual(type(err), str)# 应返回错误信息字符串 # 测试忘记密码发送验证码功能 - 成功情况 @@ -292,6 +301,9 @@ class AccountTest(TestCase): ======= self.assertEqual(type(err), str)#测试邮箱验证码功能:验证有效邮箱和无效邮箱的验证码校验结果 >>>>>>> LXY_branch +======= + self.assertEqual(type(err), str)#lxy测试邮箱验证码功能:验证有效邮箱和无效邮箱的验证码校验结果 +>>>>>>> LXY_branch # 验证正确的邮箱和验证码 err = utils.verify("admin@admin.com", code) @@ -311,6 +323,7 @@ class AccountTest(TestCase): ) self.assertEqual(resp.status_code, 200) +<<<<<<< HEAD <<<<<<< HEAD self.assertEqual(resp.content.decode("utf-8"), "ok")# 验证返回成功消息 # 测试忘记密码发送验证码功能 - 失败情况 @@ -325,6 +338,9 @@ class AccountTest(TestCase): ======= self.assertEqual(resp.content.decode("utf-8"), "ok")#测试忘记密码的邮箱验证码发送:分别验证成功和失败场景(如邮箱错误)的接口响应 >>>>>>> LXY_branch +======= + self.assertEqual(resp.content.decode("utf-8"), "ok")#lxy测试忘记密码的邮箱验证码发送:分别验证成功和失败场景(如邮箱错误)的接口响应 +>>>>>>> LXY_branch def test_forget_password_email_code_fail(self): """测试发送密码重置验证码失败的情况""" @@ -441,12 +457,16 @@ class AccountTest(TestCase): data=data ) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD self.assertEqual(resp.status_code, 200)# 应返回错误页面而非重定向 ======= self.assertEqual(resp.status_code, 200)#测试忘记密码流程:成功场景:验证密码修改后是否生效;失败场景:验证不存在用户、验证码错误时的接口响应 >>>>>>> LXY_branch +======= + self.assertEqual(resp.status_code, 200)#lxy测试忘记密码流程:成功场景:验证密码修改后是否生效;失败场景:验证不存在用户、验证码错误时的接口响应 +>>>>>>> LXY_branch ======= self.assertEqual(resp.status_code, 200) # 应返回页面但不重置密码 diff --git a/src/django-master/accounts/user_login_backend.py b/src/django-master/accounts/user_login_backend.py index a2d8f85..283c8d9 100644 --- a/src/django-master/accounts/user_login_backend.py +++ b/src/django-master/accounts/user_login_backend.py @@ -8,7 +8,7 @@ from django.contrib.auth.backends import ModelBackend # 导入Django内置的 >>>>>>> JYN_branch -class EmailOrUsernameModelBackend(ModelBackend):#自定义Django认证后端,支持用户名或邮箱两种方式登录。 +class EmailOrUsernameModelBackend(ModelBackend):#lxy自定义Django认证后端,支持用户名或邮箱两种方式登录。 """ <<<<<<< HEAD 允许使用用户名或邮箱登录 @@ -75,7 +75,7 @@ class EmailOrUsernameModelBackend(ModelBackend):#自定义Django认证后端, # 若查询不到用户(用户名/邮箱不存在),返回None(认证失败) >>>>>>> JYN_branch return None -#核心认证逻辑:判断输入是否为邮箱(含@),分别用邮箱或用户名查询用户,验证密码后返回用户对象;若用户不存在则返回None。 +#lxy核心认证逻辑:判断输入是否为邮箱(含@),分别用邮箱或用户名查询用户,验证密码后返回用户对象;若用户不存在则返回None。 def get_user(self, username): """ <<<<<<< HEAD @@ -94,6 +94,7 @@ class EmailOrUsernameModelBackend(ModelBackend):#自定义Django认证后端, # 用户不存在时返回 None return None <<<<<<< HEAD +<<<<<<< HEAD ======= 根据用户ID获取用户对象(Django认证系统必须实现的方法) 作用:认证成功后,系统通过此方法获取用户完整信息 @@ -110,3 +111,6 @@ class EmailOrUsernameModelBackend(ModelBackend):#自定义Django认证后端, ======= #根据用户ID(主键)查询用户,不存在则返回None,用于Django认证系统的用户查询环节 >>>>>>> LXY_branch +======= +#lxy根据用户ID(主键)查询用户,不存在则返回None,用于Django认证系统的用户查询环节 +>>>>>>> LXY_branch diff --git a/src/django-master/accounts/utils.py b/src/django-master/accounts/utils.py index 2e649f0..cc58cc5 100644 --- a/src/django-master/accounts/utils.py +++ b/src/django-master/accounts/utils.py @@ -16,6 +16,7 @@ from django.core.cache import cache # 导入Django缓存模块,用于存储 from django.utils.translation import gettext # 用于获取即时翻译文本 from django.utils.translation import gettext_lazy as _ # 用于延迟翻译文本(支持国际化) +<<<<<<< HEAD <<<<<<< HEAD from djangoblog.utils import send_email # 导入项目自定义的发送邮件工具函数 @@ -25,6 +26,9 @@ _code_ttl = timedelta(minutes=5) ======= _code_ttl = timedelta(minutes=5)#验证码有效期,设置为5分钟。 >>>>>>> LXY_branch +======= +_code_ttl = timedelta(minutes=5)#lxy验证码有效期,设置为5分钟。 +>>>>>>> LXY_branch def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): @@ -105,6 +109,7 @@ def set_code(email: str, code: str): cache.set(email, code, _code_ttl.seconds) +<<<<<<< HEAD <<<<<<< HEAD def get_code(email: str) -> typing.Optional[str]: """ @@ -122,6 +127,9 @@ def get_code(email: str) -> typing.Optional[str]: # 直接调用 Django 缓存的 get 方法 ======= def get_code(email: str) -> typing.Optional[str]:#从缓存中获取指定邮箱对应的验证码 +>>>>>>> LXY_branch +======= +def get_code(email: str) -> typing.Optional[str]:#lxy从缓存中获取指定邮箱对应的验证码 >>>>>>> LXY_branch """获取code""" return cache.get(email) diff --git a/src/django-master/accounts/views.py b/src/django-master/accounts/views.py index c6f0601..ef0e161 100644 --- a/src/django-master/accounts/views.py +++ b/src/django-master/accounts/views.py @@ -88,11 +88,15 @@ class RegisterView(FormView): <<<<<<< HEAD # 指定使用的表单类 form_class = RegisterForm +<<<<<<< HEAD <<<<<<< HEAD template_name = 'account/registration_form.html' # 使用装饰器确保视图禁用缓存(never_cache)并启用 CSRF 防护 ======= template_name = 'account/registration_form.html'#处理用户注册逻辑,指定表单类RegisterForm和模板account/registration_form.html。 +======= + template_name = 'account/registration_form.html'#lxy处理用户注册逻辑,指定表单类RegisterForm和模板account/registration_form.html。 +>>>>>>> LXY_branch >>>>>>> LXY_branch @method_decorator(csrf_protect) @@ -185,6 +189,7 @@ class RegisterView(FormView): # 表单无效时重新渲染表单(显示错误信息) return self.render_to_response({ 'form': form +<<<<<<< HEAD <<<<<<< HEAD }) ======= @@ -195,6 +200,9 @@ class RegisterView(FormView): ======= })#form_valid方法中,保存用户并设置为非活跃状态,生成邮箱验证链接并发送验证邮件,最后重定向到结果页。 >>>>>>> LXY_branch +======= + })#lxyform_valid方法中,保存用户并设置为非活跃状态,生成邮箱验证链接并发送验证邮件,最后重定向到结果页。 +>>>>>>> LXY_branch # 4. 跳转到注册结果页(提示用户查收验证邮件) url = reverse('accounts:result') + f'?type=register&id={str(user.id)}' @@ -207,12 +215,16 @@ class RegisterView(FormView): # 登出视图,继承自RedirectView,重定向到登录页面 class LogoutView(RedirectView): <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD # 登出后重定向的URL url = '/login/' # 使用never_cache装饰器确保视图不会被缓存 ======= url = '/login/'#处理用户登出,登出后重定向到/login/ +======= + url = '/login/'#lxy处理用户登出,登出后重定向到/login/ +>>>>>>> LXY_branch >>>>>>> LXY_branch @method_decorator(never_cache) @@ -237,6 +249,7 @@ class LogoutView(RedirectView): logout(request) # 删除侧边栏缓存 delete_sidebar_cache() +<<<<<<< HEAD <<<<<<< HEAD # 调用父类的get方法完成重定向 return super(LogoutView, self).get(request, *args, **kwargs) @@ -249,6 +262,9 @@ class LogoutView(RedirectView): ======= return super(LogoutView, self).get(request, *args, **kwargs)#get方法中调用logout登出用户,删除侧边栏缓存后完成重定向 >>>>>>> LXY_branch +======= + return super(LogoutView, self).get(request, *args, **kwargs)#lxyget方法中调用logout登出用户,删除侧边栏缓存后完成重定向 +>>>>>>> LXY_branch # 登录视图,继承自FormView class LoginView(FormView): @@ -261,6 +277,7 @@ class LoginView(FormView): success_url = '/' # 重定向字段名 redirect_field_name = REDIRECT_FIELD_NAME +<<<<<<< HEAD # 登录会话有效期(一个月的时间,单位:秒) login_ttl = 2626560 # 一个月的时间 # 使用多个装饰器装饰dispatch方法 @@ -279,6 +296,9 @@ class LoginView(FormView): success_url = '/' # 登录成功默认重定向地址(首页) redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名(默认next) login_ttl = 2626560 # “记住登录”状态的有效期(秒),约等于1个月 +======= + login_ttl = 2626560 #lxy 一个月的时间 +>>>>>>> LXY_branch # 为视图添加多重装饰器:敏感参数保护、CSRF保护、禁止缓存 @method_decorator(sensitive_post_parameters('password')) # 保护密码参数,避免日志泄露 @@ -305,9 +325,13 @@ class LoginView(FormView): kwargs['redirect_to'] = redirect_to # 调用父类方法获取其他上下文数据 +<<<<<<< HEAD <<<<<<< HEAD return super(LoginView, self).get_context_data(**kwargs) # 表单验证通过后的处理 +======= + return super(LoginView, self).get_context_data(**kwargs)#lxy处理用户登录逻辑,指定表单类LoginForm、模板account / login.html和成功后重定向地址 / +>>>>>>> LXY_branch def form_valid(self, form): # 重新创建认证表单(这里可能有逻辑问题,因为form已经传入) ======= @@ -351,16 +375,24 @@ class LoginView(FormView): # 调用父类form_valid,执行重定向 >>>>>>> JYN_branch return super(LoginView, self).form_valid(form) +<<<<<<< HEAD +======= + #lxyreturn HttpResponseRedirect('/') +>>>>>>> LXY_branch else: <<<<<<< HEAD # 表单无效,重新渲染表单并显示错误 return self.render_to_response({ 'form': form +<<<<<<< HEAD <<<<<<< HEAD }) # 获取成功后的跳转URL ======= })#form_valid方法中验证表单,登录用户并根据“记住我”选项设置会话过期时间 +======= + })#lxyform_valid方法中验证表单,登录用户并根据“记住我”选项设置会话过期时间 +>>>>>>> LXY_branch >>>>>>> LXY_branch def get_success_url(self): @@ -374,6 +406,7 @@ class LoginView(FormView): # 如果不安全则使用默认成功URL redirect_to = self.success_url <<<<<<< HEAD +<<<<<<< HEAD ======= # 表单验证失败(如密码错误),重新渲染表单并显示错误 return self.render_to_response({'form': form}) @@ -391,6 +424,9 @@ class LoginView(FormView): ======= return redirect_to#get_success_url方法处理登录后的重定向地址,确保其安全性 >>>>>>> LXY_branch +======= + return redirect_to#lxyget_success_url方法处理登录后的重定向地址,确保其安全性 +>>>>>>> LXY_branch # 账户操作结果页面(如注册成功、邮箱验证等) def account_result(request): @@ -416,12 +452,16 @@ def account_result(request): # 获取对应的用户,若不存在则返回404 user = get_object_or_404(get_user_model(), id=id) +<<<<<<< HEAD <<<<<<< HEAD logger.info(type) # 日志记录操作类型 # 若用户已激活,直接重定向到首页(避免重复验证) ======= logger.info(type)#处理注册和邮箱验证的结果逻辑,根据type参数区分场景: +>>>>>>> LXY_branch +======= + logger.info(type)#lxy处理注册和邮箱验证的结果逻辑,根据type参数区分场景: >>>>>>> LXY_branch if user.is_active: return HttpResponseRedirect('/') @@ -486,6 +526,7 @@ class ForgetPasswordView(FormView): <<<<<<< HEAD # 使用的表单类 form_class = ForgetPasswordForm +<<<<<<< HEAD <<<<<<< HEAD # 模板文件路径 template_name = 'account/forget_password.html' @@ -500,6 +541,9 @@ class ForgetPasswordView(FormView): ======= template_name = 'account/forget_password.html'#处理忘记密码逻辑,指定表单类ForgetPasswordForm和模板account/forget_password.html >>>>>>> LXY_branch +======= + template_name = 'account/forget_password.html'#lxy处理忘记密码逻辑,指定表单类ForgetPasswordForm和模板account/forget_password.html +>>>>>>> LXY_branch # 表单验证通过后的处理 @@ -524,6 +568,7 @@ class ForgetPasswordView(FormView): # 重定向到登录页面 return HttpResponseRedirect('/login/') else: +<<<<<<< HEAD <<<<<<< HEAD # 表单无效,重新渲染表单并显示错误 ======= @@ -540,9 +585,12 @@ class ForgetPasswordEmailCode(View): # 处理POST请求 ======= return self.render_to_response({'form': form})#form_valid方法中验证表单后,重置用户密码并重定向到登录页 +======= + return self.render_to_response({'form': form})#lxyform_valid方法中验证表单后,重置用户密码并重定向到登录页 +>>>>>>> LXY_branch -class ForgetPasswordEmailCode(View):# 处理忘记密码的邮箱验证码发送逻辑 +class ForgetPasswordEmailCode(View):#lxy处理忘记密码的邮箱验证码发送逻辑 >>>>>>> LXY_branch def post(self, request: HttpRequest): @@ -585,5 +633,9 @@ class ForgetPasswordEmailCode(View):# 处理忘记密码的邮箱验证码发送 >>>>>>> JYN_branch ======= +<<<<<<< HEAD return HttpResponse("ok")# post方法中验证邮箱表单,生成并发送验证码,将验证码存入缓存后返回成功标识 >>>>>>> LXY_branch +======= + return HttpResponse("ok")#lxypost方法中验证邮箱表单,生成并发送验证码,将验证码存入缓存后返回成功标识 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/admin.py b/src/django-master/blog/admin.py index c637bfc..9c65461 100644 --- a/src/django-master/blog/admin.py +++ b/src/django-master/blog/admin.py @@ -16,6 +16,7 @@ from .models import Article #ymq:从当前应用的models模块导入Article模型 +<<<<<<< HEAD class ArticleForm(forms.ModelForm): #ymq:定义Article模型对应的表单类,继承自ModelForm # body = forms.CharField(widget=AdminPagedownWidget()) @@ -31,28 +32,51 @@ class ArticleForm(forms.ModelForm): def makr_article_publish(modeladmin, request, queryset): #ymq:定义批量发布文章的动作函数 +======= +class ArticleForm(forms.ModelForm):#lxy 文章表单类 + # body = forms.CharField(widget=AdminPagedownWidget())#lxy 富文本组件 + + class Meta: + model = Article#lxy 关联Article模型 + fields = '__all__'#lxy 包含所有字段 + + +def makr_article_publish(modeladmin, request, queryset):#lxy 批量设为已发布 +>>>>>>> LXY_branch queryset.update(status='p') #ymq:将选中的文章状态更新为'p'(发布状态) +<<<<<<< HEAD def draft_article(modeladmin, request, queryset): #ymq:定义批量设为草稿的动作函数 +======= +def draft_article(modeladmin, request, queryset):#lxy 批量设为草稿 +>>>>>>> LXY_branch queryset.update(status='d') #ymq:将选中的文章状态更新为'd'(草稿状态) +<<<<<<< HEAD def close_article_commentstatus(modeladmin, request, queryset): #ymq:定义批量关闭评论的动作函数 +======= +def close_article_commentstatus(modeladmin, request, queryset):#lxy 关闭评论 +>>>>>>> LXY_branch queryset.update(comment_status='c') #ymq:将选中的文章评论状态更新为'c'(关闭状态) +<<<<<<< HEAD def open_article_commentstatus(modeladmin, request, queryset): #ymq:定义批量开启评论的动作函数 +======= +def open_article_commentstatus(modeladmin, request, queryset):#lxy 开启评论 +>>>>>>> LXY_branch queryset.update(comment_status='o') #ymq:将选中的文章评论状态更新为'o'(开启状态) - +#lxy 操作描述 makr_article_publish.short_description = _('Publish selected articles') #ymq:设置发布动作在admin中的显示名称(支持国际化) draft_article.short_description = _('Draft selected articles') @@ -63,6 +87,7 @@ open_article_commentstatus.short_description = _('Open article comments') #ymq:设置开启评论动作在admin中的显示名称(支持国际化) +<<<<<<< HEAD class ArticlelAdmin(admin.ModelAdmin): #ymq:定义Article模型的admin管理类,继承自ModelAdmin list_per_page = 20 @@ -72,6 +97,13 @@ class ArticlelAdmin(admin.ModelAdmin): form = ArticleForm #ymq:指定使用自定义的ArticleForm表单 list_display = ( +======= +class ArticlelAdmin(admin.ModelAdmin):#lxy 文章Admin配置 + list_per_page = 20#lxy 每页显示20条 + search_fields = ('body', 'title')#lxy 搜索字段 + form = ArticleForm#lxy 关联表单 + list_display = (#lxy 列表显示字段 +>>>>>>> LXY_branch 'id', 'title', 'author', @@ -81,6 +113,7 @@ class ArticlelAdmin(admin.ModelAdmin): 'status', 'type', 'article_order') +<<<<<<< HEAD #ymq:设置列表页显示的字段 list_display_links = ('id', 'title') #ymq:设置列表页中可点击跳转编辑页的字段 @@ -93,14 +126,26 @@ class ArticlelAdmin(admin.ModelAdmin): view_on_site = True #ymq:启用"在站点上查看"功能 actions = [ +======= + list_display_links = ('id', 'title') #lxy 排序字段 + list_filter = ('status', 'type', 'category') #lxy 可点击字段 + filter_horizontal = ('tags',)#lxy 标签选择器 + exclude = ('creation_time', 'last_modify_time')#lxy 隐藏字段 + view_on_site = True#lxy 允许查看站点 + actions = [#lxy 自定义操作 +>>>>>>> LXY_branch makr_article_publish, draft_article, close_article_commentstatus, open_article_commentstatus] #ymq:注册批量操作动作 +<<<<<<< HEAD def link_to_category(self, obj): #ymq:自定义列表页中分类字段的显示方式(转为链接) +======= + def link_to_category(self, obj):#lxy 分类链接 +>>>>>>> LXY_branch info = (obj.category._meta.app_label, obj.category._meta.model_name) #ymq:获取分类模型的应用标签和模型名称 link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) @@ -108,6 +153,7 @@ class ArticlelAdmin(admin.ModelAdmin): return format_html(u'%s' % (link, obj.category.name)) #ymq:返回HTML链接,点击可跳转到分类编辑页 +<<<<<<< HEAD link_to_category.short_description = _('category') #ymq:设置自定义字段在列表页的显示名称(支持国际化) @@ -115,19 +161,33 @@ class ArticlelAdmin(admin.ModelAdmin): #ymq:重写获取表单的方法,自定义表单字段 form = super(ArticlelAdmin, self).get_form(request, obj,** kwargs) #ymq:调用父类方法获取表单 +======= + link_to_category.short_description = _('category') #lxy 字段名称 + + def get_form(self, request, obj=None, **kwargs):#lxy 重写表单 + form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) +>>>>>>> LXY_branch form.base_fields['author'].queryset = get_user_model( ).objects.filter(is_superuser=True) #ymq:限制作者字段只能选择超级用户 return form #ymq:返回修改后的表单 +<<<<<<< HEAD def save_model(self, request, obj, form, change): #ymq:重写保存模型的方法(可在此添加自定义保存逻辑) +======= + def save_model(self, request, obj, form, change):#lxy 重写保存 +>>>>>>> LXY_branch super(ArticlelAdmin, self).save_model(request, obj, form, change) #ymq:调用父类的保存方法完成默认保存 +<<<<<<< HEAD def get_view_on_site_url(self, obj=None): #ymq:重写"在站点上查看"的URL生成方法 +======= + def get_view_on_site_url(self, obj=None):#lxy 查看站点URL +>>>>>>> LXY_branch if obj: #ymq:如果有具体对象,返回对象的完整URL url = obj.get_full_url() @@ -139,6 +199,7 @@ class ArticlelAdmin(admin.ModelAdmin): return site +<<<<<<< HEAD class TagAdmin(admin.ModelAdmin): #ymq:定义Tag模型的admin管理类 exclude = ('slug', 'last_mod_time', 'creation_time') @@ -169,5 +230,26 @@ class SideBarAdmin(admin.ModelAdmin): class BlogSettingsAdmin(admin.ModelAdmin): #ymq:定义BlogSettings模型的admin管理类 +======= +class TagAdmin(admin.ModelAdmin):#lxy 标签Admin配置 + exclude = ('slug', 'last_mod_time', 'creation_time')#lxy 隐藏字段 + + +class CategoryAdmin(admin.ModelAdmin):#lxy 分类Admin配置 + list_display = ('name', 'parent_category', 'index') #lxy 列表显示 + exclude = ('slug', 'last_mod_time', 'creation_time')#lxy 隐藏字段 + + +class LinksAdmin(admin.ModelAdmin): #lxy 链接Admin配置 + exclude = ('last_mod_time', 'creation_time') #lxy 隐藏字段 + + +class SideBarAdmin(admin.ModelAdmin): #lxy 侧边栏Admin配置 + list_display = ('name', 'content', 'is_enable', 'sequence')#lxy 列表显示 + exclude = ('last_mod_time', 'creation_time') #lxy 隐藏字段 + + +class BlogSettingsAdmin(admin.ModelAdmin):#lxy 博客设置Admin配置 +>>>>>>> LXY_branch pass #ymq:暂未设置特殊配置,使用默认admin行为 \ No newline at end of file diff --git a/src/django-master/blog/apps.py b/src/django-master/blog/apps.py index d7e57a1..ce813c5 100644 --- a/src/django-master/blog/apps.py +++ b/src/django-master/blog/apps.py @@ -1,7 +1,15 @@ +<<<<<<< HEAD from django.apps import AppConfig #ymq:导入Django的AppConfig类,用于定义应用的配置信息 class BlogConfig(AppConfig): #ymq:定义博客应用的配置类,继承自AppConfig name = 'blog' - #ymq:指定应用的名称为'blog',Django通过该名称识别此应用 \ No newline at end of file + #ymq:指定应用的名称为'blog',Django通过该名称识别此应用 +======= +from django.apps import AppConfig#lxy 导入Django应用配置类 + + +class BlogConfig(AppConfig):#lxy 博客应用的配置类 + name = 'blog'#lxy 应用名称(对应项目中的blog模块) +>>>>>>> LXY_branch diff --git a/src/django-master/blog/context_processors.py b/src/django-master/blog/context_processors.py index ebe7d09..21d681d 100644 --- a/src/django-master/blog/context_processors.py +++ b/src/django-master/blog/context_processors.py @@ -13,6 +13,7 @@ logger = logging.getLogger(__name__) #ymq:创建当前模块的日志记录器实例 +<<<<<<< HEAD def seo_processor(requests): #ymq:定义SEO上下文处理器,用于向模板全局注入通用数据 key = 'seo_processor' @@ -62,4 +63,39 @@ def seo_processor(requests): #ymq:将生成的上下文数据存入缓存,有效期10小时(60秒*60分*10小时) return value - #ymq:返回构建好的上下文数据字典 \ No newline at end of file + #ymq:返回构建好的上下文数据字典 +======= +def seo_processor(requests):#lxy SEO相关上下文处理器 + key = 'seo_processor'#lxy 缓存键名 + value = cache.get(key)#lxy 从缓存取数据 + if value: + return value#lxy 有缓存则直接返回 + else: + logger.info('set processor cache.')#lxy 记录缓存设置日志 + setting = get_blog_setting()#lxy 获取博客配置 + value = { + 'SITE_NAME': setting.site_name,#lxy 站点名称 + 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,#lxy 是否显示谷歌广告 + #lxy 谷歌广告代码 + 'SITE_SEO_DESCRIPTION': setting.site_seo_description,#lxy 站点SEO描述 + 'SITE_DESCRIPTION': setting.site_description,#lxy 站点描述 + 'SITE_KEYWORDS': setting.site_keywords,#lxy 站点关键词 + 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',#lxy 站点基础URL + 'ARTICLE_SUB_LENGTH': setting.article_sub_length,#lxy 文章摘要长度 + 'nav_category_list': Category.objects.all(),#lxy 导航分类列表 + 'nav_pages': Article.objects.filter(#lxy 导航页面 + type='p', + status='p'), + 'OPEN_SITE_COMMENT': setting.open_site_comment,#lxy 是否开启站点评论 + 'BEIAN_CODE': setting.beian_code,#lxy 备案号 + 'ANALYTICS_CODE': setting.analytics_code,#lxy 统计代码 + "BEIAN_CODE_GONGAN": setting.gongan_beiancode,#lxy 公安备案号 + "SHOW_GONGAN_CODE": setting.show_gongan_code, #lxy 是否显示公安备案 + "CURRENT_YEAR": timezone.now().year,#lxy 当前年份 + "GLOBAL_HEADER": setting.global_header,#lxy 全局头部内容 + "GLOBAL_FOOTER": setting.global_footer,#lxy 全局底部内容 + "COMMENT_NEED_REVIEW": setting.comment_need_review,#lxy 评论是否需要审核 + } + cache.set(key, value, 60 * 60 * 10)#lxy 设置缓存(有效期10小时) + return value#lxy 返回上下文数据 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/documents.py b/src/django-master/blog/documents.py index 38db391..65383a6 100644 --- a/src/django-master/blog/documents.py +++ b/src/django-master/blog/documents.py @@ -15,8 +15,9 @@ from blog.models import Article #ymq:判断是否启用Elasticsearch(检查settings中是否配置了ELASTICSEARCH_DSL) ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') - +#lxy 判断是否启用ES if ELASTICSEARCH_ENABLED: +<<<<<<< HEAD #ymq:如果启用Elasticsearch,创建连接 connections.create_connection( hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) @@ -25,13 +26,26 @@ if ELASTICSEARCH_ENABLED: es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) from elasticsearch.client import IngestClient #ymq:创建Ingest客户端,用于管理数据处理管道 +======= + connections.create_connection(#lxy 创建ES连接 + hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) + from elasticsearch import Elasticsearch#lxy 导入ES客户端 + + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])#lxy 初始化ES客户端 + from elasticsearch.client import IngestClient#lxy 导入Ingest客户端 +>>>>>>> LXY_branch - c = IngestClient(es) + c = IngestClient(es) #lxy 初始化Ingest客户端 try: +<<<<<<< HEAD #ymq:尝试获取名为'geoip'的管道,检查是否已存在 c.get_pipeline('geoip') except elasticsearch.exceptions.NotFoundError: #ymq:如果管道不存在,则创建它(用于解析IP地址的地理位置信息) +======= + c.get_pipeline('geoip')#lxy 检查geoip管道是否存在 + except elasticsearch.exceptions.NotFoundError:#lxy 创建geoip管道(解析IP地理信息) +>>>>>>> LXY_branch c.put_pipeline('geoip', body='''{ "description" : "Add geoip info", "processors" : [ @@ -44,6 +58,7 @@ if ELASTICSEARCH_ENABLED: }''') +<<<<<<< HEAD class GeoIp(InnerDoc): #ymq:定义地理位置信息的内部文档(嵌套结构) continent_name = Keyword() # 大陆名称(关键字类型,不分词) @@ -61,15 +76,52 @@ class UserAgentBrowser(InnerDoc): class UserAgentOS(UserAgentBrowser): #ymq:定义用户代理中操作系统信息的内部文档(继承浏览器结构) pass +======= +class GeoIp(InnerDoc): #lxy IP地理信息嵌套文档 + continent_name = Keyword() #lxy 大洲名称 + country_iso_code = Keyword() #lxy 国家ISO代码 + country_name = Keyword() #lxy 国家名称 + location = GeoPoint() #lxy 地理位置坐标 + + +class UserAgentBrowser(InnerDoc): #lxy 浏览器信息嵌套文档 + Family = Keyword() #lxy 浏览器类型 + Version = Keyword() #lxy 浏览器版本 +class UserAgentOS(UserAgentBrowser): #lxy 操作系统信息嵌套文档 + pass #lxy 继承浏览器文档结构 +>>>>>>> LXY_branch + +class UserAgentDevice(InnerDoc): #lxy 设备信息嵌套文档 + Family = Keyword() #lxy 设备类型 + Brand = Keyword() #lxy 设备品牌 + Model = Keyword() #lxy 设备型号 + +<<<<<<< HEAD class UserAgentDevice(InnerDoc): #ymq:定义用户代理中设备信息的内部文档 Family = Keyword() # 设备家族 Brand = Keyword() # 设备品牌 Model = Keyword() # 设备型号 - - +======= +class UserAgent(InnerDoc): #lxy 用户代理信息嵌套文档 + browser = Object(UserAgentBrowser, required=False) #lxy 浏览器信息 + os = Object(UserAgentOS, required=False) #lxy 操作系统信息 + device = Object(UserAgentDevice, required=False) #lxy 设备信息 + string = Text() #lxy 用户代理原始字符串 + is_bot = Boolean() #lxy 是否为爬虫 +>>>>>>> LXY_branch + + class ElapsedTimeDocument(Document): # lxy 耗时统计ES文档类 + url = Keyword() # lxy 请求URL + time_taken = Long() # lxy 耗时(毫秒) + log_datetime = Date() # lxy 日志时间 + ip = Keyword() # lxy 请求IP + geoip = Object(GeoIp, required=False) # lxy IP地理信息 + useragent = Object(UserAgent, required=False) # lxy 用户代理信息 + +<<<<<<< HEAD class UserAgent(InnerDoc): #ymq:定义用户代理完整信息的内部文档(嵌套结构) browser = Object(UserAgentBrowser, required=False) # 浏览器信息 @@ -94,8 +146,18 @@ class ElapsedTimeDocument(Document): settings = { "number_of_shards": 1, # 分片数量 "number_of_replicas": 0 # 副本数量 +======= + class Index: #lxy ES索引配置 + name = 'performance' #lxy 索引名称 + settings = { + "number_of_shards": 1, #lxy 分片数 + "number_of_replicas": 0 #lxy 副本数 +>>>>>>> LXY_branch } + class Meta: #lxy 文档元信息 + doc_type = 'ElapsedTime' #lxy 文档类型 +<<<<<<< HEAD class Meta: doc_type = 'ElapsedTime' # 文档类型(Elasticsearch 7.x后逐渐废弃) @@ -125,20 +187,40 @@ class ElaspedTimeDocumentManager: #ymq:构建用户代理信息对象 ua = UserAgent() +======= +class ElapsedTimeDocumentManager: #lxy 耗时文档管理类 + @staticmethod + def build_index(): #lxy 创建ES索引 + from elasticsearch import Elasticsearch + client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + res = client.indices.exists(index="performance") #lxy 检查索引是否存在 + if not res: + ElapsedTimeDocument.init() #lxy 初始化索引 + + @staticmethod + def delete_index(): #lxy 删除ES索引 + from elasticsearch import Elasticsearch + es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) + es.indices.delete(index='performance', ignore=[400, 404]) #lxy 忽略不存在错误 + + @staticmethod + def create(url, time_taken, log_datetime, useragent, ip): #lxy 创建耗时文档 + ElapsedTimeDocumentManager.build_index() + ua = UserAgent() #lxy 初始化用户代理对象 +>>>>>>> LXY_branch 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 +<<<<<<< HEAD #ymq:创建文档实例,使用时间戳作为唯一ID doc = ElapsedTimeDocument( @@ -147,11 +229,15 @@ class ElaspedTimeDocumentManager: round( time.time() * 1000)) # 毫秒级时间戳作为ID +======= + doc = ElapsedTimeDocument( #lxy 构造耗时文档 + meta={ + 'id': int(round(time.time() * 10000)) #lxy 生成唯一ID +>>>>>>> LXY_branch }, - url=url, - time_taken=time_taken, - log_datetime=log_datetime, + url=url, time_taken=time_taken, log_datetime=log_datetime, useragent=ua, ip=ip) +<<<<<<< HEAD #ymq:保存文档时应用geoip管道解析IP地址 doc.save(pipeline="geoip") @@ -173,10 +259,33 @@ class ArticleDocument(Document): }) #ymq:嵌套标签信息(数组) tags = Object(properties={ +======= + doc.save(pipeline="geoip") #lxy 保存文档(用geoip管道解析IP) + +class ArticleDocument(Document): #lxy 文章ES文档类 + body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') #lxy 文章内容(中文分词) + title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') #lxy 文章标题(中文分词) + author = Object(properties={ #lxy 作者信息 + 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), + 'id': Integer() + }) + category = Object(properties={ #lxy 分类信息 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), 'id': Integer() }) - + tags = Object(properties={ #lxy 标签信息 +>>>>>>> LXY_branch + 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), + 'id': Integer() + }) + pub_time = Date() #lxy 发布时间 + status = Text() #lxy 文章状态 + comment_status = Text() #lxy 评论状态 + type = Text() #lxy 文章类型 + views = Integer() #lxy 阅读量 + article_order = Integer() #lxy 文章排序 + +<<<<<<< HEAD pub_time = Date() # 发布时间 status = Text() # 状态(发布/草稿) comment_status = Text() # 评论状态(开启/关闭) @@ -186,11 +295,18 @@ class ArticleDocument(Document): class Index: name = 'blog' # 索引名称 +======= + class Index: #lxy 文章索引配置 + name = 'blog' #lxy 索引名称 +>>>>>>> LXY_branch settings = { - "number_of_shards": 1, - "number_of_replicas": 0 + "number_of_shards": 1, #lxy 分片数 + "number_of_replicas": 0 #lxy 副本数 } + class Meta: #lxy 文档元信息 + doc_type = 'Article' #lxy 文档类型 +<<<<<<< HEAD class Meta: doc_type = 'Article' # 文档类型 @@ -208,12 +324,27 @@ class ArticleDocumentManager(): def delete_index(self): #ymq:删除blog索引 +======= +class ArticleDocumentManager():#lxy 文章文档管理类 + + def __init__(self): #lxy 初始化方法 + self.create_index() + + def create_index(self):#lxy 创建文章索引 + ArticleDocument.init() + + def delete_index(self): #lxy 删除文章索引 +>>>>>>> LXY_branch from elasticsearch import Elasticsearch es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) es.indices.delete(index='blog', ignore=[400, 404]) +<<<<<<< HEAD def convert_to_doc(self, articles): #ymq:将Django模型对象转换为Elasticsearch文档对象 +======= + def convert_to_doc(self, articles):#lxy 文章对象转ES文档 +>>>>>>> LXY_branch return [ ArticleDocument( meta={'id': article.id}, # 使用文章ID作为文档ID @@ -237,6 +368,7 @@ class ArticleDocumentManager(): ) for article in articles ] +<<<<<<< HEAD def rebuild(self, articles=None): #ymq:重建索引(默认同步所有文章,可指定文章列表) ArticleDocument.init() @@ -248,4 +380,16 @@ class ArticleDocumentManager(): def update_docs(self, docs): #ymq:批量更新文档 for doc in docs: - doc.save() \ No newline at end of file + doc.save() +======= + def rebuild(self, articles=None):#lxy 重建文章索引 + ArticleDocument.init() + articles = articles if articles else Article.objects.all()#lxy 获取所有文章 + docs = self.convert_to_doc(articles) + for doc in docs: + doc.save()#lxy 保存到ES + + def update_docs(self, docs):#lxy 更新文章文档 + for doc in docs: + doc.save() #lxy 保存更新 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/forms.py b/src/django-master/blog/forms.py index e637b4f..0705104 100644 --- a/src/django-master/blog/forms.py +++ b/src/django-master/blog/forms.py @@ -10,6 +10,7 @@ from django import forms from haystack.forms import SearchForm #ymq:导入Haystack的SearchForm基类,扩展实现博客搜索表单 +<<<<<<< HEAD logger = logging.getLogger(__name__) #ymq:创建当前模块的日志记录器实例 @@ -33,4 +34,20 @@ class BlogSearchForm(SearchForm): logger.info(self.cleaned_data['querydata']) return datas - #ymq:返回最终的搜索结果集 \ No newline at end of file + #ymq:返回最终的搜索结果集 +======= +logger = logging.getLogger(__name__)#lxy 获取当前模块的日志记录器 + + +class BlogSearchForm(SearchForm): #lxy 博客搜索表单类 + querydata = forms.CharField(required=True)#lxy 搜索关键词字段(必填) + + def search(self):#lxy 搜索方法 + datas = super(BlogSearchForm, self).search()#lxy 调用父类搜索方法 + if not self.is_valid():#lxy 校验表单是否合法 + return self.no_query_found()#lxy 不合法则返回无结果 + + if self.cleaned_data['querydata']:#lxy 若有搜索关键词 + logger.info(self.cleaned_data['querydata'])#lxy 记录搜索关键词日志 + return datas#lxy 返回搜索结果 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/middleware.py b/src/django-master/blog/middleware.py index 2c2bf83..909825d 100644 --- a/src/django-master/blog/middleware.py +++ b/src/django-master/blog/middleware.py @@ -14,6 +14,7 @@ logger = logging.getLogger(__name__) #ymq:创建当前模块的日志记录器实例 +<<<<<<< HEAD class OnlineMiddleware(object): #ymq:定义在线中间件类,用于记录页面加载性能和访问信息 def __init__(self, get_response=None): @@ -52,11 +53,45 @@ class OnlineMiddleware(object): ip=ip) #ymq: 客户端IP #ymq:替换响应内容中的标记为实际加载时间(保留前5位字符) +======= +class OnlineMiddleware(object):#lxy 在线统计中间件 + def __init__(self, get_response=None):#lxy 初始化方法 + self.get_response = get_response + super().__init__() + + def __call__(self, request):#lxy 中间件核心方法(处理请求) + ''' page render time ''' + start_time = time.time() + response = self.get_response(request) + http_user_agent = request.META.get('HTTP_USER_AGENT', '')#lxy 获取用户代理 + ip, _ = get_client_ip(request)#lxy 获取客户端IP + user_agent = parse(http_user_agent) #lxy 解析用户代理 + + if not response.streaming: #lxy 非流式响应时执行 + try: + cast_time = time.time() - start_time#lxy 计算耗时 + if ELASTICSEARCH_ENABLED:#lxy 若启用ES + time_taken = round((cast_time) * 1000, 2)#lxy 耗时转毫秒 + url = request.path#lxy 请求路径 + from django.utils import timezone # 记录耗时到ES + ElaspedTimeDocumentManager.create( + url=url, + time_taken=time_taken, + log_datetime=timezone.now(), + useragent=user_agent, + ip=ip) # 替换页面中的加载时间标记 +>>>>>>> LXY_branch response.content = response.content.replace( b'', str.encode(str(cast_time)[:5])) except Exception as e: +<<<<<<< HEAD #ymq:捕获并记录处理过程中的异常 logger.error("Error OnlineMiddleware: %s" % e) - return response #ymq: 返回处理后的响应 \ No newline at end of file + return response #ymq: 返回处理后的响应 +======= + logger.error("Error OnlineMiddleware: %s" % e)#lxy 捕获异常并日志 + + return response#lxy 返回响应 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/models.py b/src/django-master/blog/models.py index d92d098..24a9195 100644 --- a/src/django-master/blog/models.py +++ b/src/django-master/blog/models.py @@ -15,9 +15,20 @@ from uuslug import slugify # 导入slug生成工具 from djangoblog.utils import cache_decorator, cache # 导入缓存相关工具 from djangoblog.utils import get_current_site # 导入获取当前站点信息的工具 +<<<<<<< HEAD logger = logging.getLogger(__name__) # 创建当前模块的日志记录器 - - +======= +logger = logging.getLogger(__name__) #lxy 初始化日志记录器 +>>>>>>> LXY_branch + +class LinkShowType(models.TextChoices): #lxy 链接展示类型枚举 + I = ('i', _('index')) #lxy 首页展示 + L = ('l', _('list')) #lxy 列表页展示 + P = ('p', _('post')) #lxy 文章页展示 + A = ('a', _('all')) #lxy 所有页面展示 + S = ('s', _('side')) #lxy 侧边栏展示 + +<<<<<<< HEAD class LinkShowType(models.TextChoices): #ymq:定义链接展示位置的枚举类 I = ('i', _('index')) # 首页展示 @@ -49,17 +60,36 @@ class BaseModel(models.Model): slug = getattr( self, 'title') if 'title' in self.__dict__ else getattr( self, 'name') +======= +class BaseModel(models.Model): #lxy 模型基类(公共字段) + id = models.AutoField(primary_key=True) #lxy 主键ID + creation_time = models.DateTimeField(_('creation time'), default=now) #lxy 创建时间 + last_modify_time = models.DateTimeField(_('modify time'), default=now) #lxy 修改时间 + + def save(self, *args, **kwargs): #lxy 重写保存方法 + # 判断是否是更新文章阅读量 + is_update_views = isinstance(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) #lxy 单独更新阅读量 + else: + if 'slug' in self.__dict__: #lxy 若有slug字段,自动生成 + slug = getattr(self, 'title') if 'title' in self.__dict__ else getattr(self, 'name') +>>>>>>> LXY_branch setattr(self, 'slug', slugify(slug)) - super().save(*args, **kwargs) + super().save(*args, **kwargs) #lxy 调用父类保存 +<<<<<<< HEAD def get_full_url(self): #ymq:生成包含域名的完整URL +======= + def get_full_url(self): #lxy 获取完整URL +>>>>>>> LXY_branch site = get_current_site().domain - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) + url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: +<<<<<<< HEAD abstract = True # 声明为抽象模型,不生成数据库表 @abstractmethod @@ -71,20 +101,39 @@ class BaseModel(models.Model): class Article(BaseModel): """文章模型""" # 状态选项:草稿/已发布 +======= + abstract = True #lxy 抽象基类(不生成表) + + @abstractmethod + def get_absolute_url(self): #lxy 抽象方法:获取对象URL + pass + +class Article(BaseModel): #lxy 文章模型 + # 文章状态枚举 +>>>>>>> LXY_branch STATUS_CHOICES = ( - ('d', _('Draft')), - ('p', _('Published')), + ('d', _('Draft')), #lxy 草稿 + ('p', _('Published')), #lxy 已发布 ) +<<<<<<< HEAD # 评论状态选项:开启/关闭 +======= + # 评论状态枚举 +>>>>>>> LXY_branch COMMENT_STATUS = ( - ('o', _('Open')), - ('c', _('Close')), + ('o', _('open')), #lxy 开放评论 + ('c', _('close')), #lxy 关闭评论 ) +<<<<<<< HEAD # 类型选项:文章/页面 +======= + # 文章类型枚举 +>>>>>>> LXY_branch TYPE = ( - ('a', _('Article')), - ('p', _('Page')), + ('a', _('Article')), #lxy 文章 + ('p', _('Page')), #lxy 页面 ) +<<<<<<< HEAD title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题 body = MDTextField(_('body')) # 文章内容(使用markdown编辑器) @@ -129,26 +178,64 @@ class Article(BaseModel): class Meta: ordering = ['-article_order', '-pub_time'] # 默认排序:先按排序号降序,再按发布时间降序 +======= + title = models.CharField(_('title'), max_length=200, unique=True) #lxy 标题 + body = MDTextField(_('body')) #lxy 正文(Markdown) + pub_time = models.DateTimeField(_('publish time'), blank=False, null=False, default=now) #lxy 发布时间 + status = models.CharField(_('status'), max_length=1, choices=STATUS_CHOICES, default='p') #lxy 文章状态 + comment_status = models.CharField(_('comment status'), max_length=1, choices=COMMENT_STATUS, default='o') #lxy 评论状态 + type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') #lxy 文章类型 + views = models.PositiveIntegerField(_('views'), default=0) #lxy 阅读量 + author = models.ForeignKey( #lxy 关联作者 + 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) #lxy 排序序号 + show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) #lxy 是否显示目录 + category = models.ForeignKey( #lxy 关联分类 + 'Category', verbose_name=_('category'), on_delete=models.CASCADE, blank=False, null=False + ) + tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) #lxy 关联标签(多对多) + + def body_to_string(self): #lxy 正文转字符串 + return self.body + + def __str__(self): #lxy 实例字符串表示 + return self.title + + class Meta: + ordering = ['-article_order', '-pub_time'] #lxy 默认排序(倒序) +>>>>>>> LXY_branch verbose_name = _('article') verbose_name_plural = verbose_name get_latest_by = 'id' # 按id获取最新记录 +<<<<<<< HEAD def get_absolute_url(self): #ymq:生成文章详情页的URL return reverse('blog:detailbyid', kwargs={ +======= + def get_absolute_url(self): #lxy 文章详情页URL + return reverse('blog:detail', kwargs={ +>>>>>>> LXY_branch 'article_id': self.id, 'year': self.creation_time.year, 'month': self.creation_time.month, 'day': self.creation_time.day }) +<<<<<<< HEAD @cache_decorator(60 * 60 * 10) # 缓存10小时 def get_category_tree(self): #ymq:获取当前文章所属分类的层级结构(含父级分类) +======= + @cache_decorator(60 * 60 * 10) #lxy 缓存10小时 + def get_category_tree(self): #lxy 获取分类层级 +>>>>>>> LXY_branch tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) return names +<<<<<<< HEAD def save(self, *args, **kwargs): #ymq:重写保存方法(可扩展自定义逻辑) super().save(*args, **kwargs) @@ -160,12 +247,23 @@ class Article(BaseModel): def comment_list(self): #ymq:获取文章的评论列表(带缓存) +======= + def save(self, *args, **kwargs): #lxy 重写保存 + super().save(*args, **kwargs) + + def viewed(self): #lxy 阅读量+1 + self.views += 1 + self.save(update_fields=['views']) #lxy 仅更新阅读量 + + def comment_list(self): #lxy 获取文章评论 +>>>>>>> LXY_branch 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: +<<<<<<< HEAD comments = self.comment_set.filter(is_enable=True).order_by('-id') cache.set(cache_key, comments, 60 * 100) # 缓存100分钟 logger.info('set article comments:{id}'.format(id=self.id)) @@ -190,11 +288,40 @@ class Article(BaseModel): def get_first_image_url(self): """从文章内容中提取第一张图片的URL""" match = re.search(r'!\[.*?\]\((.+?)\)', self.body) # 匹配markdown图片语法 +======= + comments = self.comment_set.filter(is_enable=True).order_by('-id') #lxy 过滤启用的评论 + cache.set(cache_key, comments, 60 * 100) #lxy 缓存评论 + logger.info('set article comments:{id}'.format(id=self.id)) + return comments + + def get_admin_url(self): #lxy 后台管理URL + info = (self._meta.app_label, self._meta.model_name) + return reverse('admin:%s_%s_change' % info, args=(self.pk,)) + + @cache_decorator(expiration=60 * 100) #lxy 缓存 + def next_article(self): #lxy 获取下一篇文章 + return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first() + + @cache_decorator(expiration=60 * 100) #lxy 缓存 + def prev_article(self): #lxy 获取上一篇文章 + return Article.objects.filter(id__lt=self.id, status='p').first() + + def get_first_image_url(self): #lxy 获取正文第一张图URL + match = re.search(pattern=r'!\[.*?]\((.*?)\)', self.body) +>>>>>>> LXY_branch if match: return match.group(1) return "" + class Category(BaseModel): # lxy 分类模型 + name = models.CharField(_('category name'), max_length=30, unique=True) # lxy 分类名称 + parent_category = models.ForeignKey( # lxy 父分类(自关联) + 'self', verbose_name=_('parent category'), blank=True, null=True, on_delete=models.CASCADE + ) + slug = models.SlugField(default='no-slug', max_length=60, blank=True) # lxy 别名 + index = models.IntegerField(default=0, verbose_name=_('index')) # lxy 排序索引 +<<<<<<< HEAD class Category(BaseModel): """文章分类模型""" name = models.CharField(_('category name'), max_length=30, unique=True) # 分类名称 @@ -319,9 +446,127 @@ class SideBar(models.Model): def __str__(self): #ymq:模型实例的字符串表示(侧边栏标题) +======= + class Meta: + ordering = ['-index'] # lxy 按索引倒序 + verbose_name = _('category') + verbose_name_plural = verbose_name + + def get_absolute_url(self): # lxy 分类详情页URL + return reverse('blog:category_detail', kwargs={'category_name': self.slug}) + + def __str__(self): # lxy 实例字符串表示 + return self.name + + def get_category_tree(self): # lxy 获取分类层级链 + categories = [] + + def parse(category): + categories.append(category) + if category.parent_category: + parse(category.parent_category) + + parse(self) + return categories + + @cache_decorator(60 * 60 * 10) # lxy 缓存 + def get_sub_categories(self): # lxy 获取所有子分类 + categories = [] + all_categories = Category.objects.all() + + def parse(category): + if category not in categories: + categories.append(category) + children = all_categories.filter(parent_category=category) + for child in children: + if category not in categories: + categories.append(child) + parse(child) + + parse(self) + return categories + +class Tag(BaseModel): #lxy 标签模型 + name = models.CharField(_('tag name'), max_length=30, unique=True) #lxy 标签名称 + slug = models.SlugField(default='no-slug', max_length=60, blank=True) #lxy 别名 + + def __str__(self): #lxy 实例字符串表示 return self.name + def get_absolute_url(self): #lxy 标签详情页URL + return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) + + @cache_decorator(60 * 60 * 10) #lxy 缓存 + def get_article_count(self): #lxy 获取标签关联文章数 + return Article.objects.filter(tags__name=self.name).distinct().count() + class Meta: + ordering = ['name'] #lxy 按名称排序 + verbose_name = _('tag') + verbose_name_plural = verbose_name + +class Links(models.Model): #lxy 友链模型 + name = models.CharField(_('link name'), max_length=30, unique=True) #lxy 友链名称 + link = models.URLField(_('link')) #lxy 友链地址 + sequence = models.IntegerField(_('order'), unique=True) #lxy 排序序号 + is_enable = models.BooleanField(_('is show'), default=True, blank=False, null=False) #lxy 是否启用 + show_type = models.CharField( #lxy 展示类型 + _('show type'), max_length=1, choices=LinkShowType.choices, default=LinkShowType.I + ) + creation_time = models.DateTimeField(_('creation time'), default=now) #lxy 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) #lxy 修改时间 + + class Meta: + ordering = ['sequence'] #lxy 按序号排序 + verbose_name = _('link') + verbose_name_plural = verbose_name + + def __str__(self): #lxy 实例字符串表示 + return self.name + +class SideBar(models.Model): #lxy 侧边栏模型 + name = models.CharField(_('title'), max_length=100) #lxy 侧边栏标题 + content = models.TextField(_('content')) #lxy 侧边栏内容(HTML) + sequence = models.IntegerField(_('order'), unique=True) #lxy 排序序号 + is_enable = models.BooleanField(_('is enable'), default=True) #lxy 是否启用 + creation_time = models.DateTimeField(_('creation time'), default=now) #lxy 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) #lxy 修改时间 + + class Meta: + ordering = ['sequence'] #lxy 按序号排序 + verbose_name = _('sidebar') + verbose_name_plural = verbose_name + + def __str__(self): #lxy 实例字符串表示 +>>>>>>> LXY_branch + return self.name + + class BlogSettings(models.Model): # lxy 博客配置模型 + site_name = models.CharField(_('site name'), blank=False, default='') # lxy 站点名称 + site_description = models.TextField( # lxy 站点描述 + _('site description'), max_length=1000, null=False, blank=False, default='' + ) + site_seo_description = models.TextField( # lxy 站点SEO描述 + _('site seo description'), max_length=1000, null=False, blank=False, default='' + ) + site_keywords = models.TextField( # lxy 站点关键词 + _('site keywords'), max_length=1000, null=False, blank=False, default='' + ) + article_sub_length = models.IntegerField(_('article sub length'), default=300) # lxy 文章摘要长度 + sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) # lxy 侧边栏文章数 + sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) # lxy 侧边栏评论数 + show_google_adsense = models.BooleanField(_('show adsense'), default=False) # lxy 是否显示谷歌广告 + google_adsense_codes = models.TextField( # lxy 谷歌广告代码 + _('adsense code'), max_length=2000, null=True, blank=True, default='' + ) + open_site_comment = models.BooleanField(_('open site comment'), default=True) # lxy 是否开放站点评论 + global_header = models.TextField("公共头部", null=True, blank=True, default='') # lxy 公共头部HTML + global_footer = models.TextField("公共底部", null=True, blank=True, default='') # lxy 公共底部HTML + beian_code = models.CharField( # lxy 备案号 + '备案号', max_length=2000, null=True, blank=True, default='' + ) + +<<<<<<< HEAD class BlogSettings(models.Model): """博客全局配置模型""" site_name = models.CharField( @@ -361,10 +606,14 @@ class BlogSettings(models.Model): blank=True, default='') # 网站备案号 analytics_code = models.TextField( +======= + analytics_code = models.TextField( # lxy 网站统计代码 +>>>>>>> LXY_branch "网站统计代码", max_length=1000, null=False, blank=False, +<<<<<<< HEAD default='') # 统计分析代码 show_gongan_code = models.BooleanField( '是否显示公安备案号', default=False, null=False) # 是否显示公安备案号 @@ -376,22 +625,53 @@ class BlogSettings(models.Model): default='') # 公安备案号 comment_need_review = models.BooleanField( '评论是否需要审核', default=False, null=False) # 评论是否需要审核 +======= + default="" + ) + show_gongan_code = models.BooleanField( # lxy 是否显示公安备案号 + "是否显示公安备案号", default=False, null=False + ) + gongan_beiancode = models.TextField( # lxy 公安备案号 + "公安备案号", + max_length=2000, + null=True, + blank=True, + default="" + ) + comment_need_review = models.BooleanField( # lxy 评论是否需要审核 + "评论是否需要审核", default=False, null=False + ) +>>>>>>> LXY_branch - class Meta: - verbose_name = _('Website configuration') - verbose_name_plural = verbose_name + class Meta: # lxy 模型元信息 + verbose_name = _('Website configuration') # lxy 单数名称 + verbose_name_plural = verbose_name # lxy 复数名称 +<<<<<<< HEAD def __str__(self): #ymq:模型实例的字符串表示(网站名称) return self.site_name def clean(self): #ymq:数据验证,确保全局配置只能有一条记录 +======= + def __str__(self): # lxy 实例字符串表示 + return self.site_name + + def clean(self): # lxy 数据校验(确保仅存在一个配置) +>>>>>>> LXY_branch if BlogSettings.objects.exclude(id=self.id).count(): - raise ValidationError(_('There can only be one configuration')) + raise ValidationError(_('There can only be one configuration')) # lxy 抛出唯一配置异常 +<<<<<<< HEAD def save(self, *args, **kwargs): #ymq:保存配置后清除缓存,确保配置立即生效 super().save(*args, **kwargs) from djangoblog.utils import cache - cache.clear() \ No newline at end of file + cache.clear() +======= + def save(self, *args, **kwargs): # lxy 重写保存方法 + super().save(*args, **kwargs) # lxy 调用父类保存 + from djangoblog.utils import cache + cache.clear() # lxy 保存后清空缓存 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/search_indexes.py b/src/django-master/blog/search_indexes.py index f492392..63215cf 100644 --- a/src/django-master/blog/search_indexes.py +++ b/src/django-master/blog/search_indexes.py @@ -5,6 +5,7 @@ from blog.models import Article #ymq:从blog应用导入Article模型,为其创建搜索索引 +<<<<<<< HEAD class ArticleIndex(indexes.SearchIndex, indexes.Indexable): #ymq:定义文章搜索索引类,继承自SearchIndex和Indexable #ymq: document=True表示该字段是主要搜索字段,use_template=True表示使用模板定义字段内容 @@ -17,4 +18,16 @@ class ArticleIndex(indexes.SearchIndex, indexes.Indexable): def index_queryset(self, using=None): #ymq:定义需要被索引的数据集 #ymq: 只索引状态为'p'(已发布)的文章 - return self.get_model().objects.filter(status='p') \ No newline at end of file + return self.get_model().objects.filter(status='p') +======= +class ArticleIndex(indexes.SearchIndex, indexes.Indexable):#lxy 文章搜索索引类 + text = indexes.CharField(document=True, use_template=True)#lxy 搜索字段(关联模板) + + def get_model(self):#lxy 指定关联的模型 + return Article#lxy 关联Article模型 + + + def index_queryset(self, using=None):#lxy 指定要索引的数据集 + # 仅索引状态为“已发布(p)”的文章 + return self.get_model().objects.filter(status='p')#lxy 过滤已发布文章 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/tests.py b/src/django-master/blog/tests.py index fd67d7d..adc8e05 100644 --- a/src/django-master/blog/tests.py +++ b/src/django-master/blog/tests.py @@ -32,6 +32,7 @@ from oauth.models import OAuthUser, OAuthConfig # Create your tests here. +<<<<<<< HEAD class ArticleTest(TestCase): #ymq:定义文章相关的测试类,继承自TestCase def setUp(self): @@ -60,14 +61,44 @@ class ArticleTest(TestCase): response = self.client.get('admin/admin/logentry/') # 创建侧边栏测试数据 +======= + +class ArticleTest(TestCase): # lxy 文章相关测试类 + def setUp(self): # lxy 测试初始化 + self.client = Client() # lxy 测试客户端 + self.factory = RequestFactory() # lxy 请求工厂 + + def test_validate_article(self): # lxy 文章功能验证 + site = get_current_site().domain # lxy 获取站点域名 + # 创建测试用户 + user = BlogUser.objects.get_or_create(email="liangliangyy@gmail.com", username="liangliangyy")[0] + user.set_password("liangliangyy") + user.is_staff = True + user.is_superuser = True + user.save() + # 测试用户页面访问 + response = self.client.get(user.get_absolute_url()) + self.assertEqual(response.status_code, 200) # lxy 断言状态码200 + # 访问后台页面(无权限,仅请求) + self.client.get('/admin/servermanager/emailsendlog/') + self.client.get('admin/admin/logentry/') + + # 创建测试侧边栏 +>>>>>>> LXY_branch s = SideBar() s.sequence = 1 s.name = 'test' s.content = 'test content' s.is_enable = True s.save() +<<<<<<< HEAD # 创建分类测试数据 +======= + + # 创建测试分类 + +>>>>>>> LXY_branch category = Category() category.name = "category" category.creation_time = timezone.now() @@ -242,6 +273,7 @@ class ArticleTest(TestCase): save_user_avatar( 'https://www.python.org/static/img/python-logo.png') # 测试保存头像 +<<<<<<< HEAD def test_errorpage(self): #ymq:测试错误页面(404) rsp = self.client.get('/eee') # 访问不存在的URL @@ -250,6 +282,13 @@ class ArticleTest(TestCase): def test_commands(self): #ymq:测试Django管理命令 # 创建测试用户 +======= + def test_errorpage(self): # lxy 错误页面测试 + rsp = self.client.get('/eee') # lxy 访问不存在的路径 + self.assertEqual(rsp.status_code, 404) # lxy 断言404状态码 + + def test_commands(self):#lxy 命令行指令测试 +>>>>>>> LXY_branch user = BlogUser.objects.get_or_create( email="liangliangyy@gmail.com", username="liangliangyy")[0] @@ -290,6 +329,7 @@ class ArticleTest(TestCase): # 测试Elasticsearch索引构建命令(如果启用) from blog.documents import ELASTICSEARCH_ENABLED +<<<<<<< HEAD if ELASTICSEARCH_ENABLED: call_command("build_index") @@ -298,4 +338,14 @@ class ArticleTest(TestCase): call_command("create_testdata") # 创建测试数据 call_command("clear_cache") # 清理缓存 call_command("sync_user_avatar") # 同步用户头像 - call_command("build_search_words") # 构建搜索词 \ No newline at end of file + call_command("build_search_words") # 构建搜索词 +======= + + if ELASTICSEARCH_ENABLED: + call_command("build_index") # lxy 构建ES索引 + call_command("ping_baidu", "all") # lxy ping百度 + call_command("create_testdata") # lxy 创建测试数据 + call_command("clear_cache") # lxy 清理缓存 + call_command("sync_user_avatar") # lxy 同步用户头像 + call_command("build_search_words") # lxy 构建搜索词 +>>>>>>> LXY_branch diff --git a/src/django-master/blog/urls.py b/src/django-master/blog/urls.py index 966e0d4..b5d70e4 100644 --- a/src/django-master/blog/urls.py +++ b/src/django-master/blog/urls.py @@ -6,13 +6,18 @@ from django.views.decorators.cache import cache_page from . import views #ymq:从当前应用导入views模块,引用视图函数/类 +<<<<<<< HEAD app_name = "blog" #ymq:定义应用命名空间,避免URL名称冲突 +======= +app_name = "blog" #lxy 应用命名空间 +>>>>>>> LXY_branch urlpatterns = [ path( r'', views.IndexView.as_view(), +<<<<<<< HEAD name='index'), #ymq:首页URL,映射到IndexView视图类,名称为'index' @@ -64,11 +69,51 @@ urlpatterns = [ name='tag_detail_page'), #ymq:标签分页详情页URL,接收标签名称和页码参数,名称为'tag_detail_page' +======= + name='index'),#lxy 路由名称:首页 + path( + r'page//', + views.IndexView.as_view(), + name='index_page'),#lxy 路由名称:首页分页 + # 文章详情路由(按日期+ID) + path( + r'article////.html', + views.ArticleDetailView.as_view(), + name='detailbyid'),#lxy 路由名称:文章详情 + # 分类详情路由 + path( + r'category/.html', + views.CategoryDetailView.as_view(), + name='category_detail'), #lxy 路由名称:分类详情 + # 分类详情分页路由 + path( + r'category//.html', + views.CategoryDetailView.as_view(), + name='category_detail_page'),#lxy 路由名称:分类详情分页 + # 作者详情路由 + path( + r'author/.html', + views.AuthorDetailView.as_view(), + name='author_detail'),#lxy 路由名称:作者详情 + path( + r'author//.html', + views.AuthorDetailView.as_view(), + name='author_detail_page'),#lxy 路由名称:作者详情分页 + path( + r'tag/.html', + views.TagDetailView.as_view(), + name='tag_detail'),#lxy 路由名称:标签详情 + path( + r'tag//.html', + views.TagDetailView.as_view(), + name='tag_detail_page'),#lxy 路由名称:标签详情分页 +>>>>>>> LXY_branch path( 'archives.html', cache_page( 60 * 60)( views.ArchivesView.as_view()), +<<<<<<< HEAD name='archives'), #ymq:归档页面URL,使用cache_page装饰器缓存1小时(60*60秒),名称为'archives' @@ -89,4 +134,20 @@ urlpatterns = [ views.clean_cache_view, name='clean'), #ymq:清理缓存URL,映射到clean_cache_view视图函数,名称为'clean' -] \ No newline at end of file +] +======= + name='archives'),#lxy 路由名称:归档页 + path( + 'links.html', + views.LinkListView.as_view(), + name='links'),#lxy 路由名称:友链页 + path( + r'upload', + views.fileupload, + name='upload'),#lxy 路由名称:文件上传 + path( + r'clean', + views.clean_cache_view, + name='clean'),#lxy 路由名称:缓存清理 +] +>>>>>>> LXY_branch diff --git a/src/django-master/blog/views.py b/src/django-master/blog/views.py index 01f69af..a465094 100644 --- a/src/django-master/blog/views.py +++ b/src/django-master/blog/views.py @@ -31,6 +31,7 @@ logger = logging.getLogger(__name__) #ymq:创建当前模块的日志记录器实例 +<<<<<<< HEAD class ArticleListView(ListView): #ymq:文章列表基础视图类,继承自Django的ListView # template_name属性用于指定使用哪个模板进行渲染 @@ -52,24 +53,39 @@ class ArticleListView(ListView): @property def page_number(self): #ymq:获取当前页码(从URL参数或默认值) +======= +class ArticleListView(ListView): #lxy 文章列表视图基类 + template_name = 'blog/article_index.html' #lxy 渲染模板 + context_object_name = 'article_list' #lxy 模板中数据变量名 + page_type = '' #lxy 页面类型标识 + paginate_by = settings.PAGINATE_BY #lxy 每页数量 + page_kwarg = 'page' #lxy 分页参数名 + link_type = LinkShowType.L #lxy 友链展示类型 + + def get_view_cache_key(self): #lxy 获取视图缓存键 + return self.request.get['pages'] + + @property + def page_number(self):#lxy 获取当前页码 +>>>>>>> LXY_branch page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 return page - def get_queryset_cache_key(self): + def get_queryset_cache_key(self):#lxy 子类需重写:获取查询集缓存键 """ 子类重写.获得queryset的缓存key """ raise NotImplementedError() # 强制子类实现该方法 - def get_queryset_data(self): + def get_queryset_data(self):#lxy 子类需重写:获取查询集数据 """ 子类重写.获取queryset的数据 """ raise NotImplementedError() # 强制子类实现该方法 - def get_queryset_from_cache(self, cache_key): + def get_queryset_from_cache(self, cache_key):#lxy 从缓存取查询集 ''' 缓存页面数据 :param cache_key: 缓存key @@ -80,12 +96,17 @@ class ArticleListView(ListView): logger.info('get view cache.key:{key}'.format(key=cache_key)) return value else: +<<<<<<< HEAD article_list = self.get_queryset_data() # 调用子类实现的方法获取数据 cache.set(cache_key, article_list) # 存入缓存 +======= + article_list = self.get_queryset_data()#lxy 从数据库取数据 + cache.set(cache_key, article_list)#lxy 写入缓存 +>>>>>>> LXY_branch logger.info('set view cache.key:{key}'.format(key=cache_key)) return article_list - def get_queryset(self): + def get_queryset(self):#lxy 获取查询集(优先缓存) ''' 重写默认,从缓存获取数据 :return: @@ -94,21 +115,32 @@ class ArticleListView(ListView): value = self.get_queryset_from_cache(key) # 从缓存获取数据 return value +<<<<<<< HEAD def get_context_data(self, **kwargs): #ymq:扩展上下文数据,添加链接类型 +======= + def get_context_data(self, **kwargs):#lxy 补充上下文数据 +>>>>>>> LXY_branch kwargs['linktype'] = self.link_type return super(ArticleListView, self).get_context_data(** kwargs) -class IndexView(ArticleListView): +class IndexView(ArticleListView):#lxy 首页视图 ''' 首页视图 ''' +<<<<<<< HEAD # 友情链接类型:首页展示 link_type = LinkShowType.I def get_queryset_data(self): #ymq:获取首页文章列表(已发布的文章) +======= + # 友情链接类型 + link_type = LinkShowType.I#lxy 首页友链展示类型 + + def get_queryset_data(self): #lxy 获取首页文章(已发布) +>>>>>>> LXY_branch article_list = Article.objects.filter(type='a', status='p') return article_list @@ -167,9 +199,14 @@ class ArticleDetailView(DetailView): kwargs['comment_count'] = len( article_comments) if article_comments else 0 +<<<<<<< HEAD # 上一篇/下一篇文章 kwargs['next_article'] = self.object.next_article kwargs['prev_article'] = self.object.prev_article +======= + kwargs['next_article'] = self.object.next_article # lxy 下一篇文章 + kwargs['prev_article'] = self.object.prev_article # lxy 上一篇文章 +>>>>>>> LXY_branch # 调用父类方法获取基础上下文 context = super(ArticleDetailView, self).get_context_data(**kwargs) @@ -184,16 +221,24 @@ class ArticleDetailView(DetailView): return context -class CategoryDetailView(ArticleListView): +class CategoryDetailView(ArticleListView):#lxy 分类详情视图 ''' 分类目录列表视图 ''' +<<<<<<< HEAD page_type = "分类目录归档" # 页面类型标识 def get_queryset_data(self): #ymq:获取指定分类下的文章列表 slug = self.kwargs['category_name'] # 从URL获取分类别名 category = get_object_or_404(Category, slug=slug) # 获取分类对象 +======= + page_type = "分类目录归档"#lxy 页面类型 + + def get_queryset_data(self):#lxy 获取分类下文章 + slug = self.kwargs['category_name'] + category = get_object_or_404(Category, slug=slug) +>>>>>>> LXY_branch categoryname = category.name self.categoryname = categoryname @@ -273,8 +318,12 @@ class TagDetailView(ArticleListView): tags__name=tag_name, type='a', status='p') return article_list +<<<<<<< HEAD def get_queryset_cache_key(self): #ymq:生成标签文章列表缓存键 +======= + def get_queryset_cache_key(self):#lxy 标签缓存键(含标签名、页码) +>>>>>>> LXY_branch slug = self.kwargs['tag_name'] tag = get_object_or_404(Tag, slug=slug) tag_name = tag.name @@ -291,7 +340,7 @@ class TagDetailView(ArticleListView): return super(TagDetailView, self).get_context_data(** kwargs) -class ArchivesView(ArticleListView): +class ArchivesView(ArticleListView):#lxy 文章归档视图 ''' 文章归档页面视图 ''' @@ -300,16 +349,24 @@ class ArchivesView(ArticleListView): page_kwarg = None # 无页码参数 template_name = 'blog/article_archives.html' # 归档页模板 +<<<<<<< HEAD def get_queryset_data(self): #ymq:获取所有已发布文章(用于归档) return Article.objects.filter(status='p').all() def get_queryset_cache_key(self): #ymq:生成归档页缓存键 +======= + def get_queryset_data(self):#lxy 获取所有已发布文章 + return Article.objects.filter(status='p').all() + + def get_queryset_cache_key(self):#lxy 归档缓存键 +>>>>>>> LXY_branch cache_key = 'archives' return cache_key +<<<<<<< HEAD class LinkListView(ListView): #ymq:友情链接列表视图 model = Links # 关联模型 @@ -325,6 +382,19 @@ class EsSearchView(SearchView): def get_context(self): #ymq:构建搜索结果页面的上下文数据 paginator, page = self.build_page() # 处理分页 +======= +class LinkListView(ListView):#lxy 友链列表视图 + model = Links + template_name = 'blog/links_list.html' #lxy 渲染模板 + + def get_queryset(self):#lxy 获取启用的友链 + return Links.objects.filter(is_enable=True) + + +class EsSearchView(SearchView):#lxy ES搜索视图 + def get_context(self):#lxy 补充搜索上下文 + paginator, page = self.build_page() +>>>>>>> LXY_branch context = { "query": self.query, # 搜索关键词 "form": self.form, # 搜索表单