diff --git a/doc/个人贡献.docx b/doc/个人贡献.docx new file mode 100644 index 0000000..e8d520d Binary files /dev/null and b/doc/个人贡献.docx differ diff --git a/src/django-master/accounts/admin.py b/src/django-master/accounts/admin.py index 32e483c..d3889c0 100644 --- a/src/django-master/accounts/admin.py +++ b/src/django-master/accounts/admin.py @@ -1,59 +1,100 @@ -from django import forms -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 _ +#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 _ # 国际化翻译函数 +# 本地应用导入 # Register your models here. -from .models import BlogUser +from .models import BlogUser # 导入自定义用户模型 class BlogUserCreationForm(forms.ModelForm): - password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) - password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) + """ + 自定义用户创建表单(用于管理后台添加新用户) + 继承自 ModelForm专门处理 BlogUser 模型的创建 + """ + + # 定义密码输入字段(需要输入两次以确保一致) + password1 = forms.CharField(label=_('password'), # 字段标签(支持国际化) + widget=forms.PasswordInput) # 密码输入控件 + password2 = forms.CharField(label=_('Enter password again'), # 确认密码标签 + widget=forms.PasswordInput) # 密码输入控件 class Meta: - model = BlogUser - fields = ('email',) + 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") + 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 + raise forms.ValidationError(_("passwords do not match"))# 错误信息(支持国际化) + return password2# 返回验证后的值 + def save(self, commit=True): + """ + 重写保存方法,在保存用户前处理密码哈希 + """ # Save the provided password in hashed format - user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) + user = super().save(commit=False) # 调用父类保存方法但不提交到数据库 + user.set_password(self.cleaned_data["password1"]) # 对密码进行哈希加密 if commit: - user.source = 'adminsite' - user.save() - return user + user.source = 'adminsite' # 设置用户来源标记(表示通过管理后台创建) + user.save()# 保存到数据库 + return user# 返回用户对象 class BlogUserChangeForm(UserChangeForm): + """ + 自定义用户信息修改表单(用于管理后台编辑用户) + 继承自 Django 自带的 UserChangeForm + """ + class Meta: - model = BlogUser - fields = '__all__' - field_classes = {'username': UsernameField} + model = BlogUser # 关联的模型类 + fields = '__all__'# 显示所有字段 + field_classes = {'username': UsernameField}# 指定用户名使用专用字段类型 def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - + """ + 表单初始化方法 + 可以在这里对表单字段进行自定义设置 + """ + super().__init__(*args, **kwargs) # 调用父类初始化方法 + # 可以在这里添加自定义逻辑,如修改字段属性等 class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm + """ + 自定义用户管理后台配置 + 继承自 Django 自带的 UserAdmin + """ + + # 指定使用的表单类 + form = BlogUserChangeForm # 编辑用户时使用的表单 + add_form = BlogUserCreationForm# 添加用户时使用的表单 + + # 管理后台列表页显示配置 list_display = ( - 'id', - 'nickname', - 'username', - 'email', - 'last_login', - 'date_joined', - 'source') + 'id', # 用户ID + 'nickname', # 用户昵称 + 'username',# 用户名 + 'email', # 电子邮箱 + 'last_login', # 最后登录时间 + 'date_joined', # 注册时间 + 'source')# 用户来源标记 + + # 设置哪些字段可以点击跳转到编辑页 list_display_links = ('id', 'username') + + # 默认排序规则(按ID降序排列) ordering = ('-id',) + diff --git a/src/django-master/accounts/apps.py b/src/django-master/accounts/apps.py index 9b3fc5a..bf4ecc8 100644 --- a/src/django-master/accounts/apps.py +++ b/src/django-master/accounts/apps.py @@ -1,5 +1,11 @@ from django.apps import AppConfig - + class AccountsConfig(AppConfig): - name = 'accounts' + """ + Accounts 应用的配置类。 + 功能: + 1. 定义应用名称(供 Django 内部识别)。 + 2. 可在此处覆盖 ready() 方法以注册信号等。 + """ + name = 'accounts'# 必须与项目中的应用目录名完全一致 \ No newline at end of file diff --git a/src/django-master/accounts/forms.py b/src/django-master/accounts/forms.py index 2d47d28..43c0677 100644 --- a/src/django-master/accounts/forms.py +++ b/src/django-master/accounts/forms.py @@ -2,6 +2,7 @@ from django import forms #导入 Django 表单模块,用于创建自定义表 from django.contrib.auth import get_user_model, password_validation #get_user_model 用于获取项目中自定义的用户模型(遵循 Django 推荐的用户模型扩展方式)。 +<<<<<<< HEAD from django.contrib.auth.forms import AuthenticationForm, UserCreationForm #UserCreationForm:导入 Django 内置的认证表单(AuthenticationForm 用于登录)和用户创建表单(UserCreationForm 用于注册),作为自定义表单的基类。 from django.core.exceptions import ValidationError #导入 Django 的验证异常类,用于在表单验证时抛出自定义错误。 @@ -18,18 +19,29 @@ class LoginForm(AuthenticationForm): #继承 Django 内置的 AuthenticationForm super(LoginForm, self).__init__(*args, **kwargs) #调用父类构造方法,确保基础功能正常。 +======= +# 登录表单,继承自Django内置的AuthenticationForm +class LoginForm(AuthenticationForm): + def __init__(self, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) +>>>>>>> 5d714be542986b7f935eb0ec4df9b0918e75eeca self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) #为用户名字段设置文本输入小部件,定义占位符和 Bootstrap 样式类(form-control)。 self.fields['password'].widget = widgets.PasswordInput( attrs={'placeholder': "password", "class": "form-control"}) #为密码字段设置密码输入小部件,同样定义占位符和样式类。 +<<<<<<< HEAD class RegisterForm(UserCreationForm): #继承 Django 内置的 UserCreationForm,自定义注册表单的字段、样式和验证逻辑。 +======= +# 注册表单,继承自Django内置的UserCreationForm +class RegisterForm(UserCreationForm): +>>>>>>> 5d714be542986b7f935eb0ec4df9b0918e75eeca 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( @@ -38,18 +50,18 @@ class RegisterForm(UserCreationForm): #继承 Django 内置的 UserCreationForm 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"), @@ -60,7 +72,7 @@ class ForgetPasswordForm(forms.Form): } ), ) - + # 新密码字段2(用于确认) new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -70,7 +82,7 @@ class ForgetPasswordForm(forms.Form): } ), ) - + # 邮箱字段 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -80,7 +92,7 @@ class ForgetPasswordForm(forms.Form): } ), ) - + # 验证码字段 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -90,16 +102,16 @@ 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) + password_validation.validate_password(password2)# 使用Django的密码验证器 return password2 - + # 验证邮箱是否已注册 def clean_email(self): user_email = self.cleaned_data.get("email") if not BlogUser.objects.filter( @@ -108,10 +120,10 @@ class ForgetPasswordForm(forms.Form): # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 raise ValidationError(_("email does not exist")) return user_email - + # 验证用户输入的验证码是否正确 def clean_code(self): code = self.cleaned_data.get("code") - error = utils.verify( + error = utils.verify(# 调用工具函数验证验证码 email=self.cleaned_data.get("email"), code=code, ) @@ -119,7 +131,7 @@ class ForgetPasswordForm(forms.Form): raise ValidationError(error) return code - +# 忘记密码功能中的验证码发送表单(仅需邮箱字段) class ForgetPasswordCodeForm(forms.Form): email = forms.EmailField( label=_('Email'), diff --git a/src/django-master/accounts/models.py b/src/django-master/accounts/models.py index 3baddbb..0ac8c4d 100644 --- a/src/django-master/accounts/models.py +++ b/src/django-master/accounts/models.py @@ -7,29 +7,33 @@ from djangoblog.utils import get_current_site # Create your models here. - +# 自定义用户模型,继承Django内置的AbstractUser class BlogUser(AbstractUser): + # 用户昵称(可选) nickname = models.CharField(_('nick name'), max_length=100, blank=True) + # 账号创建时间(默认当前时间) creation_time = models.DateTimeField(_('creation time'), default=now) + # 最后修改时间(默认当前时间) last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # 账号创建来源(如:网站注册/第三方登录等,可选) source = models.CharField(_('create source'), max_length=100, blank=True) - + # 获取用户详情页的绝对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 + site = get_current_site().domain# 获取当前站点域名 url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url - + # 元数据配置(模型级别的选项) class Meta: - ordering = ['-id'] - verbose_name = _('user') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-id'] # 默认按ID降序排列 + verbose_name = _('user') # 单数形式名称(后台显示) + verbose_name_plural = verbose_name# 复数形式名称(后台显示) + get_latest_by = 'id'# 指定最新记录的排序字段 diff --git a/src/django-master/accounts/tests.py b/src/django-master/accounts/tests.py index 6893411..503396e 100644 --- a/src/django-master/accounts/tests.py +++ b/src/django-master/accounts/tests.py @@ -10,86 +10,106 @@ from . import utils # Create your tests here. - +# 创建测试类(继承Django的TestCase) 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) + self.assertEqual(loginresult, True)# 验证登录成功 + # 测试访问管理员后台 response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) - + 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.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) + 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) - + 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() + # 创建分类 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" @@ -99,73 +119,76 @@ class AccountTest(TestCase): 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' + '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) - + 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) - + 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") + data=dict(email="admin@admin.com") # 使用正确邮箱格式 ) self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok") - + 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) + 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, + 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( @@ -173,12 +196,12 @@ class AccountTest(TestCase): ).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", + email="123@123.com",# 不存在的邮箱 code="123456", ) resp = self.client.post( @@ -186,22 +209,23 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 200) # 应返回错误页面而非重定向 + # 测试忘记密码重置功能 - 验证码错误情况 def test_forget_password_email_code_error(self): code = generate_code() - utils.set_code(self.blog_user.email, code) + utils.set_code(self.blog_user.email, code)# 设置正确验证码 + # 使用错误验证码提交 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111",# 错误验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 200)# 应返回错误页面而非重定向 diff --git a/src/django-master/accounts/urls.py b/src/django-master/accounts/urls.py index 107a801..4ab17e2 100644 --- a/src/django-master/accounts/urls.py +++ b/src/django-master/accounts/urls.py @@ -1,28 +1,37 @@ -from django.urls import path -from django.urls import re_path - -from . import views -from .forms import LoginForm - +# 导入 Django 的 URL 路由工具 +from django.urls import path# 用于简单路径匹配(如 'account/result.html') +from django.urls import re_path# 用于正则表达式路径匹配(如 '^login/$') +# 导入当前应用的视图和表单 +from . import views# 导入 views.py 中的所有视图类/函数 +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 路由列表 +urlpatterns = [ + # 登录视图(使用正则路径) + re_path(r'^login/$', # 正则匹配路径:以 login 结尾(斜杠必需) + views.LoginView.as_view(success_url='/'), # 使用 Django 内置 LoginView,登录成功后跳转到首页 + name='login',# URL 名称(用于模板或代码中反向解析) + kwargs={'authentication_form': LoginForm}# 传递额外参数:覆盖默认登录表单 + ), + # 注册视图 + re_path(r'^register/$', # 正则匹配路径:以 register 结尾 + views.RegisterView.as_view(success_url="/"),# 自定义注册视图,注册成功后跳转首页 + name='register'), # URL 名称 + # 登出视图 + re_path(r'^logout/$', # 正则匹配路径:以 logout 结尾 + views.LogoutView.as_view(),# 使用 Django 内置 LogoutView + name='logout'),# URL 名称 + # 账户结果页面(使用 path,非正则) + path(r'account/result.html',# 简单路径匹配(自动添加开头结尾的 ^$) + views.account_result,# 指向普通视图函数(非类视图) + name='result'),# URL 名称 + # 忘记密码视图 + re_path(r'^forget_password/$', # 正则匹配路径:以 forget_password 结尾 + views.ForgetPasswordView.as_view(),# 自定义忘记密码视图 + name='forget_password'),# URL 名称 + # 忘记密码验证码处理视图 + re_path(r'^forget_password_code/$',# 正则匹配路径:以 forget_password_code 结尾 + views.ForgetPasswordEmailCode.as_view(), # 自定义验证码处理视图 + name='forget_password_code'), # URL 名称 ] diff --git a/src/django-master/accounts/user_login_backend.py b/src/django-master/accounts/user_login_backend.py index 73cdca1..603e009 100644 --- a/src/django-master/accounts/user_login_backend.py +++ b/src/django-master/accounts/user_login_backend.py @@ -1,26 +1,57 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend +# 导入 Django 认证系统所需的模块 +from django.contrib.auth import get_user_model# 动态获取当前项目的 User 模型 +from django.contrib.auth.backends import ModelBackend# Django 默认的认证后端基类 class EmailOrUsernameModelBackend(ModelBackend): """ 允许使用用户名或邮箱登录 + 承自 Django 的 ModelBackend,重写 authenticate 和 get_user 方法。 """ def authenticate(self, request, username=None, password=None, **kwargs): + """ + 重写认证方法,支持用户名或邮箱登录。 + + 参数: + request: HttpRequest 对象(可能包含额外的认证上下文) + username: 用户输入的用户名或邮箱(前端传递的字段名固定为 username) + password: 用户输入的密码 + **kwargs: 其他可能的参数(如通过信号传递的额外参数) + + 返回: + 如果认证成功返回 User 对象,否则返回 None + """ + # 判断输入是邮箱还是用户名(通过 '@' 符号区分) if '@' in username: + # 如果是邮箱,设置查询条件为 email 字段 kwargs = {'email': username} else: + # 否则按用户名查询 kwargs = {'username': username} try: + # 尝试从数据库获取用户(使用当前项目的自定义 User 模型) user = get_user_model().objects.get(**kwargs) + # 验证密码是否正确(使用 Django 的密码哈希校验) if user.check_password(password): - return user + return user# 认证成功返回用户对象 except get_user_model().DoesNotExist: + # 用户不存在时返回 None(Django 会继续尝试其他认证后端) return None def get_user(self, username): + """ + 根据用户 ID 获取用户对象(用于 Session 认证等场景)。 + + 参数: + username: 实际是用户的 primary key(通常由 Session 存储) + + 返回: + 找到用户返回 User 对象,否则返回 None + """ try: + # 通过主键查询用户(Django 默认行为) return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: + # 用户不存在时返回 None return None diff --git a/src/django-master/accounts/utils.py b/src/django-master/accounts/utils.py index 4b94bdf..811766a 100644 --- a/src/django-master/accounts/utils.py +++ b/src/django-master/accounts/utils.py @@ -1,12 +1,15 @@ -import typing -from datetime import timedelta +# 导入标准库模块 +import typing # 用于类型注解 +from datetime import timedelta # 用于处理时间间隔 -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 +# 导入 Django 核心组件 +from django.core.cache import cache # Django 缓存系统 +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) @@ -17,9 +20,11 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) subject: 邮件主题 code: 验证码 """ + # 构造邮件正文(使用国际化字符串,并插入动态验证码) 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) @@ -34,16 +39,42 @@ def verify(email: str, code: str) -> typing.Optional[str]: 这里的错误处理不太合理,应该采用raise抛出 否测调用方也需要对error进行处理 """ + # 从缓存获取该邮箱对应的正确验证码 cache_code = get_code(email) + # 比对用户输入的验证码和缓存中的验证码 if cache_code != code: - return gettext("Verification code error") + return gettext("Verification code error") # 返回翻译后的错误信息 + # 隐含逻辑:验证成功时返回 None(调用方需检查返回值是否为 None) def set_code(email: str, code: str): + """ + 将验证码存入缓存 + + Args: + email (str): 用户邮箱(作为缓存键) + code (str): 要存储的验证码 + + Note: + 验证码有效期由全局变量 _code_ttl 控制(5分钟) + """ + # 使用 Django 缓存设置键值对,并指定过期时间(转换为秒) """设置code""" cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: + """ + 从缓存获取验证码 + + Args: + email (str): 用户邮箱(缓存键) + + Returns: + typing.Optional[str]: + - 存在则返回验证码字符串 + - 不存在或过期返回 None + """ + # 直接调用 Django 缓存的 get 方法 """获取code""" return cache.get(email) diff --git a/src/django-master/accounts/views.py b/src/django-master/accounts/views.py index ae67aec..b168f68 100644 --- a/src/django-master/accounts/views.py +++ b/src/django-master/accounts/views.py @@ -1,59 +1,92 @@ +# 导入日志模块,用于记录运行时的信息和错误 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.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__) # 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(): - user = form.save(False) - user.is_active = False - user.source = 'Register' - user.save(True) + # 保存用户对象(但暂不激活 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 = """

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

