diff --git a/src/accounts/__init__.py b/src/accounts/__init__.py index e69de29..4b3a918 100644 --- a/src/accounts/__init__.py +++ b/src/accounts/__init__.py @@ -0,0 +1 @@ +#hyt: \ No newline at end of file diff --git a/src/accounts/__pycache__/__init__.cpython-312.pyc b/src/accounts/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a8d19b8..0000000 Binary files a/src/accounts/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/admin.cpython-312.pyc b/src/accounts/__pycache__/admin.cpython-312.pyc deleted file mode 100644 index 2e9919a..0000000 Binary files a/src/accounts/__pycache__/admin.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/apps.cpython-312.pyc b/src/accounts/__pycache__/apps.cpython-312.pyc deleted file mode 100644 index 2406f98..0000000 Binary files a/src/accounts/__pycache__/apps.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/forms.cpython-312.pyc b/src/accounts/__pycache__/forms.cpython-312.pyc deleted file mode 100644 index 67a1db9..0000000 Binary files a/src/accounts/__pycache__/forms.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/models.cpython-312.pyc b/src/accounts/__pycache__/models.cpython-312.pyc deleted file mode 100644 index 72d2328..0000000 Binary files a/src/accounts/__pycache__/models.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/urls.cpython-312.pyc b/src/accounts/__pycache__/urls.cpython-312.pyc deleted file mode 100644 index d616a53..0000000 Binary files a/src/accounts/__pycache__/urls.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/user_login_backend.cpython-312.pyc b/src/accounts/__pycache__/user_login_backend.cpython-312.pyc deleted file mode 100644 index 216a07f..0000000 Binary files a/src/accounts/__pycache__/user_login_backend.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/utils.cpython-312.pyc b/src/accounts/__pycache__/utils.cpython-312.pyc deleted file mode 100644 index 653df06..0000000 Binary files a/src/accounts/__pycache__/utils.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/__pycache__/views.cpython-312.pyc b/src/accounts/__pycache__/views.cpython-312.pyc deleted file mode 100644 index f9aa826..0000000 Binary files a/src/accounts/__pycache__/views.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/admin.py b/src/accounts/admin.py index 32e483c..1258fb8 100644 --- a/src/accounts/admin.py +++ b/src/accounts/admin.py @@ -1,3 +1,5 @@ +# hyt: + from django import forms from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import UserChangeForm @@ -9,15 +11,27 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """ + 博客用户创建表单 + + 功能:处理新用户注册时的数据验证和保存 + 扩展了Django标准用户创建流程,添加密码确认和来源记录 + """ password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: model = BlogUser - fields = ('email',) + fields = ('email',) # 创建用户时只需要邮箱字段 def clean_password2(self): - # Check that the two password entries match + """ + 密码确认验证 + + 功能:验证两次输入的密码是否一致 + 返回:验证通过的密码 + 异常:当密码不匹配时抛出ValidationError + """ password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: @@ -25,28 +39,50 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): - # Save the provided password in hashed format + """ + 保存用户信息 + + 功能:处理密码哈希化和用户来源记录 + 参数:commit - 是否立即保存到数据库 + 返回:保存后的用户对象 + """ user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) + user.set_password(self.cleaned_data["password1"]) # 密码哈希化 if commit: - user.source = 'adminsite' + user.source = 'adminsite' # 标记用户来源为管理后台 user.save() return user class BlogUserChangeForm(UserChangeForm): + """ + 博客用户信息修改表单 + + 功能:处理用户信息的编辑和更新 + 继承自Django标准UserChangeForm,支持所有字段编辑 + """ + class Meta: model = BlogUser - fields = '__all__' - field_classes = {'username': UsernameField} + fields = '__all__' # 包含所有字段 + field_classes = {'username': UsernameField} # 用户名字段使用特定类型 def __init__(self, *args, **kwargs): + """初始化表单,设置字段属性""" super().__init__(*args, **kwargs) class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm + """ + 博客用户管理后台配置 + + 功能:自定义Django管理后台的用户管理界面 + 扩展了默认的用户管理功能,优化显示字段和排序 + """ + form = BlogUserChangeForm # 使用自定义修改表单 + add_form = BlogUserCreationForm # 使用自定义创建表单 + + # 列表页显示字段 list_display = ( 'id', 'nickname', @@ -54,6 +90,8 @@ class BlogUserAdmin(UserAdmin): 'email', 'last_login', 'date_joined', - 'source') - list_display_links = ('id', 'username') - ordering = ('-id',) + 'source' + ) + + list_display_links = ('id', 'username') # 可点击链接的字段 + ordering = ('-id',) # 按ID降序排列 diff --git a/src/accounts/apps.py b/src/accounts/apps.py index 9b3fc5a..57a0980 100644 --- a/src/accounts/apps.py +++ b/src/accounts/apps.py @@ -1,5 +1,13 @@ +# hyt: + from django.apps import AppConfig class AccountsConfig(AppConfig): - name = 'accounts' + """ + 账户应用配置类 + + 功能:定义Django账户应用的配置信息 + 继承自AppConfig,用于应用初始化和元数据配置 + """ + name = 'accounts' # 应用名称,对应INSTALLED_APPS中的配置 diff --git a/src/accounts/forms.py b/src/accounts/forms.py index fce4137..5c510d5 100644 --- a/src/accounts/forms.py +++ b/src/accounts/forms.py @@ -1,3 +1,5 @@ +# hyt: + from django import forms from django.contrib.auth import get_user_model, password_validation from django.contrib.auth.forms import AuthenticationForm, UserCreationForm @@ -9,18 +11,37 @@ from .models import BlogUser class LoginForm(AuthenticationForm): + """ + 用户登录表单 + + 功能:处理用户登录认证,自定义表单控件样式 + 继承自Django标准AuthenticationForm,添加Bootstrap样式支持 + """ + 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): + """ + 用户注册表单 + + 功能:处理新用户注册,包含用户名、邮箱和密码验证 + 扩展Django标准UserCreationForm,添加邮箱验证和表单样式 + """ + def __init__(self, *args, **kwargs): + """初始化表单,设置所有字段的Bootstrap样式和占位符""" super(RegisterForm, self).__init__(*args, **kwargs) + # 设置各字段的表单控件样式 self.fields['username'].widget = widgets.TextInput( attrs={'placeholder': "username", "class": "form-control"}) self.fields['email'].widget = widgets.EmailInput( @@ -31,17 +52,33 @@ class RegisterForm(UserCreationForm): attrs={'placeholder': "repeat password", "class": "form-control"}) def clean_email(self): + """ + 邮箱唯一性验证 + + 功能:验证邮箱是否已被注册 + 返回:验证通过的邮箱 + 异常:邮箱已存在时抛出ValidationError + """ 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") + """表单元数据配置""" + model = get_user_model() # 使用当前激活的用户模型 + fields = ("username", "email") # 表单包含的字段 class ForgetPasswordForm(forms.Form): + """ + 忘记密码重置表单 + + 功能:处理密码重置流程,包含邮箱验证、验证码校验和新密码设置 + 用于用户通过邮箱和验证码找回密码的场景 + """ + + # 新密码字段 new_password1 = forms.CharField( label=_("New password"), widget=forms.PasswordInput( @@ -52,6 +89,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 确认密码字段 new_password2 = forms.CharField( label="确认密码", widget=forms.PasswordInput( @@ -62,6 +100,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 邮箱字段 email = forms.EmailField( label='邮箱', widget=forms.TextInput( @@ -72,6 +111,7 @@ class ForgetPasswordForm(forms.Form): ), ) + # 验证码字段 code = forms.CharField( label=_('Code'), widget=forms.TextInput( @@ -83,24 +123,43 @@ class ForgetPasswordForm(forms.Form): ) def clean_new_password2(self): + """ + 新密码确认验证 + + 功能:验证两次输入的新密码是否一致并符合密码策略 + 返回:验证通过的密码 + 异常:密码不匹配或不符合策略时抛出ValidationError + """ 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): + """ + 邮箱存在性验证 + + 功能:验证邮箱是否在系统中注册 + 返回:验证通过的邮箱 + 异常:邮箱未注册时抛出ValidationError + """ user_email = self.cleaned_data.get("email") - if not BlogUser.objects.filter( - email=user_email - ).exists(): - # todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改 + if not BlogUser.objects.filter(email=user_email).exists(): + # 安全提示:这里的报错会暴露邮箱是否注册,可根据安全需求调整 raise ValidationError(_("email does not exist")) return user_email def clean_code(self): + """ + 验证码校验 + + 功能:验证邮箱验证码的有效性 + 返回:验证通过的验证码 + 异常:验证码无效时抛出ValidationError + """ code = self.cleaned_data.get("code") error = utils.verify( email=self.cleaned_data.get("email"), @@ -112,6 +171,13 @@ class ForgetPasswordForm(forms.Form): class ForgetPasswordCodeForm(forms.Form): + """ + 忘记密码验证码请求表单 + + 功能:用于请求发送密码重置验证码,仅包含邮箱字段 + 简化表单,专门用于验证码发送流程 + """ + email = forms.EmailField( - label=_('Email'), + label=_('Email'), # 邮箱标签 ) diff --git a/src/accounts/migrations/0001_initial.py b/src/accounts/migrations/0001_initial.py deleted file mode 100644 index e13c533..0000000 --- a/src/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,104 +0,0 @@ -#hyt: -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - """ - BlogUser 模型的初始迁移文件 - - 创建自定义用户模型 BlogUser,扩展 Django 内置 User 模型 - 添加了昵称、时间戳和来源字段,支持中文显示和自定义排序 - """ - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='BlogUser', - fields=[ - # 主键字段 - 使用 BigAutoField 作为自增主键 - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - - # 认证相关字段 - Django 内置用户认证系统必需字段 - ('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' - )), - ('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' - )), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - - # 自定义扩展字段 - 博客用户特有字段 - ('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - ('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' - )), - ], - options={ - # 模型元选项 - 定义模型在 admin 中的显示和排序 - 'verbose_name': '用户', # 单数显示名称 - 'verbose_name_plural': '用户', # 复数显示名称 - 'ordering': ['-id'], # 按 ID 降序排列 - 'get_latest_by': 'id', # 获取最新记录的依据字段 - }, - managers=[ - # 模型管理器 - 使用 Django 默认的用户管理器 - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ] \ No newline at end of file diff --git a/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py deleted file mode 100644 index 22eb985..0000000 --- a/src/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ /dev/null @@ -1,81 +0,0 @@ -#hyt: -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - """ - BlogUser 模型结构调整迁移文件 - - 对 accounts 应用的 BlogUser 模型进行字段优化和国际化改进: - 1. 调整时间字段命名,统一使用英文命名规范 - 2. 更新模型选项,改进 Admin 后台显示 - 3. 字段标签国际化,为多语言支持做准备 - """ - - dependencies = [ - ('accounts', '0001_initial'), # 依赖于初始迁移文件 - ] - - operations = [ - # 模型选项调整 - 更新 Admin 后台显示配置 - migrations.AlterModelOptions( - name='bloguser', - options={ - 'get_latest_by': 'id', # 按 ID 获取最新记录 - 'ordering': ['-id'], # 按 ID 降序排列 - 'verbose_name': 'user', # 单数显示名称(英文) - 'verbose_name_plural': 'user', # 复数显示名称(英文) - }, - ), - - # 字段清理 - 移除旧的时间字段 - migrations.RemoveField( - model_name='bloguser', - name='created_time', # 移除旧的创建时间字段 - ), - migrations.RemoveField( - model_name='bloguser', - name='last_mod_time', # 移除旧的修改时间字段 - ), - - # 字段添加 - 新增标准化时间字段 - migrations.AddField( - model_name='bloguser', - name='creation_time', - field=models.DateTimeField( - default=django.utils.timezone.now, # 默认值为当前时间 - verbose_name='creation time' # 字段显示名称(英文) - ), - ), - migrations.AddField( - model_name='bloguser', - name='last_modify_time', - field=models.DateTimeField( - default=django.utils.timezone.now, # 默认值为当前时间 - verbose_name='last modify time' # 字段显示名称(英文) - ), - ), - - # 字段调整 - 更新字段标签为英文 - migrations.AlterField( - model_name='bloguser', - name='nickname', - field=models.CharField( - blank=True, # 允许为空 - max_length=100, # 最大长度100字符 - verbose_name='nick name' # 字段显示名称(英文) - ), - ), - migrations.AlterField( - model_name='bloguser', - name='source', - field=models.CharField( - blank=True, # 允许为空 - max_length=100, # 最大长度100字符 - verbose_name='create source' # 字段显示名称(英文) - ), - ), - ] \ No newline at end of file diff --git a/src/accounts/migrations/__init__.py b/src/accounts/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc b/src/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 95a7532..0000000 Binary files a/src/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-312.pyc b/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-312.pyc deleted file mode 100644 index 82e80ca..0000000 Binary files a/src/accounts/migrations/__pycache__/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/migrations/__pycache__/__init__.cpython-312.pyc b/src/accounts/migrations/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 7090758..0000000 Binary files a/src/accounts/migrations/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/models.py b/src/accounts/models.py index 3baddbb..e8fe7cd 100644 --- a/src/accounts/models.py +++ b/src/accounts/models.py @@ -1,3 +1,5 @@ +# hyt: + from django.contrib.auth.models import AbstractUser from django.db import models from django.urls import reverse @@ -9,27 +11,64 @@ from djangoblog.utils import get_current_site # Create your models here. class BlogUser(AbstractUser): + """ + 博客用户模型 + + 功能:扩展Django标准用户模型,添加博客系统特有字段 + 继承自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) def get_absolute_url(self): + """ + 获取用户详情页的绝对URL + + 返回:用户作者详情页的URL路径 + 用于Django admin和模板中的链接生成 + """ return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): + """ + 对象字符串表示 + + 返回:用户的邮箱地址 + 用于Django admin和其他显示场景 + """ return self.email def get_full_url(self): + """ + 获取用户的完整URL(包含域名) + + 返回:包含协议和域名的完整用户URL + 用于生成外部可访问的用户主页链接 + """ 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' + """ + 模型元数据配置 + + 定义模型在数据库和Django admin中的行为 + """ + ordering = ['-id'] # 默认按ID降序排列 + verbose_name = _('user') # 单数显示名称 + verbose_name_plural = verbose_name # 复数显示名称(与单数相同) + get_latest_by = 'id' # 获取最新记录的依据字段 \ No newline at end of file diff --git a/src/accounts/templatetags/__pycache__/__init__.cpython-312.pyc b/src/accounts/templatetags/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 3061f4f..0000000 Binary files a/src/accounts/templatetags/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/src/accounts/urls.py b/src/accounts/urls.py index 107a801..92f5dbf 100644 --- a/src/accounts/urls.py +++ b/src/accounts/urls.py @@ -1,28 +1,42 @@ -from django.urls import path +# hyt: + +# from django.urls import path from django.urls import re_path from . import views from .forms import LoginForm -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'), - ] +app_name = "accounts" # 应用命名空间,用于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(), # 注销视图,使用Django内置LogoutView + name='logout'), # URL名称,用于反向解析 + + # 账户操作结果页面URL + 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/accounts/user_login_backend.py b/src/accounts/user_login_backend.py index 73cdca1..2b67872 100644 --- a/src/accounts/user_login_backend.py +++ b/src/accounts/user_login_backend.py @@ -1,26 +1,70 @@ +# hyt: + from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend class EmailOrUsernameModelBackend(ModelBackend): """ - 允许使用用户名或邮箱登录 + 多字段认证后端 + + 功能:扩展Django认证系统,支持使用用户名或邮箱登录 + 继承自ModelBackend,提供灵活的用户身份验证方式 + 应用场景:方便用户使用用户名或邮箱任意一种方式登录系统 """ def authenticate(self, request, username=None, password=None, **kwargs): + """ + 用户认证方法 + + 功能:根据用户名或邮箱验证用户身份 + 参数: + request: HTTP请求对象 + username: 用户名或邮箱地址 + password: 用户密码 + **kwargs: 其他关键字参数 + + 返回: + User对象: 认证成功返回用户实例 + None: 认证失败返回None + + 逻辑: + - 判断输入是否包含'@'符号来区分用户名和邮箱 + - 查询对应用户并验证密码 + - 支持Django认证系统的标准接口 + """ + # 根据输入内容判断是用户名还是邮箱 if '@' in username: - kwargs = {'email': username} + kwargs = {'email': username} # 包含@符号,按邮箱处理 else: - kwargs = {'username': username} + 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: + # 用户不存在,认证失败 return None def get_user(self, username): + """ + 根据用户ID获取用户对象 + + 功能:通过用户主键获取用户实例 + 参数: + username: 用户ID(主键) + + 返回: + User对象: 用户存在时返回用户实例 + None: 用户不存在时返回None + + 注意:此处的username参数实际上是用户主键ID + 用于session认证中从用户ID获取用户对象 + """ try: return get_user_model().objects.get(pk=username) except get_user_model().DoesNotExist: - return None + return None \ No newline at end of file diff --git a/src/accounts/utils.py b/src/accounts/utils.py index 4b94bdf..bf093df 100644 --- a/src/accounts/utils.py +++ b/src/accounts/utils.py @@ -1,3 +1,5 @@ +# hyt: + import typing from datetime import timedelta @@ -7,15 +9,21 @@ 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: 邮件主题 - code: 验证码 + """ + 发送验证邮件 + + 功能:向指定邮箱发送包含验证码的邮件,用于密码重置等验证场景 + 参数: + to_mail: 接收邮箱地址 + code: 验证码内容 + subject: 邮件主题,默认为"Verify Email" + + 注意:邮件内容包含验证码和5分钟有效期的提示 """ html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " @@ -24,15 +32,20 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 - Args: - email: 请求邮箱 - code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + """ + 验证验证码有效性 + + 功能:验证用户输入的验证码与缓存中的验证码是否匹配 + 参数: + email: 用户邮箱地址,作为缓存键 + code: 用户输入的验证码 + + 返回: + str: 验证失败时返回错误信息 + None: 验证成功时返回None + + 注意:当前错误处理方式不够合理,建议改为异常抛出机制 + 这样调用方可以通过try-except处理错误,避免条件判断 """ cache_code = get_code(email) if cache_code != code: @@ -40,10 +53,29 @@ def verify(email: str, code: str) -> typing.Optional[str]: def set_code(email: str, code: str): - """设置code""" + """ + 设置验证码到缓存 + + 功能:将验证码存储到缓存系统,使用邮箱作为键 + 参数: + email: 邮箱地址,作为缓存键 + code: 验证码内容,作为缓存值 + + 缓存配置:验证码有效期为5分钟(_code_ttl) + """ cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) + """ + 从缓存获取验证码 + + 功能:根据邮箱地址从缓存中获取对应的验证码 + 参数: + email: 邮箱地址,作为缓存键 + + 返回: + str: 存在验证码时返回验证码内容 + None: 验证码不存在或已过期时返回None + """ + return cache.get(email) \ No newline at end of file diff --git a/src/accounts/views.py b/src/accounts/views.py index ae67aec..e70429e 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -1,3 +1,5 @@ +# hyt: + import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings @@ -32,28 +34,46 @@ logger = logging.getLogger(__name__) # Create your views here. class RegisterView(FormView): - form_class = RegisterForm - template_name = 'account/registration_form.html' + """ + 用户注册视图 + + 功能:处理新用户注册流程,包括表单验证、用户创建和邮箱验证 + 继承自FormView,使用自定义注册表单 + """ + form_class = RegisterForm # 使用自定义注册表单 + template_name = 'account/registration_form.html' # 注册页面模板 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): + """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) + 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' + site = '127.0.0.1:8000' # 调试模式下使用本地地址 + 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 +84,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + # 发送验证邮件 send_email( emailto=[ user.email, @@ -71,102 +92,152 @@ 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 = '/login/' + """ + 用户注销视图 + + 功能:处理用户注销流程,清理会话和缓存 + 继承自RedirectView,注销后重定向到登录页面 + """ + url = '/login/' # 注销后重定向地址 @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): + """禁用缓存,确保注销操作实时生效""" return super(LogoutView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): - logout(request) - delete_sidebar_cache() + """ + 处理GET请求注销 + + 功能:执行用户注销操作,清理侧边栏缓存 + """ + logout(request) # Django内置注销函数 + delete_sidebar_cache() # 清理侧边栏缓存 return super(LogoutView, self).get(request, *args, **kwargs) class LoginView(FormView): - form_class = LoginForm - template_name = 'account/login.html' - 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) + """ + 用户登录视图 + + 功能:处理用户登录认证,支持记住登录状态 + 继承自FormView,使用自定义登录表单 + """ + form_class = LoginForm # 使用自定义登录表单 + template_name = 'account/login.html' # 登录页面模板 + success_url = '/' # 登录成功默认跳转地址 + redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名 + login_ttl = 2626560 # 记住登录状态的有效期:一个月(秒数) + + @method_decorator(sensitive_post_parameters('password')) # 敏感参数保护 + @method_decorator(csrf_protect) # CSRF保护 + @method_decorator(never_cache) # 禁用缓存 def dispatch(self, request, *args, **kwargs): - + """多重装饰器保护登录流程安全""" return super(LoginView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): + """ + 获取模板上下文数据 + + 功能:添加重定向地址到上下文,用于登录后跳转 + """ redirect_to = self.request.GET.get(self.redirect_field_name) if redirect_to is None: - redirect_to = '/' + redirect_to = '/' # 默认跳转到首页 kwargs['redirect_to'] = redirect_to 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() + delete_sidebar_cache() # 清理侧边栏缓存 logger.info(self.redirect_field_name) - auth.login(self.request, form.get_user()) + auth.login(self.request, form.get_user()) # Django内置登录函数 + # 处理"记住我"选项 if self.request.POST.get("remember"): - self.request.session.set_expiry(self.login_ttl) + 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 }) def get_success_url(self): + """ + 获取登录成功后的跳转地址 + 功能:验证重定向地址的安全性,防止开放重定向攻击 + """ redirect_to = self.request.POST.get(self.redirect_field_name) + # 验证重定向地址是否安全 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 # 不安全则使用默认地址 return redirect_to def account_result(request): - type = request.GET.get('type') - id = request.GET.get('id') + """ + 账户操作结果页面视图 + + 功能:显示注册或邮箱验证的结果信息 + 处理注册成功提示和邮箱验证激活 + """ + type = request.GET.get('type') # 操作类型:register或validation + id = request.GET.get('id') # 用户ID - user = get_object_or_404(get_user_model(), id=id) + user = get_object_or_404(get_user_model(), id=id) # 获取用户对象 logger.info(type) + + # 如果用户已激活,直接跳转到首页 if user.is_active: return HttpResponseRedirect('/') + + # 处理注册和验证两种场景 if type and type in ['register', 'validation']: if type == 'register': + # 注册成功场景 content = ''' 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 ''' title = '注册成功' else: + # 邮箱验证场景 c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) sign = request.GET.get('sign') + # 验证签名防止篡改 if sign != c_sign: return HttpResponseForbidden() - user.is_active = True + user.is_active = True # 激活用户 user.save() content = ''' 恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。 ''' title = '验证成功' + # 渲染结果页面 return render(request, 'account/result.html', { 'title': title, 'content': content @@ -176,29 +247,56 @@ def account_result(request): class ForgetPasswordView(FormView): - form_class = ForgetPasswordForm - template_name = 'account/forget_password.html' + """ + 忘记密码重置视图 + + 功能:处理用户密码重置流程 + 继承自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() + # 使用Django密码哈希函数设置新密码 blog_user.password = make_password(form.cleaned_data["new_password2"]) blog_user.save() - return HttpResponseRedirect('/login/') + return HttpResponseRedirect('/login/') # 重定向到登录页面 else: + # 表单验证失败,重新渲染表单 return self.render_to_response({'form': form}) class ForgetPasswordEmailCode(View): + """ + 忘记密码验证码发送视图 + + 功能:处理密码重置验证码的发送请求 + 继承自View,处理POST请求发送验证码邮件 + """ def post(self, request: HttpRequest): + """ + 处理POST请求发送验证码 + + 功能:验证邮箱格式,生成并发送验证码 + """ form = ForgetPasswordCodeForm(request.POST) if not form.is_valid(): - return HttpResponse("错误的邮箱") + return HttpResponse("错误的邮箱") # 邮箱格式错误 + to_email = form.cleaned_data["email"] + # 生成并发送验证码 code = generate_code() - utils.send_verify_email(to_email, code) - utils.set_code(to_email, code) + utils.send_verify_email(to_email, code) # 发送验证邮件 + utils.set_code(to_email, code) # 存储验证码到缓存 - return HttpResponse("ok") + return HttpResponse("ok") # 返回成功响应