From 7e7ba6f5038dac1eeec3f471d36ea55f6af09666 Mon Sep 17 00:00:00 2001 From: dynastxu <151742029+ETOofficial@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:42:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?chore(submodule):=20=E6=B7=BB=E5=8A=A0=20Dj?= =?UTF-8?q?angoBlog=20=E5=AD=90=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 初始化 DjangoBlog 作为项目子模块 - 配置子模块路径为 src/DjangoBlog - 设置子模块远程仓库地址 --- .gitmodules | 3 +++ src/DjangoBlog | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 src/DjangoBlog diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f9999c2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/DjangoBlog"] + path = src/DjangoBlog + url = https://github.com/ETOofficial/DjangoBlog.git diff --git a/src/DjangoBlog b/src/DjangoBlog new file mode 160000 index 0000000..6854347 --- /dev/null +++ b/src/DjangoBlog @@ -0,0 +1 @@ +Subproject commit 6854347d35f6619d9e16852d0d6b133ca645c6c5 From 285ac07e419a4bd572be2de5001d031e2b344859 Mon Sep 17 00:00:00 2001 From: wei664 <3069318212@qq.com> Date: Sat, 8 Nov 2025 18:38:26 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DjangoBlog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DjangoBlog b/src/DjangoBlog index 6854347..df6020c 160000 --- a/src/DjangoBlog +++ b/src/DjangoBlog @@ -1 +1 @@ -Subproject commit 6854347d35f6619d9e16852d0d6b133ca645c6c5 +Subproject commit df6020cd44f39affee81f58088380b23a5fb0c96 From cf58b9ef184d3f6c4e022177819e8ac95a32a55e Mon Sep 17 00:00:00 2001 From: wei664 <3069318212@qq.com> Date: Sat, 8 Nov 2025 21:55:19 +0800 Subject: [PATCH 3/3] =?UTF-8?q?accounts=E4=BB=A3=E7=A0=81=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DjangoBlog/accounts/admin.py | 77 +++++--- src/DjangoBlog/accounts/apps.py | 6 + src/DjangoBlog/accounts/forms.py | 73 ++++++-- .../accounts/migrations/0001_initial.py | 68 ++++++-- ...s_remove_bloguser_created_time_and_more.py | 38 +++- src/DjangoBlog/accounts/models.py | 36 +++- src/DjangoBlog/accounts/tests.py | 136 ++++++++++----- src/DjangoBlog/accounts/urls.py | 60 ++++--- src/DjangoBlog/accounts/user_login_backend.py | 26 ++- src/DjangoBlog/accounts/utils.py | 16 +- src/DjangoBlog/accounts/views.py | 165 ++++++++++++------ 11 files changed, 505 insertions(+), 196 deletions(-) diff --git a/src/DjangoBlog/accounts/admin.py b/src/DjangoBlog/accounts/admin.py index 29d162a..46307bf 100644 --- a/src/DjangoBlog/accounts/admin.py +++ b/src/DjangoBlog/accounts/admin.py @@ -1,60 +1,87 @@ +#shw 导入Django的表单模块 from django import forms +#shw 导入Django后台管理的基础用户管理类 from django.contrib.auth.admin import UserAdmin +#shw 导入Django后台用于修改用户信息的表单 from django.contrib.auth.forms import UserChangeForm +#shw 导入Django后台用于用户名的字段类 from django.contrib.auth.forms import UsernameField +#shw 导入Django的国际化和翻译工具 from django.utils.translation import gettext_lazy as _ -# Register your models here. +#shw 注册你的模型到这里(这是一个注释提示,实际注册在文件末尾) from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): +#shw 自定义用户创建表单,用于在Django Admin后台添加新用户。 +#shw 它继承自 ModelForm,并增加了密码输入和确认的逻辑。 + + #shw 定义第一个密码字段,使用PasswordInput控件隐藏输入内容 password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) + #shw 定义第二个密码字段,用于确认密码 password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: - model = BlogUser - fields = ('email',) +#shw Meta类用于配置表单与模型的关联 + model = BlogUser #shw 指定该表单对应的模型是 BlogUser + fields = ('email',) #shw 在创建用户时,除了密码外,只显示邮箱字段 def clean_password2(self): - # Check that the two password entries match - password1 = self.cleaned_data.get("password1") - password2 = self.cleaned_data.get("password2") +#shw 自定义验证方法,用于检查两次输入的密码是否一致 + #shw Check that the two password entries match + password1 = self.cleaned_data.get("password1") #shw 从已清洗的数据中获取第一个密码 + password2 = self.cleaned_data.get("password2") #shw 从已清洗的数据中获取第二个密码 + #shw 如果两个密码都存在且不相等,则抛出验证错误 if password1 and password2 and password1 != password2: raise forms.ValidationError(_("passwords do not match")) - return password2 + return password2 #shw 返回第二个密码作为清洗后的数据 def save(self, commit=True): - # Save the provided password in hashed format - user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) +#shw 重写save方法,以便在保存用户时处理密码哈希 + #shw Save the provided password in hashed format + user = super().save(commit=False) #shw 调用父类的save方法,但先不提交到数据库(commit=False) + user.set_password(self.cleaned_data["password1"]) #shw 使用Django的set_password方法将明文密码加密后存储 if commit: - user.source = 'adminsite' - user.save() - return user + user.source = 'adminsite' #shw 如果决定提交,则设置用户的来源为 'adminsite' + user.save() #shw 将用户对象保存到数据库 + return user #shw 返回保存后的用户对象 class BlogUserChangeForm(UserChangeForm): +#shw 自定义用户修改表单,用于在Django Admin后台编辑现有用户信息。 +#shw 它继承自Django的UserChangeForm,以复用大部分功能。 + class Meta: - model = BlogUser - fields = '__all__' +#shw Meta类用于配置表单与模型的关联 + model = BlogUser #shw 指定该表单对应的模型是 BlogUser + fields = '__all__' #shw 在修改用户时,显示模型中的所有字段 + #shw 指定 'username' 字段使用的字段类为 UsernameField field_classes = {'username': UsernameField} def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +#shw 重写初始化方法,可以在这里添加自定义的初始化逻辑 + super().__init__(*args, **kwargs) #shw 调用父类的初始化方法 class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm +#shw 自定义用户管理类,用于在Django Admin后台定制 BlogUser 模型的显示和操作方式。 +#shw 它继承自Django的UserAdmin,以复用用户管理的标准功能。 + + form = BlogUserChangeForm #shw 指定修改用户时使用的表单 + add_form = BlogUserCreationForm #shw 指定添加用户时使用的表单 + #shw 定义在用户列表页面显示的列 list_display = ( - 'id', - 'nickname', - 'username', - 'email', - 'last_login', - 'date_joined', - 'source') + 'id', #shw 用户ID + 'nickname', #shw 昵称 + 'username', #shw 用户名 + 'email', #shw 邮箱 + 'last_login', #shw 最后登录时间 + 'date_joined', #shw 注册时间 + 'source') #shw 创建来源 + #shw 定义在列表页面中,哪些列可以点击进入编辑页面 list_display_links = ('id', 'username') + #shw 定义用户列表的默认排序方式,按ID降序排列 ordering = ('-id',) + #shw 定义搜索框可以搜索的字段 search_fields = ('username', 'nickname', 'email') diff --git a/src/DjangoBlog/accounts/apps.py b/src/DjangoBlog/accounts/apps.py index 9b3fc5a..3830729 100644 --- a/src/DjangoBlog/accounts/apps.py +++ b/src/DjangoBlog/accounts/apps.py @@ -1,5 +1,11 @@ +#shw 导入Django的应用配置基类 from django.apps import AppConfig class AccountsConfig(AppConfig): +#shw 这是accounts应用的配置类。 +#shw 它用于定义该应用的各种元数据和行为。 + + #shw 指定这个配置类所属的应用的完整Python路径。 + #shw Django通过这个name来找到并加载这个应用。 name = 'accounts' diff --git a/src/DjangoBlog/accounts/forms.py b/src/DjangoBlog/accounts/forms.py index fce4137..82be976 100644 --- a/src/DjangoBlog/accounts/forms.py +++ b/src/DjangoBlog/accounts/forms.py @@ -1,26 +1,45 @@ +#shw 导入Django的表单模块 from django import forms +#shw 导入获取当前激活用户模型的函数 from django.contrib.auth import get_user_model, password_validation +#shw 导入Django内置的认证表单和用户创建表单 from django.contrib.auth.forms import AuthenticationForm, UserCreationForm +#shw 导入Django的验证错误类 from django.core.exceptions import ValidationError +#shw 导入Django的表单控件模块 from django.forms import widgets +#shw 导入Django的国际化和翻译工具 from django.utils.translation import gettext_lazy as _ +#shw 导入本地的工具模块 from . import utils +#shw 导入本地的BlogUser模型 from .models import BlogUser class LoginForm(AuthenticationForm): +#shw 自定义登录表单,继承自Django的AuthenticationForm。 +#shw 主要用于自定义前端显示的样式。 + def __init__(self, *args, **kwargs): - super(LoginForm, self).__init__(*args, **kwargs) +#shw 重写初始化方法,以自定义字段的控件属性。 + super(LoginForm, self).__init__(*args, **kwargs) #shw 调用父类的初始化方法 + #shw 自定义 'username' 字段的控件,添加placeholder和CSS类 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) + #shw 自定义 'password' 字段的控件,添加placeholder和CSS类 self.fields['password'].widget = widgets.PasswordInput( attrs={'placeholder': "password", "class": "form-control"}) class RegisterForm(UserCreationForm): +#shw 自定义注册表单,继承自Django的UserCreationForm。 +#shw 增加了邮箱唯一性验证和前端样式自定义。 + def __init__(self, *args, **kwargs): - super(RegisterForm, self).__init__(*args, **kwargs) +#shw 重写初始化方法,以自定义字段的控件属性。 + super(RegisterForm, self).__init__(*args, **kwargs) #shw 调用父类的初始化方法 + #shw 为各个字段添加Bootstrap风格的CSS类和placeholder self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['email'].widget = widgets.EmailInput( @@ -31,17 +50,23 @@ class RegisterForm(UserCreationForm): attrs={'placeholder': "repeat password", "class": "form-control"}) def clean_email(self): - email = self.cleaned_data['email'] +#shw 自定义邮箱字段的验证方法,确保邮箱在系统中是唯一的。 + email = self.cleaned_data['email'] #shw 获取清洗后的邮箱数据 + #shw 检查数据库中是否已存在该邮箱 if get_user_model().objects.filter(email=email).exists(): - raise ValidationError(_("email already exists")) - return email + raise ValidationError(_("email already exists")) #shw 如果存在,抛出验证错误 + return email #shw 返回清洗后的邮箱 class Meta: - model = get_user_model() - fields = ("username", "email") +#shw Meta类用于配置表单与模型的关联 + model = get_user_model() #shw 动态获取用户模型,而不是硬编码BlogUser,更具可复用性 + fields = ("username", "email") #shw 指定注册表单中显示的字段 class ForgetPasswordForm(forms.Form): +#shw 忘记密码/重置密码表单,继承自基础的Form类。 +#shw 它不直接与模型关联,用于处理通过邮箱和验证码重置密码的流程。 + new_password1 = forms.CharField( label=_("New password"), widget=forms.PasswordInput( @@ -53,7 +78,7 @@ class ForgetPasswordForm(forms.Form): ) new_password2 = forms.CharField( - label="确认密码", + label="确认密码", #shw 这里使用了中文硬编码,建议使用 _("Confirm password") 以支持国际化 widget=forms.PasswordInput( attrs={ "class": "form-control", @@ -63,7 +88,7 @@ class ForgetPasswordForm(forms.Form): ) email = forms.EmailField( - label='邮箱', + label='邮箱', #shw 这里使用了中文硬编码,建议使用 _("Email") widget=forms.TextInput( attrs={ 'class': 'form-control', @@ -83,35 +108,47 @@ class ForgetPasswordForm(forms.Form): ) def clean_new_password2(self): - password1 = self.data.get("new_password1") - password2 = self.data.get("new_password2") +#shw 自定义验证方法,检查两次输入的新密码是否一致,并验证密码强度。 + password1 = self.data.get("new_password1") #shw 从原始数据中获取密码1 + password2 = self.data.get("new_password2") #shw 从原始数据中获取密码2 + #shw 检查两次密码是否一致 if password1 and password2 and password1 != password2: raise ValidationError(_("passwords do not match")) + #shw 使用Django内置的密码验证器来检查密码强度 password_validation.validate_password(password2) - return password2 + return password2 #shw 返回验证通过的新密码 def clean_email(self): - user_email = self.cleaned_data.get("email") +#shw 自定义验证方法,检查输入的邮箱是否存在于数据库中。 + user_email = self.cleaned_data.get("email") #shw 获取清洗后的邮箱 + #shw 检查该邮箱是否已注册 if not BlogUser.objects.filter( email=user_email ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 + #shwtodo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 + #shw 这是一个安全提示,直接告诉攻击者邮箱未注册可能会被利用。 raise ValidationError(_("email does not exist")) - return user_email + return user_email #shw 返回清洗后的邮箱 def clean_code(self): - code = self.cleaned_data.get("code") +#shw 自定义验证方法,验证邮箱验证码是否正确。 + code = self.cleaned_data.get("code") #shw 获取清洗后的验证码 + #shw 调用工具函数验证邮箱和验证码是否匹配 error = utils.verify( email=self.cleaned_data.get("email"), code=code, ) + #shw 如果工具函数返回错误信息,则抛出验证错误 if error: raise ValidationError(error) - return code + return code #shw 返回验证通过的验证码 class ForgetPasswordCodeForm(forms.Form): +#shw 发送忘记密码验证码的表单。 +#shw 它只包含一个邮箱字段,用于用户输入接收验证码的邮箱地址。 + email = forms.EmailField( - label=_('Email'), + label=_('Email'), #shw 邮箱字段,标签支持国际化 ) diff --git a/src/DjangoBlog/accounts/migrations/0001_initial.py b/src/DjangoBlog/accounts/migrations/0001_initial.py index d2fbcab..91bd2e7 100644 --- a/src/DjangoBlog/accounts/migrations/0001_initial.py +++ b/src/DjangoBlog/accounts/migrations/0001_initial.py @@ -1,47 +1,91 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 +# shw Generated by Django 4.1.7 on 2023-03-02 07:14 +# shw 这是Django自动生成的迁移文件,用于记录数据库模型的变更。 +# shw 生成时间:2023年3月2日 07:14 +# shw Django版本:4.1.7 +# shw 导入Django内置的认证模型和验证器,因为我们的BlogUser模型继承了它们 import django.contrib.auth.models import django.contrib.auth.validators +# shw 导入Django数据库迁移和模型的核心模块 from django.db import migrations, models +# shw 导入Django的时区工具,用于设置默认的当前时间 import django.utils.timezone class Migration(migrations.Migration): + # shw 这是accounts应用的初始迁移类。 + # shw 它负责在数据库中创建 BlogUser 数据表。 + # shw 标记这是该应用的第一次迁移 initial = True + # shw 声明此迁移的依赖关系。 + # shw 它依赖于Django内置的 'auth' 应用的 '0012_alter_user_first_name_max_length' 迁移。 + # shw 这确保了在执行我们的迁移之前,Django内置的用户表结构已经更新到了指定版本。 dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), ] + # shw 定义此迁移要执行的具体操作列表 operations = [ migrations.CreateModel( + # shw 操作名称:创建一个名为 'BlogUser' 的新模型(对应数据库中的表) name='BlogUser', + + # shw 定义 'BlogUser' 模型的所有字段 fields=[ + # shw 以下是继承自 AbstractUser 的标准字段 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('is_superuser', models.BooleanField(default=False, + help_text='Designates that this user has all permissions without explicitly assigning them.', + verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, + help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', + max_length=150, unique=True, + validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], + verbose_name='username')), ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('is_staff', models.BooleanField(default=False, + help_text='Designates whether the user can log into this admin site.', + verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, + help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', + verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), + + # shw --- 以下是 BlogUser 模型自定义的扩展字段 --- + ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), # shw 用户的昵称,可为空 ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + # shw 记录用户创建的时间戳 ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), + # shw 记录用户信息最后修改的时间戳 ('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + # shw 记录用户的注册来源(如:'website', 'wechat') + + # shw 以下是多对多关系字段,用于权限和分组 + ('groups', models.ManyToManyField(blank=True, + help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', + related_name='user_set', related_query_name='user', to='auth.group', + verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', + related_name='user_set', related_query_name='user', + to='auth.permission', verbose_name='user permissions')), ], + + # shw 定义模型的元数据选项 options={ - 'verbose_name': '用户', - 'verbose_name_plural': '用户', - 'ordering': ['-id'], - 'get_latest_by': 'id', + 'verbose_name': '用户', # shw 在Django Admin中显示的单数名称 + 'verbose_name_plural': '用户', # shw 在Django Admin中显示的复数名称 + 'ordering': ['-id'], # shw 默认的排序方式,按ID降序排列(最新的在前) + 'get_latest_by': 'id', # shw 当使用 .latest() 方法时,默认按 'id' 字段查找 }, + + # shw 为模型指定自定义的管理器 + # shw 这里使用了Django默认的 UserManager,因为它提供了创建用户和管理用户的标准方法 managers=[ ('objects', django.contrib.auth.models.UserManager()), ], diff --git a/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py index 1a9f509..b6882d2 100644 --- a/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ b/src/DjangoBlog/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py @@ -1,46 +1,82 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 +#shw Generated by Django 4.2.5 on 2023-09-06 13:13 +#shw 这是Django自动生成的迁移文件,用于记录对现有模型的修改。 +#shw 生成时间:2023年9月6日 13:13 +#shw Django版本:4.2.5 +#shw 导入Django数据库迁移和模型的核心模块 from django.db import migrations, models +#shw 导入Django的时区工具,用于设置默认的当前时间 import django.utils.timezone class Migration(migrations.Migration): +#shw 这是accounts应用的第二次迁移类。 +#shw 它修改了 BlogUser 模型的元数据、字段和字段属性。 + #shw 声明此迁移的依赖关系。 + #shw 它依赖于该应用的第一次迁移 '0001_initial'。 + #shw 这确保了在执行此迁移之前,BlogUser 表已经被创建。 dependencies = [ ('accounts', '0001_initial'), ] + #shw 定义此迁移要执行的具体操作列表 operations = [ + #shw 操作1: 修改模型的元数据选项 migrations.AlterModelOptions( + #shw 指定要修改的模型名 name='bloguser', + #shw 更新 Meta 类中的选项 options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, ), + #shw 操作2: 删除一个字段 migrations.RemoveField( + #shw 指定模型 model_name='bloguser', + #shw 指定要删除的字段名 name='created_time', ), + #shw 操作3: 删除另一个字段 migrations.RemoveField( + #shw 指定模型 model_name='bloguser', + #shw 指定要删除的字段名 name='last_mod_time', ), + #shw 操作4: 添加一个新字段 migrations.AddField( + #shw 指定模型 model_name='bloguser', + #shw 新字段名 name='creation_time', + #shw 定义新字段的类型和属性 field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #shw 操作5: 添加另一个新字段 migrations.AddField( + #shw 指定模型 model_name='bloguser', + #shw 新字段名 name='last_modify_time', + #shw 定义新字段的类型和属性 field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), ), + #shw 操作6: 修改现有字段的属性 migrations.AlterField( + #shw 指定模型 model_name='bloguser', + #shw 指定要修改的字段 name='nickname', + #shw 重新定义该字段的属性(这里只修改了 verbose_name) field=models.CharField(blank=True, max_length=100, verbose_name='nick name'), ), + #shw 操作7: 修改另一个现有字段的属性 migrations.AlterField( + #shw 指定模型 model_name='bloguser', + #shw 指定要修改的字段 name='source', + #shw 重新定义该字段的属性(这里只修改了 verbose_name) field=models.CharField(blank=True, max_length=100, verbose_name='create source'), ), ] diff --git a/src/DjangoBlog/accounts/models.py b/src/DjangoBlog/accounts/models.py index 3baddbb..2816474 100644 --- a/src/DjangoBlog/accounts/models.py +++ b/src/DjangoBlog/accounts/models.py @@ -1,35 +1,55 @@ +#shw 导入Django内置的抽象用户模型基类 from django.contrib.auth.models import AbstractUser +#shw 导入Django的数据库模型模块 from django.db import models +#shw 导入Django的URL反向解析函数 from django.urls import reverse +#shw 导入Django的时区工具,用于获取当前时间 from django.utils.timezone import now +#shw 导入Django的国际化和翻译工具 from django.utils.translation import gettext_lazy as _ +#shw 从项目工具模块中导入获取当前站点的函数 from djangoblog.utils import get_current_site -# Create your models here. +#shw 在这里创建你的模型。 class BlogUser(AbstractUser): +#shw 自定义用户模型,继承自Django的AbstractUser。 +#shw 它扩展了默认用户模型,增加了博客系统所需的额外字段。 + + #shw 用户昵称字段,可为空 nickname = models.CharField(_('nick name'), max_length=100, blank=True) + #shw 用户创建时间字段,默认为当前时间 creation_time = models.DateTimeField(_('creation time'), default=now) + #shw 用户最后修改时间字段,默认为当前时间 last_modify_time = models.DateTimeField(_('last modify time'), default=now) + #shw 用户创建来源字段(如:'adminsite', 'register'),可为空 source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): +#shw 定义获取用户详情页绝对路径的方法。 +#shw Django Admin和其他地方会使用这个方法来获取对象的URL。 return reverse( 'blog:author_detail', kwargs={ - 'author_name': self.username}) + 'author_name': self.username}) #shw 反向解析到博客应用的作者详情页,URL参数为用户名 def __str__(self): - return self.email +#shw 定义对象的字符串表示形式。 +#shw 在Django Admin或打印对象时会显示这个字符串。 + return self.email #shw 返回用户的邮箱作为其字符串表示 def get_full_url(self): - site = get_current_site().domain +#shw 定义获取用户详情页完整URL(包含域名)的方法。 + site = get_current_site().domain #shw 获取当前站点的域名 + #shw 拼接协议、域名和绝对路径,形成完整的URL 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' +#shw Meta类用于定义模型的元数据选项。 + ordering = ['-id'] #shw 默认按ID降序排列 + verbose_name = _('user') #shw 在Django Admin中显示的单数名称,支持国际化 + verbose_name_plural = verbose_name #shw 在Django Admin中显示的复数名称 + get_latest_by = 'id' #shw 当使用 .latest() 方法时,默认按 'id' 字段查找 diff --git a/src/DjangoBlog/accounts/tests.py b/src/DjangoBlog/accounts/tests.py index 6893411..8512d16 100644 --- a/src/DjangoBlog/accounts/tests.py +++ b/src/DjangoBlog/accounts/tests.py @@ -1,48 +1,67 @@ +#shw 导入Django的测试客户端、请求工厂和测试用例基类 from django.test import Client, RequestFactory, TestCase +#shw 导入Django的URL反向解析函数 from django.urls import reverse +#shw 导入Django的时区工具 from django.utils import timezone +#shw 导入Django的国际化和翻译工具 from django.utils.translation import gettext_lazy as _ +#shw 导入本地的BlogUser模型 from accounts.models import BlogUser +#shw 导入博客应用的Article和Category模型 from blog.models import Article, Category +#shw 从项目工具模块导入所有函数 from djangoblog.utils import * +#shw 导入本地的工具模块 from . import utils -# Create your tests here. +#shw 在这里创建你的测试。 class AccountTest(TestCase): +#shw 账户应用的测试用例集,继承自Django的TestCase。 +#shw TestCase提供了数据库事务回滚和客户端模拟等功能。 + def setUp(self): - self.client = Client() - self.factory = RequestFactory() +#shw 每个测试方法执行前都会运行的初始化方法。 +#shw 用于创建测试所需的公共数据和环境。 + self.client = Client() #shw 创建一个模拟的HTTP客户端,用于发送请求 + self.factory = RequestFactory() #shw 创建一个请求工厂,用于生成请求对象 + #shw 创建一个普通用户用于测试 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", password="12345678" ) - self.new_test = "xxx123--=" + self.new_test = "xxx123--=" #shw 定义一个测试用的新密码 def test_validate_account(self): - site = get_current_site().domain +#shw 测试超级用户的创建、登录和后台访问权限。 + site = get_current_site().domain #shw 获取当前站点域名 + #shw 创建一个超级用户 user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="qwer!@#$ggg") - testuser = BlogUser.objects.get(username='liangliangyy1') + testuser = BlogUser.objects.get(username='liangliangyy1') #shw 从数据库中获取刚创建的超级用户 + #shw 使用client模拟登录 loginresult = self.client.login( username='liangliangyy1', password='qwer!@#$ggg') - self.assertEqual(loginresult, True) - response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) + self.assertEqual(loginresult, True) #shw 断言登录成功 + response = self.client.get('/admin/') #shw 模拟访问后台管理页面 + self.assertEqual(response.status_code, 200) #shw 断言访问成功,状态码为200 + #shw 创建一个文章分类用于测试 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() + #shw 创建一篇文章用于测试 article = Article() article.title = "nicetitleaaa" article.body = "nicecontentaaa" @@ -52,38 +71,48 @@ class AccountTest(TestCase): article.status = 'p' article.save() + #shw 模拟访问文章的后台编辑页面 response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) #shw 断言访问成功 def test_validate_register(self): +#shw 测试用户注册、邮箱验证、登录、登出等一系列流程。 + #shw 断言注册前,数据库中不存在该邮箱的用户 self.assertEquals( 0, len( BlogUser.objects.filter( email='user123@user.com'))) + #shw 模拟发送POST请求到注册页面 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', }) + #shw 断言注册后,数据库中存在该邮箱的用户 self.assertEquals( 1, len( BlogUser.objects.filter( email='user123@user.com'))) - user = BlogUser.objects.filter(email='user123@user.com')[0] + user = BlogUser.objects.filter(email='user123@user.com')[0] #shw 获取新注册的用户 + #shw 生成用于邮箱验证的签名 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - path = reverse('accounts:result') + path = reverse('accounts:result') #shw 获取验证结果页面的URL路径 + #shw 构造完整的验证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) + response = self.client.get(url) #shw 模拟用户点击邮箱中的验证链接 + self.assertEqual(response.status_code, 200) #shw 断言访问成功 + #shw 模拟用户登录 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 = BlogUser.objects.filter(email='user123@user.com')[0] #shw 重新获取用户对象 + user.is_superuser = True #shw 将用户提升为超级用户,以便访问后台 user.is_staff = True user.save() - delete_sidebar_cache() + delete_sidebar_cache() #shw 删除侧边栏缓存 + + #shw 创建测试用的分类和文章 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() @@ -95,66 +124,79 @@ class AccountTest(TestCase): article.title = "nicetitle333" article.body = "nicecontentttt" article.author = user - article.type = 'a' article.status = 'p' article.save() + #shw 登录状态下访问文章后台页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) + #shw 模拟用户登出 response = self.client.get(reverse('account:logout')) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #shw 断言登出成功(重定向或OK) + #shw 登出后再次访问文章后台页面 response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #shw 断言被重定向到登录页 + #shw 模拟使用错误的密码登录 response = self.client.post(reverse('account:login'), { 'username': 'user1233', - 'password': 'password123' + 'password': 'password123' #shw 错误的密码 }) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #shw 断言登录失败,页面重定向 + #shw 登录失败后访问文章后台页面 response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) #shw 断言依然无法访问 def test_verify_email_code(self): +#shw 测试邮箱验证码的生成、发送和验证功能。 to_email = "admin@admin.com" - code = generate_code() - utils.set_code(to_email, code) - utils.send_verify_email(to_email, code) + code = generate_code() #shw 生成一个验证码 + utils.set_code(to_email, code) #shw 将验证码与邮箱关联(通常是存入缓存或数据库) + utils.send_verify_email(to_email, code) #shw 发送验证码邮件(测试环境中可能不会真的发送) + #shw 使用正确的邮箱和验证码进行验证 err = utils.verify("admin@admin.com", code) - self.assertEqual(err, None) + self.assertEqual(err, None) #shw 断言验证成功,无错误信息返回 + #shw 使用错误的邮箱进行验证 err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) + self.assertEqual(type(err), str) #shw 断言验证失败,返回一个字符串类型的错误信息 def test_forget_password_email_code_success(self): +#shw 测试成功发送忘记密码验证码的场景。 resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict(email="admin@admin.com") + data=dict(email="admin@admin.com") #shw 使用一个已存在的邮箱 ) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok") + self.assertEqual(resp.status_code, 200) #shw 断言请求成功 + self.assertEqual(resp.content.decode("utf-8"), "ok") #shw 断言返回内容为"ok" def test_forget_password_email_code_fail(self): +#shw 测试发送忘记密码验证码失败的场景(如邮箱格式错误)。 + #shw 测试不提供邮箱的情况 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict() ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #shw 断言返回错误提示 + #shw 测试提供格式错误的邮箱的情况 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@com") ) - self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") #shw 断言返回错误提示 def test_forget_password_email_success(self): - code = generate_code() - utils.set_code(self.blog_user.email, code) +#shw 测试成功重置密码的场景。 + code = generate_code() #shw 生成一个验证码 + utils.set_code(self.blog_user.email, code) #shw 为测试用户设置验证码 + #shw 构造重置密码的请求数据 data = dict( new_password1=self.new_test, new_password2=self.new_test, @@ -165,20 +207,21 @@ class AccountTest(TestCase): path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.status_code, 302) #shw 断言请求成功并重定向 - # 验证用户密码是否修改成功 + #shw 验证用户密码是否真的被修改了 blog_user = BlogUser.objects.filter( email=self.blog_user.email, ).first() # type: BlogUser - self.assertNotEqual(blog_user, None) - self.assertEqual(blog_user.check_password(data["new_password1"]), True) + self.assertNotEqual(blog_user, None) #shw 断言用户依然存在 + self.assertEqual(blog_user.check_password(data["new_password1"]), True) #shw 断言新密码是正确的 def test_forget_password_email_not_user(self): +#shw 测试重置一个不存在用户的密码的场景。 data = dict( new_password1=self.new_test, new_password2=self.new_test, - email="123@123.com", + email="123@123.com", #shw 使用一个不存在的邮箱 code="123456", ) resp = self.client.post( @@ -186,22 +229,23 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 200) #shw 断言请求未重定向,停留在原页面并显示错误 def test_forget_password_email_code_error(self): - code = generate_code() - utils.set_code(self.blog_user.email, code) +#shw 测试使用错误验证码重置密码的场景。 + code = generate_code() #shw 生成一个验证码 + utils.set_code(self.blog_user.email, code) #shw 为测试用户设置验证码 + #shw 构造重置密码的请求数据,但验证码是错误的 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111", #shw 错误的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 200) #shw 断言请求未重定向,停留在原页面并显示错误 diff --git a/src/DjangoBlog/accounts/urls.py b/src/DjangoBlog/accounts/urls.py index 107a801..d673930 100644 --- a/src/DjangoBlog/accounts/urls.py +++ b/src/DjangoBlog/accounts/urls.py @@ -1,28 +1,44 @@ -from django.urls import path -from django.urls import re_path +#shw 导入Django的path和re_path函数,用于定义URL路由 +from django.urls import path, re_path +#shw 导入本地的视图模块 from . import views +#shw 导入本地的LoginForm表单,用于传递给登录视图 from .forms import LoginForm +#shw 定义应用的命名空间,用于在模板中反向解析URL,如 {% url 'accounts:login' %} 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'), - ] +#shw 定义URL模式列表 +urlpatterns = [ + #shw 定义登录页面的URL路由 + re_path(r'^login/$', + views.LoginView.as_view(success_url='/'), #shw 关联到LoginView类视图,并指定登录成功后重定向到根路径 + name='login', #shw 为这个URL模式命名为 'login' + kwargs={'authentication_form': LoginForm}), #shw 向LoginView传递额外的关键字参数,指定使用自定义的LoginForm + + #shw 定义注册页面的URL路由 + re_path(r'^register/$', + views.RegisterView.as_view(success_url="/"), #shw 关联到RegisterView类视图,并指定注册成功后重定向到根路径 + name='register'), #shw 命名为 'register' + + #shw 定义登出页面的URL路由 + re_path(r'^logout/$', + views.LogoutView.as_view(), #shw 关联到LogoutView类视图 + name='logout'), #shw 命名为 'logout' + + #shw 定义注册/操作结果页面的URL路由 + path(r'account/result.html', + views.account_result, #shw 关联到account_result函数视图 + name='result'), #shw 命名为 'result' + + #shw 定义忘记密码页面的URL路由(用于输入新密码和验证码) + re_path(r'^forget_password/$', + views.ForgetPasswordView.as_view(), #shw 关联到ForgetPasswordView类视图 + name='forget_password'), #shw 命名为 'forget_password' + + #shw 定义发送忘记密码验证码的URL路由 + re_path(r'^forget_password_code/$', + views.ForgetPasswordEmailCode.as_view(), #shw 关联到ForgetPasswordEmailCode类视图 + name='forget_password_code'), #shw 命名为 'forget_password_code' +] diff --git a/src/DjangoBlog/accounts/user_login_backend.py b/src/DjangoBlog/accounts/user_login_backend.py index 73cdca1..497536a 100644 --- a/src/DjangoBlog/accounts/user_login_backend.py +++ b/src/DjangoBlog/accounts/user_login_backend.py @@ -1,4 +1,6 @@ +#shw 导入Django的获取用户模型函数 from django.contrib.auth import get_user_model +#shw 导入Django默认的基于模型的认证后端基类 from django.contrib.auth.backends import ModelBackend @@ -6,21 +8,33 @@ class EmailOrUsernameModelBackend(ModelBackend): """ 允许使用用户名或邮箱登录 """ +#shw 自定义认证后端,继承自ModelBackend。 +#shw 它扩展了Django默认的认证功能,使用户既可以使用用户名,也可以使用邮箱进行登录。 def authenticate(self, request, username=None, password=None, **kwargs): +#shw 重写authenticate方法,这是认证的核心逻辑。 +#shw Django的login视图会调用这个方法来验证用户身份。 + #shw 判断用户输入的 'username' 字段是否包含 '@' 符号,以此来区分是邮箱还是用户名 if '@' in username: - kwargs = {'email': username} + kwargs = {'email': username} #shw 如果是邮箱,则设置查询条件为 email else: - kwargs = {'username': username} + kwargs = {'username': username} #shw 如果是用户名,则设置查询条件为 username try: + #shw 使用动态构建的查询条件(email或username)去数据库中查找用户 user = get_user_model().objects.get(**kwargs) + #shw 如果找到了用户,则调用 check_password 方法来验证密码是否正确 if user.check_password(password): - return user + return user #shw 密码正确,返回用户对象,认证成功 except get_user_model().DoesNotExist: - return None + #shw 如果根据email或username找不到用户,捕获异常 + return None #shw 返回None,表示认证失败 def get_user(self, username): +#shw 重写get_user方法。 +#shw Django的认证中间件会在每个请求中调用此方法,根据session中的user_id来获取用户对象。 try: - return get_user_model().objects.get(pk=username) + #shw 注意:这里的参数名是username,但实际传入的是用户的主键(pk),通常是ID + return get_user_model().objects.get(pk=username) #shw 根据主键(pk)查找用户 except get_user_model().DoesNotExist: - return None + #shw 如果根据主键找不到用户,捕获异常 + return None #shw 返回None diff --git a/src/DjangoBlog/accounts/utils.py b/src/DjangoBlog/accounts/utils.py index 4b94bdf..f8372d8 100644 --- a/src/DjangoBlog/accounts/utils.py +++ b/src/DjangoBlog/accounts/utils.py @@ -1,12 +1,19 @@ +#shw 导入类型提示模块,用于增强代码可读性和健壮性 import typing +#shw 导入时间间隔类,用于定义验证码有效期 from datetime import timedelta +#shw 导入Django的缓存模块 from django.core.cache import cache +#shw 导入Django的翻译函数 from django.utils.translation import gettext +#shw 导入Django的延迟翻译函数,用于在类或模型定义等场景 from django.utils.translation import gettext_lazy as _ +#shw 从项目工具模块导入发送邮件的函数 from djangoblog.utils import send_email +#shw 定义验证码的生存时间(Time To Live)为5分钟 _code_ttl = timedelta(minutes=5) @@ -17,9 +24,11 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) subject: 邮件主题 code: 验证码 """ + #shw 构造邮件的HTML内容,使用国际化字符串,并将验证码动态插入 html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " "properly") % {'code': code} + #shw 调用项目通用的邮件发送函数来完成发送 send_email([to_mail], subject, html_content) @@ -32,18 +41,23 @@ def verify(email: str, code: str) -> typing.Optional[str]: 如果有错误就返回错误str Node: 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + 否测调用方就需要对error进行处理 """ + #shw 从缓存中获取指定邮箱对应的验证码 cache_code = get_code(email) + #shw 比较用户输入的验证码和缓存中的验证码是否一致 if cache_code != code: + #shw 如果不一致,返回一个翻译后的错误信息字符串 return gettext("Verification code error") def set_code(email: str, code: str): """设置code""" + #shw 将验证码存入缓存,键为邮箱,值为验证码,并设置5分钟的过期时间 cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: """获取code""" + #shw 从缓存中根据邮箱(键)获取验证码(值) return cache.get(email) diff --git a/src/DjangoBlog/accounts/views.py b/src/DjangoBlog/accounts/views.py index ae67aec..6134e07 100644 --- a/src/DjangoBlog/accounts/views.py +++ b/src/DjangoBlog/accounts/views.py @@ -1,59 +1,86 @@ +#shw 导入日志模块 import logging +#shw 导入Django的延迟翻译函数 from django.utils.translation import gettext_lazy as _ +#shw 导入Django的项目设置 from django.conf import settings +#shw 导入Django的认证模块 from django.contrib import auth +#shw 导入登录后重定向字段的常量名 from django.contrib.auth import REDIRECT_FIELD_NAME +#shw 导入获取用户模型的函数 from django.contrib.auth import get_user_model +#shw 导入登出函数 from django.contrib.auth import logout +#shw 导入Django内置的认证表单 from django.contrib.auth.forms import AuthenticationForm +#shw 导入密码哈希生成函数 from django.contrib.auth.hashers import make_password +#shw 导入HTTP响应相关类 from django.http import HttpResponseRedirect, HttpResponseForbidden from django.http.request import HttpRequest from django.http.response import HttpResponse +#shw 导入Django的快捷函数 from django.shortcuts import get_object_or_404 from django.shortcuts import render +#shw 导入URL反向解析函数 from django.urls import reverse +#shw 导入方法装饰器 from django.utils.decorators import method_decorator +#shw 导入URL安全检查函数 from django.utils.http import url_has_allowed_host_and_scheme +#shw 导入Django的视图基类 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 +#shw 从项目工具模块导入所需函数 from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache +#shw 导入本地的工具模块和表单 from . import utils from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm +#shw 导入本地的模型 from .models import BlogUser +#shw 获取当前模块的日志记录器 logger = logging.getLogger(__name__) -# Create your views here. +#shw 在这里创建你的视图。 class RegisterView(FormView): - form_class = RegisterForm - template_name = 'account/registration_form.html' +#shw 用户注册视图,继承自FormView,用于处理用户注册逻辑。 + form_class = RegisterForm #shw 指定使用的表单类 + template_name = 'account/registration_form.html' #shw 指定渲染的模板 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): +#shw 为视图的dispatch方法添加CSRF保护 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 +#shw 当表单验证通过时执行此方法 + if form.is_valid(): #shw 再次确认表单有效 + user = form.save(False) #shw 保存表单数据,但先不提交到数据库(commit=False) + user.is_active = False #shw 将用户状态设为未激活,需要邮箱验证 + user.source = 'Register' #shw 设置用户来源为注册 + user.save(True) #shw 现在将用户对象保存到数据库 + + site = get_current_site().domain #shw 获取当前站点域名 + #shw 生成用于邮箱验证的双重哈希签名 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) + #shw 如果是调试模式,则使用本地地址 if settings.DEBUG: site = '127.0.0.1:8000' - path = reverse('account:result') + path = reverse('account:result') #shw 获取结果页面的URL路径 + #shw 构造完整的邮箱验证链接 url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( site=site, path=path, id=user.id, sign=sign) + #shw 构造邮件内容 content = """
请点击下面链接验证您的邮箱
@@ -64,6 +91,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + #shw 发送验证邮件 send_email( emailto=[ user.email, @@ -71,134 +99,157 @@ class RegisterView(FormView): title='验证您的电子邮箱', content=content) + #shw 构造注册成功后的跳转URL,提示用户去查收邮件 url = reverse('accounts:result') + \ '?type=register&id=' + str(user.id) - return HttpResponseRedirect(url) + return HttpResponseRedirect(url) #shw 重定向到结果页面 else: + #shw 如果表单无效,重新渲染注册页面并显示错误 return self.render_to_response({ 'form': form }) class LogoutView(RedirectView): - url = '/login/' +#shw 用户登出视图,继承自RedirectView,用于处理用户登出逻辑。 + url = '/login/' #shw 登出后重定向的URL @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): +#shw 为视图添加never_cache装饰器,确保该页面不被缓存 return super(LogoutView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): - logout(request) - delete_sidebar_cache() - return super(LogoutView, self).get(request, *args, **kwargs) + logout(request) #shw 调用Django的logout函数,清除session信息 + delete_sidebar_cache() #shw 删除侧边栏缓存 + return super(LogoutView, self).get(request, *args, **kwargs) #shw 执行重定向 class LoginView(FormView): - form_class = LoginForm - template_name = 'account/login.html' - success_url = '/' - redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 +#shw 用户登录视图,继承自FormView,用于处理用户登录逻辑。 + form_class = LoginForm #shw 指定使用的表单类 + template_name = 'account/login.html' #shw 指定渲染的模板 + success_url = '/' #shw 登录成功后默认的重定向URL + redirect_field_name = REDIRECT_FIELD_NAME #shw 指定包含重定向URL的GET参数名 + login_ttl = 2626560 # 一个月的时间(秒),用于“记住我”功能的session过期时间 @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - +#shw 为视图添加多个装饰器:保护密码参数、CSRF保护、禁止缓存 return super(LoginView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - redirect_to = self.request.GET.get(self.redirect_field_name) +#shw 向模板上下文中添加额外的数据 + redirect_to = self.request.GET.get(self.redirect_field_name) #shw 获取GET参数中的重定向URL if redirect_to is None: - redirect_to = '/' - kwargs['redirect_to'] = redirect_to + redirect_to = '/' #shw 如果没有,则默认为根路径 + kwargs['redirect_to'] = redirect_to #shw 将重定向URL添加到上下文 return super(LoginView, self).get_context_data(**kwargs) def form_valid(self, form): +#shw 当表单验证通过时执行此方法 + #shw 使用Django内置的AuthenticationForm再次验证,因为它会调用自定义的认证后端 form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): - delete_sidebar_cache() - logger.info(self.redirect_field_name) + delete_sidebar_cache() #shw 删除侧边栏缓存 + logger.info(self.redirect_field_name) #shw 记录日志 - auth.login(self.request, form.get_user()) + auth.login(self.request, form.get_user()) #shw 调用Django的login函数,将用户信息存入session + #shw 如果用户勾选了“记住我” if self.request.POST.get("remember"): - self.request.session.set_expiry(self.login_ttl) - return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') + self.request.session.set_expiry(self.login_ttl) #shw 设置session的过期时间为一个月 + return super(LoginView, self).form_valid(form) #shw 调用父类方法,处理重定向 else: + #shw 如果验证失败,重新渲染登录页面并显示错误 return self.render_to_response({ 'form': form }) def get_success_url(self): - - redirect_to = self.request.POST.get(self.redirect_field_name) +#shw 获取登录成功后应重定向的URL + redirect_to = self.request.POST.get(self.redirect_field_name) #shw 从POST数据中获取重定向URL + #shw 检查URL是否安全,防止开放重定向攻击 if not url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts=[ self.request.get_host()]): - redirect_to = self.success_url + redirect_to = self.success_url #shw 如果URL不安全,则使用默认的success_url return redirect_to def account_result(request): - type = request.GET.get('type') - id = request.GET.get('id') +#shw 函数视图,用于处理注册和邮箱验证的结果展示。 + type = request.GET.get('type') #shw 获取URL参数中的类型 + id = request.GET.get('id') #shw 获取URL参数中的用户ID - user = get_object_or_404(get_user_model(), id=id) - logger.info(type) - if user.is_active: + user = get_object_or_404(get_user_model(), id=id) #shw 根据ID获取用户对象,如果不存在则返回404 + logger.info(type) #shw 记录日志 + if user.is_active: #shw 如果用户已经激活,则直接跳转到首页 return HttpResponseRedirect('/') + #shw 处理两种类型:注册成功提示和邮箱验证 if type and type in ['register', 'validation']: if type == 'register': + #shw 如果是注册类型,显示注册成功、请查收邮件的提示 content = ''' 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 ''' title = '注册成功' else: - c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) - sign = request.GET.get('sign') - if sign != c_sign: + #shw 如果是验证类型,需要验证签名 + c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) #shw 重新计算正确的签名 + sign = request.GET.get('sign') #shw 获取URL中的签名 + if sign != c_sign: #shw 比较签名,如果不一致则返回403禁止访问 return HttpResponseForbidden() - user.is_active = True - user.save() + user.is_active = True #shw 激活用户 + user.save() #shw 保存用户状态 + #shw 显示验证成功的提示 content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' + #shw 渲染结果页面 return render(request, 'account/result.html', { 'title': title, 'content': content }) else: + #shw 如果类型不匹配,则跳转到首页 return HttpResponseRedirect('/') class ForgetPasswordView(FormView): - form_class = ForgetPasswordForm - template_name = 'account/forget_password.html' +#shw 忘记密码视图,用于处理通过验证码重置密码的逻辑。 + form_class = ForgetPasswordForm #shw 指定使用的表单 + template_name = 'account/forget_password.html' #shw 指定渲染的模板 def form_valid(self, form): - if form.is_valid(): +#shw 当表单验证通过时执行此方法 + if form.is_valid(): #shw 再次确认表单有效 + #shw 根据邮箱获取用户对象 blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() + #shw 使用make_password对新密码进行哈希处理 blog_user.password = make_password(form.cleaned_data["new_password2"]) - blog_user.save() - return HttpResponseRedirect('/login/') + blog_user.save() #shw 保存用户的新密码 + return HttpResponseRedirect('/login/') #shw 重定向到登录页面 else: + #shw 如果表单无效,重新渲染页面并显示错误 return self.render_to_response({'form': form}) class ForgetPasswordEmailCode(View): - +#shw 发送忘记密码验证码的视图,继承自基础的View。 def post(self, request: HttpRequest): - form = ForgetPasswordCodeForm(request.POST) - if not form.is_valid(): - return HttpResponse("错误的邮箱") - to_email = form.cleaned_data["email"] +#shw 只处理POST请求 + form = ForgetPasswordCodeForm(request.POST) #shw 用POST数据实例化表单 + if not form.is_valid(): #shw 验证表单(主要是验证邮箱格式) + return HttpResponse("错误的邮箱") #shw 如果无效,返回错误信息 + to_email = form.cleaned_data["email"] #shw 获取清洗后的邮箱 - code = generate_code() - utils.send_verify_email(to_email, code) - utils.set_code(to_email, code) + code = generate_code() #shw 生成一个验证码 + utils.send_verify_email(to_email, code) #shw 调用工具函数发送验证邮件 + utils.set_code(to_email, code) #shw 调用工具函数将验证码存入缓存 - return HttpResponse("ok") + return HttpResponse("ok") #shw 返回成功信息