diff --git a/src/django-master/comments/admin.py b/src/django-master/comments/admin.py index d3889c0..a814f3f 100644 --- a/src/django-master/comments/admin.py +++ b/src/django-master/comments/admin.py @@ -1,100 +1,47 @@ -#django核心组件导入 -from django import forms# Django 表单处理模块 -from django.contrib.auth.admin import UserAdmin # Django 默认用户管理后台类 -from django.contrib.auth.forms import UserChangeForm # 用户信息修改表单基类 -from django.contrib.auth.forms import UsernameField# 用户名专用表单字段 -from django.utils.translation import gettext_lazy as _ # 国际化翻译函数 +from django.contrib import admin +from django.urls import reverse +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ -# 本地应用导入 -# Register your models here. -from .models import BlogUser # 导入自定义用户模型 +def disable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=False) -class BlogUserCreationForm(forms.ModelForm): - """ - 自定义用户创建表单(用于管理后台添加新用户) - 继承自 ModelForm专门处理 BlogUser 模型的创建 - """ - # 定义密码输入字段(需要输入两次以确保一致) - password1 = forms.CharField(label=_('password'), # 字段标签(支持国际化) - widget=forms.PasswordInput) # 密码输入控件 - password2 = forms.CharField(label=_('Enter password again'), # 确认密码标签 - widget=forms.PasswordInput) # 密码输入控件 +def enable_commentstatus(modeladmin, request, queryset): + queryset.update(is_enable=True) - class Meta: - model = BlogUser# 关联的模型类 - fields = ('email',)# 创建用户时显示的字段(这里只显示email字段) - def clean_password2(self): - """ - 验证两次输入的密码是否一致 - Django 表单验证方法,方法名必须以 clean_ 开头 - """ - # Check that the two password entries match - password1 = self.cleaned_data.get("password1")# 获取第一次输入的密码 - password2 = self.cleaned_data.get("password2")# 获取第二次输入的密码 - # 如果两次密码不一致,抛出验证错误 - if password1 and password2 and password1 != password2: - raise forms.ValidationError(_("passwords do not match"))# 错误信息(支持国际化) - return password2# 返回验证后的值 +disable_commentstatus.short_description = _('Disable comments') +enable_commentstatus.short_description = _('Enable comments') - def save(self, commit=True): - """ - 重写保存方法,在保存用户前处理密码哈希 - """ - # Save the provided password in hashed format - user = super().save(commit=False) # 调用父类保存方法但不提交到数据库 - user.set_password(self.cleaned_data["password1"]) # 对密码进行哈希加密 - if commit: - user.source = 'adminsite' # 设置用户来源标记(表示通过管理后台创建) - user.save()# 保存到数据库 - return user# 返回用户对象 - - -class BlogUserChangeForm(UserChangeForm): - """ - 自定义用户信息修改表单(用于管理后台编辑用户) - 继承自 Django 自带的 UserChangeForm - """ - - class Meta: - model = BlogUser # 关联的模型类 - fields = '__all__'# 显示所有字段 - field_classes = {'username': UsernameField}# 指定用户名使用专用字段类型 - - def __init__(self, *args, **kwargs): - """ - 表单初始化方法 - 可以在这里对表单字段进行自定义设置 - """ - super().__init__(*args, **kwargs) # 调用父类初始化方法 - # 可以在这里添加自定义逻辑,如修改字段属性等 - -class BlogUserAdmin(UserAdmin): - """ - 自定义用户管理后台配置 - 继承自 Django 自带的 UserAdmin - """ - - # 指定使用的表单类 - form = BlogUserChangeForm # 编辑用户时使用的表单 - add_form = BlogUserCreationForm# 添加用户时使用的表单 - - # 管理后台列表页显示配置 +class CommentAdmin(admin.ModelAdmin): + list_per_page = 20 list_display = ( - 'id', # 用户ID - 'nickname', # 用户昵称 - 'username',# 用户名 - 'email', # 电子邮箱 - 'last_login', # 最后登录时间 - 'date_joined', # 注册时间 - 'source')# 用户来源标记 - - # 设置哪些字段可以点击跳转到编辑页 - list_display_links = ('id', 'username') - - # 默认排序规则(按ID降序排列) - ordering = ('-id',) - + 'id', + 'body', + 'link_to_userinfo', + 'link_to_article', + 'is_enable', + 'creation_time') + list_display_links = ('id', 'body', 'is_enable') + list_filter = ('is_enable',) + exclude = ('creation_time', 'last_modify_time') + actions = [disable_commentstatus, enable_commentstatus] + + def link_to_userinfo(self, obj): + info = (obj.author._meta.app_label, obj.author._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + return format_html( + u'%s' % + (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + + def link_to_article(self, obj): + info = (obj.article._meta.app_label, obj.article._meta.model_name) + link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + return format_html( + u'%s' % (link, obj.article.title)) + + link_to_userinfo.short_description = _('User') + link_to_article.short_description = _('Article') diff --git a/src/django-master/comments/apps.py b/src/django-master/comments/apps.py index 32029ed..e8d41de 100644 --- a/src/django-master/comments/apps.py +++ b/src/django-master/comments/apps.py @@ -1,11 +1,6 @@ from django.apps import AppConfig - -class AccountsConfig(AppConfig): - """ - Accounts 应用的配置类。 - 功能: - 1. 定义应用名称(供 Django 内部识别)。 - 2. 可在此处覆盖 ready() 方法以注册信号等。 - """ - name = 'accounts'# 必须与项目中的应用目录名完全一致 + +class CommentsConfig(AppConfig): + name = 'comments' + diff --git a/src/django-master/comments/forms.py b/src/django-master/comments/forms.py index a0b5769..e83737d 100644 --- a/src/django-master/comments/forms.py +++ b/src/django-master/comments/forms.py @@ -1,117 +1,13 @@ from django import forms -from django.contrib.auth import get_user_model, password_validation -from django.contrib.auth.forms import AuthenticationForm, UserCreationForm -from django.core.exceptions import ValidationError -from django.forms import widgets -from django.utils.translation import gettext_lazy as _ -from . import utils -from .models import BlogUser +from django.forms import ModelForm -# 登录表单,继承自Django内置的AuthenticationForm -class LoginForm(AuthenticationForm): - def __init__(self, *args, **kwargs): - super(LoginForm, self).__init__(*args, **kwargs) - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['password'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) +from .models import Comment -# 注册表单,继承自Django内置的UserCreationForm -class RegisterForm(UserCreationForm): - def __init__(self, *args, **kwargs): - super(RegisterForm, self).__init__(*args, **kwargs) - # 自定义用户名、邮箱和密码字段的HTML属性 - self.fields['username'].widget = widgets.TextInput( - attrs={'placeholder': "username", "class": "form-control"}) - self.fields['email'].widget = widgets.EmailInput( - attrs={'placeholder': "email", "class": "form-control"}) - self.fields['password1'].widget = widgets.PasswordInput( - attrs={'placeholder': "password", "class": "form-control"}) - self.fields['password2'].widget = widgets.PasswordInput( - attrs={'placeholder': "repeat password", "class": "form-control"}) - # 验证邮箱唯一性 - def clean_email(self): - email = self.cleaned_data['email'] - if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) - return email - # 指定关联的用户模型和表单字段 - class Meta: - model = get_user_model() - fields = ("username", "email") - -# 忘记密码表单(验证邮箱和验证码) -class ForgetPasswordForm(forms.Form): - new_password1 = forms.CharField( - label=_("New password"), - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("New password") - } - ), - ) - # 新密码字段2(用于确认) - new_password2 = forms.CharField( - label="确认密码", - widget=forms.PasswordInput( - attrs={ - "class": "form-control", - 'placeholder': _("Confirm password") - } - ), - ) - # 邮箱字段 - email = forms.EmailField( - label='邮箱', - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Email") - } - ), - ) - # 验证码字段 - code = forms.CharField( - label=_('Code'), - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _("Code") - } - ), - ) - # 验证两次输入的密码是否一致,并检查密码强度 - def clean_new_password2(self): - password1 = self.data.get("new_password1") - password2 = self.data.get("new_password2") - if password1 and password2 and password1 != password2: - raise ValidationError(_("passwords do not match")) - password_validation.validate_password(password2)# 使用Django的密码验证器 - return password2 - # 验证邮箱是否已注册 - def clean_email(self): - user_email = self.cleaned_data.get("email") - if not BlogUser.objects.filter( - email=user_email - ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 - raise ValidationError(_("email does not exist")) - return user_email - # 验证用户输入的验证码是否正确 - def clean_code(self): - code = self.cleaned_data.get("code") - error = utils.verify(# 调用工具函数验证验证码 - email=self.cleaned_data.get("email"), - code=code, - ) - if error: - raise ValidationError(error) - return code +class CommentForm(ModelForm): + parent_comment_id = forms.IntegerField( + widget=forms.HiddenInput, required=False) -# 忘记密码功能中的验证码发送表单(仅需邮箱字段) -class ForgetPasswordCodeForm(forms.Form): - email = forms.EmailField( - label=_('Email'), - ) + class Meta: + model = Comment + fields = ['body'] diff --git a/src/django-master/comments/models.py b/src/django-master/comments/models.py index 0ac8c4d..7c3bbc8 100644 --- a/src/django-master/comments/models.py +++ b/src/django-master/comments/models.py @@ -1,39 +1,39 @@ -from django.contrib.auth.models import AbstractUser +from django.conf import settings from django.db import models -from django.urls import reverse from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ -from djangoblog.utils import get_current_site + +from blog.models import Article # Create your models here. -# 自定义用户模型,继承Django内置的AbstractUser -class BlogUser(AbstractUser): - # 用户昵称(可选) - nickname = models.CharField(_('nick name'), max_length=100, blank=True) - # 账号创建时间(默认当前时间) + +class Comment(models.Model): + body = models.TextField('正文', max_length=300) creation_time = models.DateTimeField(_('creation time'), default=now) - # 最后修改时间(默认当前时间) last_modify_time = models.DateTimeField(_('last modify time'), default=now) - # 账号创建来源(如:网站注册/第三方登录等,可选) - source = models.CharField(_('create source'), max_length=100, blank=True) - # 获取用户详情页的绝对URL(用于模板中的{% url %}反向解析) - def get_absolute_url(self): - return reverse( - 'blog:author_detail', kwargs={ - 'author_name': self.username}) - # 定义对象的字符串表示(Admin后台和shell中显示) - def __str__(self): - return self.email - # 获取用户详情页的完整URL(包含域名,用于分享链接) - def get_full_url(self): - site = get_current_site().domain# 获取当前站点域名 - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) - return url - # 元数据配置(模型级别的选项) + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + on_delete=models.CASCADE) + article = models.ForeignKey( + Article, + verbose_name=_('article'), + on_delete=models.CASCADE) + parent_comment = models.ForeignKey( + 'self', + verbose_name=_('parent comment'), + blank=True, + null=True, + on_delete=models.CASCADE) + is_enable = models.BooleanField(_('enable'), + default=False, blank=False, null=False) + class Meta: - ordering = ['-id'] # 默认按ID降序排列 - verbose_name = _('user') # 单数形式名称(后台显示) - verbose_name_plural = verbose_name# 复数形式名称(后台显示) - get_latest_by = 'id'# 指定最新记录的排序字段 + ordering = ['-id'] + verbose_name = _('comment') + verbose_name_plural = verbose_name + get_latest_by = 'id' + + def __str__(self): + return self.body diff --git a/src/django-master/comments/tests.py b/src/django-master/comments/tests.py index 503396e..2a7f55f 100644 --- a/src/django-master/comments/tests.py +++ b/src/django-master/comments/tests.py @@ -1,231 +1,109 @@ -from django.test import Client, RequestFactory, TestCase +from django.test import Client, RequestFactory, TransactionTestCase from django.urls import reverse -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ from accounts.models import BlogUser -from blog.models import Article, Category -from djangoblog.utils import * -from . import utils +from blog.models import Category, Article +from comments.models import Comment +from comments.templatetags.comments_tags import * +from djangoblog.utils import get_max_articleid_commentid # Create your tests here. -# 创建测试类(继承Django的TestCase) -class AccountTest(TestCase): - # 测试初始化方法(每个测试方法运行前都会执行) + +class CommentsTest(TransactionTestCase): def setUp(self): - # 初始化测试客户端(模拟浏览器请求) self.client = Client() - # 初始化请求工厂(用于生成请求对象) self.factory = RequestFactory() - # 创建一个普通测试用户 - self.blog_user = BlogUser.objects.create_user( - username="test", - email="admin@admin.com", - password="12345678" - ) - # 测试用的随机字符串 - self.new_test = "xxx123--=" - # 测试用户账号验证功能 - def test_validate_account(self): - # 获取当前站点域名 - site = get_current_site().domain - # 创建一个超级用户(用于测试管理员权限) - user = BlogUser.objects.create_superuser( + from blog.models import BlogSettings + value = BlogSettings() + value.comment_need_review = True + value.save() + + self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", - password="qwer!@#$ggg") - # 从数据库获取刚创建的超级用户(验证是否创建成功) - testuser = BlogUser.objects.get(username='liangliangyy1') - # 测试登录功能 - loginresult = self.client.login( - username='liangliangyy1', - password='qwer!@#$ggg') - self.assertEqual(loginresult, True)# 验证登录成功 - # 测试访问管理员后台 - response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200)# 验证返回200状态码 - # 创建测试分类 - category = Category() - category.name = "categoryaaa" - category.creation_time = timezone.now() - category.last_modify_time = timezone.now() - category.save() - # 创建测试文章 - article = Article() - article.title = "nicetitleaaa" - article.body = "nicecontentaaa" - article.author = user# 关联超级用户 - article.category = category# 关联上面创建的分类 - article.type = 'a' # 文章类型 - article.status = 'p' # 发布状态 - article.save() - # 测试访问文章的管理URL - response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) # 验证返回200状态码 - - # 测试用户注册功能 - def test_validate_register(self): - # 验证测试邮箱初始不存在 - self.assertEquals( - 0, len( - BlogUser.objects.filter( - email='user123@user.com'))) - # 模拟注册请求 - response = self.client.post(reverse('account:register'), { - 'username': 'user1233', - 'email': 'user123@user.com', - 'password1': 'password123!q@wE#R$T', - 'password2': 'password123!q@wE#R$T', - }) - # 验证用户已创建(通过邮箱查询) - self.assertEquals( - 1, len( - BlogUser.objects.filter( - email='user123@user.com'))) - # 获取刚注册的用户 - user = BlogUser.objects.filter(email='user123@user.com')[0] - # 生成验证签名(用于邮箱验证等场景) - sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - # 构造验证URL - path = reverse('accounts:result') - url = '{path}?type=validation&id={id}&sign={sign}'.format( - path=path, id=user.id, sign=sign) - # 测试访问验证URL - response = self.client.get(url) - self.assertEqual(response.status_code, 200) # 验证返回200状态码 - # 使用测试客户端登录 - self.client.login(username='user1233', password='password123!q@wE#R$T') - # 获取指定邮箱的用户并设置为超级用户和工作人员 - user = BlogUser.objects.filter(email='user123@user.com')[0] - user.is_superuser = True - user.is_staff = True - user.save() - # 删除侧边栏缓存 - delete_sidebar_cache() - # 创建分类 + password="liangliangyy1") + + def update_article_comment_status(self, article): + comments = article.comment_set.all() + for comment in comments: + comment.is_enable = True + comment.save() + + def test_validate_comment(self): + self.client.login(username='liangliangyy1', password='liangliangyy1') + category = Category() - category.name = "categoryaaa" - category.creation_time = timezone.now() - category.last_modify_time = timezone.now() + category.name = "categoryccc" category.save() - # 创建文章 + article = Article() + article.title = "nicetitleccc" + article.body = "nicecontentccc" + article.author = self.user article.category = category - article.title = "nicetitle333" - article.body = "nicecontentttt" - article.author = user - article.type = 'a' article.status = 'p' article.save() - # 测试已登录用户访问文章管理URL - response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) - # 测试注销功能 - response = self.client.get(reverse('account:logout')) - self.assertIn(response.status_code, [301, 302, 200]) - # 测试注销后访问文章管理URL(应重定向) - response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) - # 测试使用错误密码登录 - response = self.client.post(reverse('account:login'), { - 'username': 'user1233', - 'password': 'password123'# 注意这里密码与登录时使用的不同 - }) - self.assertIn(response.status_code, [301, 302, 200]) - # 测试使用错误密码登录后访问文章管理URL(应重定向) - response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) - # 测试邮箱验证码验证 - def test_verify_email_code(self): - to_email = "admin@admin.com" - code = generate_code() # 生成验证码 - utils.set_code(to_email, code)# 存储验证码 - utils.send_verify_email(to_email, code) # 发送验证邮件(实际测试中可能不会真的发送) - # 测试正确验证码 - err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) - # 测试错误邮箱 - err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str)# 应返回错误信息字符串 - # 测试忘记密码发送验证码功能 - 成功情况 - def test_forget_password_email_code_success(self): - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict(email="admin@admin.com") # 使用正确邮箱格式 - ) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok")# 验证返回成功消息 - # 测试忘记密码发送验证码功能 - 失败情况 - def test_forget_password_email_code_fail(self): - # 测试不提供邮箱 - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict() - ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") - # 测试提供错误格式邮箱 - resp = self.client.post( - path=reverse("account:forget_password_code"), - data=dict(email="admin@com") - ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") - # 测试忘记密码重置功能 - 成功情况 - def test_forget_password_email_success(self): - code = generate_code() - utils.set_code(self.blog_user.email, code)# 为测试用户设置验证码 - # 准备重置密码数据 - data = dict( - new_password1=self.new_test, # 新密码 - new_password2=self.new_test,# 确认密码 - email=self.blog_user.email,# 用户邮箱 - code=code, # 验证码 - ) - # 提交重置密码请求 - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - self.assertEqual(resp.status_code, 302) # 应重定向 - - # 验证用户密码是否修改成功 - 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) - # 测试忘记密码重置功能 - 用户不存在情况 - def test_forget_password_email_not_user(self): - data = dict( - new_password1=self.new_test, - new_password2=self.new_test, - email="123@123.com",# 不存在的邮箱 - code="123456", - ) - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - - self.assertEqual(resp.status_code, 200) # 应返回错误页面而非重定向 - - # 测试忘记密码重置功能 - 验证码错误情况 - def test_forget_password_email_code_error(self): - code = generate_code() - utils.set_code(self.blog_user.email, code)# 设置正确验证码 - # 使用错误验证码提交 - data = dict( - new_password1=self.new_test, - new_password2=self.new_test, - email=self.blog_user.email, - code="111111",# 错误验证码 - ) - resp = self.client.post( - path=reverse("account:forget_password"), - data=data - ) - - self.assertEqual(resp.status_code, 200)# 应返回错误页面而非重定向 + comment_url = reverse( + 'comments:postcomment', kwargs={ + 'article_id': article.id}) + + response = self.client.post(comment_url, + { + 'body': '123ffffffffff' + }) + + self.assertEqual(response.status_code, 302) + + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 0) + self.update_article_comment_status(article) + + self.assertEqual(len(article.comment_list()), 1) + + response = self.client.post(comment_url, + { + 'body': '123ffffffffff', + }) + + self.assertEqual(response.status_code, 302) + + article = Article.objects.get(pk=article.pk) + self.update_article_comment_status(article) + self.assertEqual(len(article.comment_list()), 2) + parent_comment_id = article.comment_list()[0].id + + response = self.client.post(comment_url, + { + 'body': ''' + # Title1 + + ```python + import os + ``` + + [url](https://www.lylinux.net/) + + [ddd](http://www.baidu.com) + + + ''', + 'parent_comment_id': parent_comment_id + }) + + self.assertEqual(response.status_code, 302) + self.update_article_comment_status(article) + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 3) + comment = Comment.objects.get(id=parent_comment_id) + tree = parse_commenttree(article.comment_list(), comment) + self.assertEqual(len(tree), 1) + data = show_comment_item(comment, True) + self.assertIsNotNone(data) + s = get_max_articleid_commentid() + self.assertIsNotNone(s) + + from comments.utils import send_comment_email + send_comment_email(comment) diff --git a/src/django-master/comments/views.py b/src/django-master/comments/views.py index b168f68..8f8c8f3 100644 --- a/src/django-master/comments/views.py +++ b/src/django-master/comments/views.py @@ -1,289 +1,62 @@ -# 导入日志模块,用于记录运行时的信息和错误 -import logging -# Django 国际化工具,`gettext_lazy` 用于延迟翻译(适合模块级字符串) -from django.utils.translation import gettext_lazy as _ -# Django 配置项,用于访问 settings.py 中的设置 -from django.conf import settings -# Django 认证系统核心模块 -from django.contrib import auth -# 认证相关常量(如重定向字段名) -from django.contrib.auth import REDIRECT_FIELD_NAME -# 获取当前用户模型的快捷方式 -from django.contrib.auth import get_user_model -# 用户登出功能 -from django.contrib.auth import logout -# Django 内置的认证表单(如登录表单) -from django.contrib.auth.forms import AuthenticationForm -# 密码哈希工具 -from django.contrib.auth.hashers import make_password -# HTTP 响应类(重定向、禁止访问等) -from django.http import HttpResponseRedirect, HttpResponseForbidden -# HTTP 请求和响应的类型提示(可选,用于类型检查) -from django.http.request import HttpRequest -from django.http.response import HttpResponse -# 快捷函数(如获取对象或返回 404) +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 -# 渲染模板的快捷方式 -from django.shortcuts import render -# URL 反转工具(通过名称生成 URL) -from django.urls import reverse -# 视图装饰器工具 from django.utils.decorators import method_decorator -# URL 安全验证工具(防止重定向攻击) -from django.utils.http import url_has_allowed_host_and_scheme -# 基础视图类 -from django.views import View -# 缓存控制装饰器(禁用缓存) -from django.views.decorators.cache import never_cache -# CSRF 防护装饰器 from django.views.decorators.csrf import csrf_protect -# 敏感参数标记(如密码字段) -from django.views.decorators.debug import sensitive_post_parameters -# 通用视图类(表单视图、重定向视图) -from django.views.generic import FormView, RedirectView -# 项目自定义工具 -from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache -# 当前应用的工具模块 -from . import utils -# 当前应用的表单(注册、登录、忘记密码等) -from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm -# 当前应用的模型(博客用户) -from .models import BlogUser -# 初始化日志记录器(__name__ 表示当前模块名) -logger = logging.getLogger(__name__) +from django.views.generic.edit import FormView +from accounts.models import BlogUser +from blog.models import Article +from .forms import CommentForm +from .models import Comment -# Create your views here. -# 注册视图类(继承自 FormView,处理表单提交) -class RegisterView(FormView): - # 指定使用的表单类 - form_class = RegisterForm - template_name = 'account/registration_form.html' - # 使用装饰器确保视图禁用缓存(never_cache)并启用 CSRF 防护 - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - # 调用父类方法处理请求 - return super(RegisterView, self).dispatch(*args, **kwargs) - # 表单验证通过后的处理逻辑 - def form_valid(self, form): - # 再次检查表单有效性(冗余,因为 FormView 已验证) - if form.is_valid(): - # 保存用户对象(但暂不激活 is_active=False) - user = form.save(False) - user.is_active = False# 用户需验证邮箱后才能登录 - user.source = 'Register' # 标记用户来源为注册 - user.save(True)# 实际保存到数据库 - - # 获取当前站点域名(用于生成验证链接) - site = get_current_site().domain - # 生成签名(双重 SHA256 哈希,用于验证链接安全性) - sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - # 开发环境使用本地地址(避免因域名未配置导致链接失效) - if settings.DEBUG: - site = '127.0.0.1:8000' - - # 生成验证结果的 URL(如 /account/result/?type=validation&id=1&sign=abc123) - path = reverse('account:result') - url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( - site=site, path=path, id=user.id, sign=sign) - # 构造邮件内容(包含验证链接) - content = """ -

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

