diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py index 32e483c..953d3c0 100644 --- a/src/DjangoBlog-master/accounts/admin.py +++ b/src/DjangoBlog-master/accounts/admin.py @@ -1,52 +1,80 @@ +# admin.py - Django后台管理配置文件 + +# 导入Django表单模块 from django import forms +# 导入Django用户管理类 from django.contrib.auth.admin import UserAdmin +# 导入用户修改表单 from django.contrib.auth.forms import UserChangeForm +# 导入用户名字段 from django.contrib.auth.forms import UsernameField +# 导入国际化翻译函数 from django.utils.translation import gettext_lazy as _ -# Register your models here. +# 在这里注册模型 +# 导入自定义的用户模型 from .models import BlogUser +# 自定义用户创建表单 class BlogUserCreationForm(forms.ModelForm): + # 密码字段1 - 用于输入密码 password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + # 密码字段2 - 用于确认密码 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: + # 指定关联的模型 model = BlogUser + # 表单包含的字段 fields = ('email',) + # 清理密码确认字段的方法 def clean_password2(self): - # Check that the two password entries match + # 从已清理的数据中获取两个密码字段的值 password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") + # 检查两个密码是否匹配 if password1 and password2 and password1 != password2: raise forms.ValidationError(_("passwords do not match")) return password2 + # 保存用户的方法 def save(self, commit=True): - # Save the provided password in hashed format + # 调用父类的save方法但不立即提交到数据库 user = super().save(commit=False) + # 设置哈希后的密码 user.set_password(self.cleaned_data["password1"]) if commit: + # 设置用户来源为管理员站点 user.source = 'adminsite' + # 保存用户到数据库 user.save() return user +# 自定义用户修改表单 class BlogUserChangeForm(UserChangeForm): class Meta: + # 指定关联的模型 model = BlogUser + # 包含所有字段 fields = '__all__' + # 字段类型映射 field_classes = {'username': UsernameField} + # 初始化方法 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) +# 自定义用户管理类 class BlogUserAdmin(UserAdmin): + # 指定修改表单 form = BlogUserChangeForm + # 指定创建表单 add_form = BlogUserCreationForm + # 列表页面显示的字段 list_display = ( 'id', 'nickname', @@ -55,5 +83,7 @@ class BlogUserAdmin(UserAdmin): 'last_login', 'date_joined', 'source') + # 列表中可点击链接的字段 list_display_links = ('id', 'username') - ordering = ('-id',) + # 默认排序字段(按ID降序) + ordering = ('-id',) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/apps.py b/src/DjangoBlog-master/accounts/apps.py index 9b3fc5a..8d946a8 100644 --- a/src/DjangoBlog-master/accounts/apps.py +++ b/src/DjangoBlog-master/accounts/apps.py @@ -1,5 +1,10 @@ +# apps.py - Django应用程序配置文件 + +# 导入Django应用配置基类 from django.apps import AppConfig +# 定义账户应用的配置类 class AccountsConfig(AppConfig): - name = 'accounts' + # 指定应用程序的完整Python路径 + name = 'accounts' \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/forms.py b/src/DjangoBlog-master/accounts/forms.py index fce4137..d778d0d 100644 --- a/src/DjangoBlog-master/accounts/forms.py +++ b/src/DjangoBlog-master/accounts/forms.py @@ -1,47 +1,71 @@ +# forms.py - Django表单定义文件 + +# 导入Django表单模块 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 +# 登录表单类 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"}) +# 注册表单类 class RegisterForm(UserCreationForm): + # 初始化方法,设置表单字段的样式和属性 def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) - + # 设置用户名字段的输入框样式 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( @@ -52,6 +76,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 确认新密码字段 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -62,6 +87,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 邮箱字段 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -72,6 +98,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 验证码字段 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -82,17 +109,22 @@ class ForgetPasswordForm(forms.Form): ), ) + # 清理确认密码字段的方法 def clean_new_password2(self): password1 = self.data.get("new_password1") password2 = self.data.get("new_password2") + # 检查两个密码是否匹配 if password1 and password2 and password1 != password2: raise ValidationError(_("passwords do not match")) + # 验证密码强度 password_validation.validate_password(password2) return password2 + # 清理邮箱字段的方法,检查邮箱是否存在 def clean_email(self): user_email = self.cleaned_data.get("email") + # 检查邮箱是否已注册 if not BlogUser.objects.filter( email=user_email ).exists(): @@ -100,8 +132,10 @@ class ForgetPasswordForm(forms.Form): 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, @@ -111,7 +145,9 @@ class ForgetPasswordForm(forms.Form): return code +# 获取忘记密码验证码表单类 class ForgetPasswordCodeForm(forms.Form): + # 邮箱字段 email = forms.EmailField( label=_('Email'), - ) + ) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py index 3baddbb..8c92325 100644 --- a/src/DjangoBlog-master/accounts/models.py +++ b/src/DjangoBlog-master/accounts/models.py @@ -1,35 +1,58 @@ +# models.py - Django数据模型定义文件 + +# 导入Django内置的用户抽象基类 from django.contrib.auth.models import AbstractUser +# 导入Django数据库模型 from django.db import models +# 导入URL反向解析函数 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 -# Create your models here. +# 在这里创建模型 +# 博客用户模型,继承自Django的AbstractUser class BlogUser(AbstractUser): + # 昵称字段,最大长度100字符,允许为空 nickname = models.CharField(_('nick name'), max_length=100, blank=True) + # 创建时间字段,默认值为当前时间 creation_time = models.DateTimeField(_('creation time'), default=now) + # 最后修改时间字段,默认值为当前时间 last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # 用户来源字段,记录创建来源,最大长度100字符,允许为空 source = models.CharField(_('create source'), max_length=100, blank=True) + # 获取用户绝对URL的方法 def get_absolute_url(self): return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) + # 对象的字符串表示方法,返回邮箱地址 def __str__(self): return self.email + # 获取完整URL的方法(包含域名) def get_full_url(self): + # 获取当前站点域名 site = get_current_site().domain + # 构建完整的URL url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url + # 模型的元数据配置 class Meta: + # 默认按ID降序排列 ordering = ['-id'] + # 单数形式的显示名称 verbose_name = _('user') + # 复数形式的显示名称(与单数相同) verbose_name_plural = verbose_name - get_latest_by = 'id' + # 指定获取最新对象的字段 + get_latest_by = 'id' \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/tests.py b/src/DjangoBlog-master/accounts/tests.py index 6893411..9b3b3aa 100644 --- a/src/DjangoBlog-master/accounts/tests.py +++ b/src/DjangoBlog-master/accounts/tests.py @@ -1,135 +1,186 @@ +# tests.py - Django测试用例文件 + +# 导入Django测试相关模块 from django.test import Client, RequestFactory, TestCase +# 导入URL反向解析 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 -# Create your tests here. +# 在这里创建测试用例 +# 账户测试类 class AccountTest(TestCase): + # 测试前置设置方法 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( 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) + # 创建测试分类 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.type = 'a' # 文章类型 + article.status = 'p' # 发布状态 article.save() + # 测试访问文章管理页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) + # 测试用户注册功能 def test_validate_register(self): + # 断言邮箱不存在 self.assertEquals( 0, len( BlogUser.objects.filter( email='user123@user.com'))) + # 发送注册请求 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))) path = reverse('accounts:result') + # 构建验证URL url = '{path}?type=validation&id={id}&sign={sign}'.format( path=path, id=user.id, sign=sign) + # 测试验证页面 response = self.client.get(url) self.assertEqual(response.status_code, 200) + # 测试用户登录 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() + + # 创建分类 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() + # 创建文章 article = Article() article.category = category article.title = "nicetitle333" article.body = "nicecontentttt" article.author = user - article.type = 'a' article.status = 'p' article.save() + # 测试访问文章管理页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) + # 测试退出登录 response = self.client.get(reverse('account:logout')) self.assertIn(response.status_code, [301, 302, 200]) + # 测试退出后访问管理页面(应该重定向) response = self.client.get(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]) + # 测试登录后访问管理页面 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"), @@ -139,33 +190,40 @@ class AccountTest(TestCase): 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) + self.assertEqual(resp.status_code, 302) # 重定向响应 # 验证用户密码是否修改成功 blog_user = BlogUser.objects.filter( @@ -174,6 +232,7 @@ class AccountTest(TestCase): 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, @@ -188,7 +247,7 @@ class AccountTest(TestCase): 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) @@ -196,12 +255,11 @@ class AccountTest(TestCase): new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111", # 错误的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 200) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/urls.py b/src/DjangoBlog-master/accounts/urls.py index 107a801..938471c 100644 --- a/src/DjangoBlog-master/accounts/urls.py +++ b/src/DjangoBlog-master/accounts/urls.py @@ -1,28 +1,54 @@ +# urls.py - Django URL路由配置文件 + +# 导入Django URL路由相关函数 from django.urls import path from django.urls import re_path +# 导入当前应用的视图模块 from . import views +# 导入登录表单类 from .forms import LoginForm +# 定义应用命名空间(用于URL反向解析) app_name = "accounts" -urlpatterns = [re_path(r'^login/$', - views.LoginView.as_view(success_url='/'), - name='login', - kwargs={'authentication_form': LoginForm}), - re_path(r'^register/$', - views.RegisterView.as_view(success_url="/"), - name='register'), - re_path(r'^logout/$', - views.LogoutView.as_view(), - name='logout'), - path(r'account/result.html', - views.account_result, - name='result'), - re_path(r'^forget_password/$', - views.ForgetPasswordView.as_view(), - name='forget_password'), - re_path(r'^forget_password_code/$', - views.ForgetPasswordEmailCode.as_view(), - name='forget_password_code'), - ] +# URL模式列表 - 定义URL路径与视图的映射关系 +urlpatterns = [ + # 登录URL - 使用正则表达式匹配路径 + re_path(r'^login/$', + # 使用类视图,设置登录成功后跳转到首页 + views.LoginView.as_view(success_url='/'), + name='login', # URL名称,用于反向解析 + # 传递额外参数,指定认证表单类 + kwargs={'authentication_form': LoginForm}), + + # 注册URL - 使用正则表达式匹配路径 + re_path(r'^register/$', + # 使用类视图,设置注册成功后跳转到首页 + views.RegisterView.as_view(success_url="/"), + name='register'), # URL名称,用于反向解析 + + # 退出登录URL - 使用正则表达式匹配路径 + re_path(r'^logout/$', + # 使用类视图 + views.LogoutView.as_view(), + name='logout'), # URL名称,用于反向解析 + + # 账户操作结果页面URL - 使用path匹配精确路径 + path(r'account/result.html', + # 使用函数视图 + views.account_result, + name='result'), # URL名称,用于反向解析 + + # 忘记密码URL - 使用正则表达式匹配路径 + re_path(r'^forget_password/$', + # 使用类视图 + views.ForgetPasswordView.as_view(), + name='forget_password'), # URL名称,用于反向解析 + + # 获取忘记密码验证码URL - 使用正则表达式匹配路径 + re_path(r'^forget_password_code/$', + # 使用类视图 + views.ForgetPasswordEmailCode.as_view(), + name='forget_password_code'), # URL名称,用于反向解析 +] \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/user_login_backend.py b/src/DjangoBlog-master/accounts/user_login_backend.py index 73cdca1..6c5907b 100644 --- a/src/DjangoBlog-master/accounts/user_login_backend.py +++ b/src/DjangoBlog-master/accounts/user_login_backend.py @@ -1,26 +1,42 @@ +# user_login_backend.py - 自定义用户认证后端 + +# 导入获取用户模型的函数 from django.contrib.auth import get_user_model +# 导入Django模型认证后端基类 from django.contrib.auth.backends import ModelBackend +# 自定义认证后端类,支持邮箱或用户名登录 class EmailOrUsernameModelBackend(ModelBackend): """ - 允许使用用户名或邮箱登录 + 允许使用用户名或邮箱登录的自定义认证后端 """ + # 用户认证方法 def authenticate(self, request, username=None, password=None, **kwargs): + # 判断输入的是邮箱还是用户名 if '@' in username: + # 如果包含@符号,按邮箱处理 kwargs = {'email': username} else: + # 否则按用户名处理 kwargs = {'username': username} + try: + # 根据用户名或邮箱获取用户 user = get_user_model().objects.get(**kwargs) + # 验证密码是否正确 if user.check_password(password): - return user + return user # 认证成功,返回用户对象 except get_user_model().DoesNotExist: + # 用户不存在,返回None return None + # 根据用户ID获取用户的方法 def get_user(self, username): try: + # 根据主键(用户ID)获取用户 return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: - return None + # 用户不存在,返回None + return None \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py index 4b94bdf..cc9081d 100644 --- a/src/DjangoBlog-master/accounts/utils.py +++ b/src/DjangoBlog-master/accounts/utils.py @@ -1,49 +1,71 @@ +# utils.py - 工具函数模块,处理验证码相关功能 + +# 导入类型提示模块 import typing +# 导入时间间隔类 from datetime import timedelta +# 导入Django缓存模块 from django.core.cache import cache +# 导入国际化翻译函数 from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ +# 导入发送邮件工具函数 from djangoblog.utils import send_email +# 验证码有效期设置为5分钟 _code_ttl = timedelta(minutes=5) def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 + """发送验证邮件 Args: - to_mail: 接受邮箱 - subject: 邮件主题 + to_mail: 接收邮箱地址 + subject: 邮件主题,默认为"验证邮件" code: 验证码 """ + # 构建邮件HTML内容,包含验证码信息 html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " "properly") % {'code': code} + # 调用发送邮件函数 send_email([to_mail], subject, html_content) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 + """验证验证码是否有效 Args: - email: 请求邮箱 - code: 验证码 + email: 请求验证的邮箱地址 + code: 用户输入的验证码 Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + 如果验证失败返回错误信息字符串,成功返回None + Note: + 这里的错误处理不太合理,应该采用raise抛出异常 + 否则调用方也需要对error进行处理 """ + # 从缓存中获取该邮箱对应的验证码 cache_code = get_code(email) + # 比较用户输入的验证码和缓存中的验证码 if cache_code != code: return gettext("Verification code error") def set_code(email: str, code: str): - """设置code""" + """设置验证码到缓存中 + Args: + email: 邮箱地址,作为缓存的key + code: 验证码,作为缓存的value + """ + # 将验证码存入缓存,设置过期时间为5分钟 cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) + """从缓存中获取验证码 + Args: + email: 邮箱地址,作为缓存的key + Return: + 返回验证码字符串,如果不存在则返回None + """ + return cache.get(email) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py index ae67aec..51de92e 100644 --- a/src/DjangoBlog-master/accounts/views.py +++ b/src/DjangoBlog-master/accounts/views.py @@ -1,59 +1,89 @@ +# views.py - Django视图文件,处理用户账户相关请求 + +# 导入日志模块 import logging +# 导入国际化翻译函数 from django.utils.translation import gettext_lazy as _ +# 导入Django设置 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 from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.hashers import make_password +# 导入HTTP响应类 from django.http import HttpResponseRedirect, HttpResponseForbidden from django.http.request import HttpRequest from django.http.response import HttpResponse +# 导入快捷函数 from django.shortcuts import get_object_or_404 from django.shortcuts import render +# 导入URL反向解析 from django.urls import reverse +# 导入方法装饰器 from django.utils.decorators import method_decorator from django.utils.http import url_has_allowed_host_and_scheme +# 导入基于类的视图 from django.views import View from django.views.decorators.cache import never_cache 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 +# 获取日志记录器 logger = logging.getLogger(__name__) -# Create your views here. +# 在这里创建视图 +# 用户注册视图 class RegisterView(FormView): + # 指定使用的表单类 form_class = RegisterForm + # 指定模板文件 template_name = 'account/registration_form.html' + # 使用CSRF保护装饰器 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): return super(RegisterView, self).dispatch(*args, **kwargs) + # 表单验证通过后的处理 def form_valid(self, form): if form.is_valid(): + # 保存用户但不提交到数据库 user = form.save(False) + # 设置用户为非激活状态(需要邮箱验证) user.is_active = False + # 设置用户来源 user.source = 'Register' + # 保存用户到数据库 user.save(True) + # 获取当前站点域名 site = get_current_site().domain + # 生成验证签名 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + # 调试模式下使用本地地址 if settings.DEBUG: site = '127.0.0.1:8000' + # 构建验证URL 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 = """
请点击下面链接验证您的邮箱
@@ -64,6 +94,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + # 发送验证邮件 send_email( emailto=[ user.email, @@ -71,43 +102,59 @@ class RegisterView(FormView): title='验证您的电子邮箱', content=content) + # 重定向到结果页面 url = reverse('accounts:result') + \ '?type=register&id=' + str(user.id) return HttpResponseRedirect(url) else: + # 表单验证失败,重新渲染表单 return self.render_to_response({ 'form': form }) +# 用户退出登录视图 class LogoutView(RedirectView): + # 退出后重定向的URL url = '/login/' + # 禁用缓存 @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): return super(LogoutView, self).dispatch(request, *args, **kwargs) + # 处理GET请求 def get(self, request, *args, **kwargs): + # 执行退出登录操作 logout(request) + # 清理侧边栏缓存 delete_sidebar_cache() return super(LogoutView, self).get(request, *args, **kwargs) +# 用户登录视图 class LoginView(FormView): + # 指定使用的表单类 form_class = LoginForm + # 指定模板文件 template_name = 'account/login.html' + # 登录成功后的默认重定向URL success_url = '/' + # 重定向字段名 redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 + # 登录会话有效期(一个月) + login_ttl = 2626560 + # 使用多个装饰器保护敏感操作 @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - return super(LoginView, self).dispatch(request, *args, **kwargs) + # 获取上下文数据 def get_context_data(self, **kwargs): + # 获取重定向URL redirect_to = self.request.GET.get(self.redirect_field_name) if redirect_to is None: redirect_to = '/' @@ -115,26 +162,33 @@ class LoginView(FormView): return super(LoginView, self).get_context_data(**kwargs) + # 表单验证通过后的处理 def form_valid(self, 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()]): @@ -142,63 +196,91 @@ class LoginView(FormView): return redirect_to +# 账户操作结果页面视图函数 def account_result(request): + # 获取操作类型和用户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') + # 签名不匹配,返回403禁止访问 if sign != c_sign: return HttpResponseForbidden() + # 激活用户账户 user.is_active = True user.save() content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' + # 渲染结果页面 return render(request, 'account/result.html', { 'title': title, 'content': content }) else: + # 无效类型,重定向到首页 return HttpResponseRedirect('/') +# 忘记密码视图 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}) +# 忘记密码验证码发送视图 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") + return HttpResponse("ok") \ No newline at end of file