@@ -64,92 +97,123 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + # 发送验证邮件 send_email( emailto=[ - user.email, + user.email, # 收件人列表 ], - title='验证您的电子邮箱', - content=content) - + 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 }) - +# 登出视图,继承自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 # 一个月的时间 - - @method_decorator(sensitive_post_parameters('password')) - @method_decorator(csrf_protect) - @method_decorator(never_cache) + # 使用多个装饰器装饰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 = ''' @@ -157,48 +221,69 @@ def account_result(request): ''' 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 }) 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") diff --git a/src/django-master/comments/apps.py b/src/django-master/comments/apps.py index 742dea1..182ec3c 100644 --- a/src/django-master/comments/apps.py +++ b/src/django-master/comments/apps.py @@ -9,6 +9,10 @@ class CommentsConfig(AppConfig): """ # 应用的唯一名称(必须与应用目录名一致),Django通过该值定位应用 name = 'comments' +<<<<<<< HEAD # 可选扩展配置(当前代码未实现,可根据需求添加): # - verbose_name:应用的人性化名称(如 verbose_name = "评论管理"),用于Admin后台显示 - # - default_auto_field:指定模型默认的主键类型(如 default_auto_field = "django.db.models.BigAutoField") \ No newline at end of file + # - default_auto_field:指定模型默认的主键类型(如 default_auto_field = "django.db.models.BigAutoField") +======= + +>>>>>>> ZYY_branch diff --git a/src/django-master/comments/views.py b/src/django-master/comments/views.py index 05540a8..895e200 100644 --- a/src/django-master/comments/views.py +++ b/src/django-master/comments/views.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Create your views here. # 导入Django核心模块、异常类、视图工具及项目内模型/表单 from django.core.exceptions import ValidationError # Django内置验证异常类,用于抛出自定义验证错误 @@ -6,6 +7,14 @@ from django.shortcuts import get_object_or_404 # 快捷查询:找到数据返 from django.utils.decorators import method_decorator # 用于给类视图的方法添加装饰器 from django.views.decorators.csrf import csrf_protect # CSRF保护装饰器:防止跨站请求伪造攻击 from django.views.generic.edit import FormView # 通用表单视图类:简化表单提交、验证、处理的逻辑 +======= +from django.core.exceptions import ValidationError +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_protect +from django.views.generic.edit import FormView +>>>>>>> ZYY_branch # 导入项目内关联模型和表单:用户、文章、评论表单、评论模型 from accounts.models import BlogUser