- {url} +class CommentPostView(FormView): + form_class = CommentForm + template_name = 'blog/article_detail.html' - 再次感谢您! -
- 如果上面链接无法打开,请将此链接复制至浏览器。 - {url} - """.format(url=url) - # 发送验证邮件 - send_email( - emailto=[ - user.email, # 收件人列表 - ], - title='验证您的电子邮箱', # 邮件标题 - content=content)# 邮件正文 - # 重定向到注册结果页面(附带用户 ID) - url = reverse('accounts:result') + \ - '?type=register&id=' + str(user.id) - return HttpResponseRedirect(url) - else: - # 表单无效时重新渲染表单(显示错误信息) - return self.render_to_response({ - 'form': form - }) + @method_decorator(csrf_protect) + def dispatch(self, *args, **kwargs): + return super(CommentPostView, self).dispatch(*args, **kwargs) -# 登出视图,继承自RedirectView,重定向到登录页面 -class LogoutView(RedirectView): - # 登出后重定向的URL - url = '/login/' - # 使用never_cache装饰器确保视图不会被缓存 - @method_decorator(never_cache) - def dispatch(self, request, *args, **kwargs): - # 调用父类的dispatch方法处理请求 - return super(LogoutView, self).dispatch(request, *args, **kwargs) - # 处理GET请求 def get(self, request, *args, **kwargs): - # 执行登出操作 - logout(request) - # 删除侧边栏缓存 - delete_sidebar_cache() - # 调用父类的get方法完成重定向 - return super(LogoutView, self).get(request, *args, **kwargs) - -# 登录视图,继承自FormView -class LoginView(FormView): - # 使用的表单类 - form_class = LoginForm - # 模板文件路径 - template_name = 'account/login.html' - # 登录成功后跳转的URL - success_url = '/' - # 重定向字段名 - redirect_field_name = REDIRECT_FIELD_NAME - # 登录会话有效期(一个月的时间,单位:秒) - login_ttl = 2626560 # 一个月的时间 - # 使用多个装饰器装饰dispatch方法 - @method_decorator(sensitive_post_parameters('password'))# 标记密码参数为敏感信息 - @method_decorator(csrf_protect) # 启用CSRF保护 - @method_decorator(never_cache)# 禁止缓存 - def dispatch(self, request, *args, **kwargs): - # 调用父类的dispatch方法处理请求 - return super(LoginView, self).dispatch(request, *args, **kwargs) - - # 获取模板上下文数据 - def get_context_data(self, **kwargs): - # 从GET参数中获取重定向URL - redirect_to = self.request.GET.get(self.redirect_field_name) - # 如果不存在则设置为根路径 - if redirect_to is None: - redirect_to = '/' - # 将重定向URL添加到上下文 - kwargs['redirect_to'] = redirect_to - # 调用父类方法获取其他上下文数据 - - return super(LoginView, self).get_context_data(**kwargs) - # 表单验证通过后的处理 - def form_valid(self, form): - # 重新创建认证表单(这里可能有逻辑问题,因为form已经传入) - form = AuthenticationForm(data=self.request.POST, request=self.request) - # 再次验证表单 - if form.is_valid(): - # 删除侧边栏缓存 - delete_sidebar_cache() - # 记录日志 - logger.info(self.redirect_field_name) - - # 登录用户 - auth.login(self.request, form.get_user()) - # 如果用户选择了"记住我" - if self.request.POST.get("remember"): - # 设置较长的会话过期时间 - self.request.session.set_expiry(self.login_ttl) - # 调用父类方法处理成功跳转 - return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') - else: - # 表单无效,重新渲染表单并显示错误 - return self.render_to_response({ - 'form': form - }) - # 获取成功后的跳转URL - def get_success_url(self): - - # 从POST参数中获取重定向URL - redirect_to = self.request.POST.get(self.redirect_field_name) - # 检查URL是否安全 - if not url_has_allowed_host_and_scheme( - url=redirect_to, allowed_hosts=[ - self.request.get_host()]): - # 如果不安全则使用默认成功URL - redirect_to = self.success_url - return redirect_to - -# 账户操作结果页面(如注册成功、邮箱验证等) -def account_result(request): - # 从GET参数中获取类型和用户ID - type = request.GET.get('type') - id = request.GET.get('id') - # 获取用户对象,如果不存在返回404 - user = get_object_or_404(get_user_model(), id=id) - logger.info(type) - # 如果用户已激活,直接重定向到首页 - if user.is_active: - return HttpResponseRedirect('/') - # 检查类型参数是否有效 - if type and type in ['register', 'validation']: - if type == 'register': - content = ''' - 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 - ''' - title = '注册成功' - else: - # 生成验证签名 - c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - # 获取请求中的签名 - sign = request.GET.get('sign') - # 验证签名是否匹配 - if sign != c_sign: - return HttpResponseForbidden() - # 激活用户账户 - user.is_active = True - user.save() - # 验证成功提示内容 - content = ''' - 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 - ''' - title = '验证成功' - # 渲染结果页面 - return render(request, 'account/result.html', { - 'title': title, - 'content': content + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + url = article.get_absolute_url() + return HttpResponseRedirect(url + "#comments") + + def form_invalid(self, form): + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + return self.render_to_response({ + 'form': form, + 'article': article }) - else: - # 无效类型重定向到首页 - return HttpResponseRedirect('/') - -# 忘记密码视图,继承自FormView -class ForgetPasswordView(FormView): - # 使用的表单类 - form_class = ForgetPasswordForm - # 模板文件路径 - template_name = 'account/forget_password.html' - - # 表单验证通过后的处理 def form_valid(self, form): - if form.is_valid(): - # 根据邮箱获取用户对象 - blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() - # 设置新密码(加密) - blog_user.password = make_password(form.cleaned_data["new_password2"]) - # 保存用户对象 - blog_user.save() - # 重定向到登录页面 - return HttpResponseRedirect('/login/') - else: - # 表单无效,重新渲染表单并显示错误 - return self.render_to_response({'form': form}) - -# 忘记密码验证码发送视图,继承自View -class ForgetPasswordEmailCode(View): - # 处理POST请求 - def post(self, request: HttpRequest): - # 验证表单 - form = ForgetPasswordCodeForm(request.POST) - if not form.is_valid(): - # 表单无效返回错误 - return HttpResponse("错误的邮箱") - # 获取邮箱地址 - to_email = form.cleaned_data["email"] - # 生成验证码 - code = generate_code() - # 发送验证邮件 - utils.send_verify_email(to_email, code) - # 存储验证码(通常有有效期) - utils.set_code(to_email, code) - # 返回成功响应 - return HttpResponse("ok") + """提交的数据验证合法后的逻辑""" + user = self.request.user + author = BlogUser.objects.get(pk=user.pk) + article_id = self.kwargs['article_id'] + article = get_object_or_404(Article, pk=article_id) + + if article.comment_status == 'c' or article.status == 'c': + raise ValidationError("该文章评论已关闭.") + comment = form.save(False) + comment.article = article + from djangoblog.utils import get_blog_setting + settings = get_blog_setting() + if not settings.comment_need_review: + comment.is_enable = True + comment.author = author + + if form.cleaned_data['parent_comment_id']: + parent_comment = Comment.objects.get( + pk=form.cleaned_data['parent_comment_id']) + comment.parent_comment = parent_comment + + comment.save(True) + return HttpResponseRedirect( + "%s#div-comment-%d" % + (article.get_absolute_url(), comment.pk))