Compare commits

...

16 Commits

@ -0,0 +1,70 @@
# 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):
#lht: 标记这是一个初始迁移文件
initial = True
#lht: 定义依赖关系该迁移依赖于auth应用的0012_alter_user_first_name_max_length迁移
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
#lht: 创建BlogUser模型的操作
migrations.CreateModel(
name='BlogUser',
fields=[
#lht: 主键字段自动创建的BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
#lht: 密码字段,存储加密后的密码
('password', models.CharField(max_length=128, verbose_name='password')),
#lht: 上次登录时间字段
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
#lht: 超级用户状态字段,拥有所有权限
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
#lht: 用户名字段,具有唯一性约束和验证器
('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')),
#lht: 名字字段
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
#lht: 姓氏字段
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
#lht: 邮箱地址字段
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
#lht: 员工状态字段,决定是否可以登录管理站点
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
#lht: 活跃状态字段,决定用户账户是否有效
('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')),
#lht: 加入日期字段
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
#lht: 昵称字段,博客用户的额外信息
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
#lht: 创建时间字段
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
#lht: 最后修改时间字段
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
#lht: 创建来源字段,标记用户通过何种方式创建
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
#lht: 用户组关联字段,多对多关系
('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')),
#lht: 用户权限字段,多对多关系
('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')),
],
#lht: 模型选项配置
options={
'verbose_name': '用户', #lht: 单数名称
'verbose_name_plural': '用户', #lht: 复数名称
'ordering': ['-id'], #lht: 默认排序方式按ID降序
'get_latest_by': 'id', #lht: 获取最新记录的依据字段
},
#lht: 模型管理器
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

@ -0,0 +1,70 @@
# 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):
#lht: 标记这是一个初始迁移文件
initial = True
#lht: 定义依赖关系该迁移依赖于auth应用的0012_alter_user_first_name_max_length迁移
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
#lht: 创建BlogUser模型的操作
migrations.CreateModel(
name='BlogUser',
fields=[
#lht: 主键字段自动创建的BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
#lht: 密码字段,存储加密后的密码
('password', models.CharField(max_length=128, verbose_name='password')),
#lht: 上次登录时间字段
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
#lht: 超级用户状态字段,拥有所有权限
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
#lht: 用户名字段,具有唯一性约束和验证器
('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')),
#lht: 名字字段
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
#lht: 姓氏字段
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
#lht: 邮箱地址字段
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
#lht: 员工状态字段,决定是否可以登录管理站点
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
#lht: 活跃状态字段,决定用户账户是否有效
('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')),
#lht: 加入日期字段
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
#lht: 昵称字段,博客用户的额外信息
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
#lht: 创建时间字段
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
#lht: 最后修改时间字段
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
#lht: 创建来源字段,标记用户通过何种方式创建
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
#lht: 用户组关联字段,多对多关系
('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')),
#lht: 用户权限字段,多对多关系
('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')),
],
#lht: 模型选项配置
options={
'verbose_name': '用户', #lht: 单数名称
'verbose_name_plural': '用户', #lht: 复数名称
'ordering': ['-id'], #lht: 默认排序方式按ID降序
'get_latest_by': 'id', #lht: 获取最新记录的依据字段
},
#lht: 模型管理器
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

@ -0,0 +1,72 @@
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 _
# Register your models here.
from .models import BlogUser
class BlogUserCreationForm(forms.ModelForm):
#lht: 创建用户表单用于在Django管理后台创建新用户
#lht: 密码输入字段
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
#lht: 确认密码输入字段
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
#lht: 指定关联的模型和字段
model = BlogUser
fields = ('email',)
def clean_password2(self):
#lht: 验证两次密码输入是否一致
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(_("passwords do not match"))
return password2
def save(self, commit=True):
#lht: 保存用户并加密密码
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
#lht: 设置用户来源为管理后台
user.source = 'adminsite'
user.save()
return user
class BlogUserChangeForm(UserChangeForm):
#lht: 修改用户表单用于在Django管理后台编辑现有用户
class Meta:
#lht: 指定关联的模型、字段和字段类
model = BlogUser
fields = '__all__'
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
#lht: 初始化表单
super().__init__(*args, **kwargs)
class BlogUserAdmin(UserAdmin):
#lht: 用户管理界面配置自定义Django管理后台的用户管理界面
#lht: 指定修改用户和创建用户使用的表单
form = BlogUserChangeForm
add_form = BlogUserCreationForm
#lht: 定义在列表页面显示的字段
list_display = (
'id',
'nickname',
'username',
'email',
'last_login',
'date_joined',
'source')
#lht: 定义在列表页面中可点击跳转到编辑页面的字段
list_display_links = ('id', 'username')
#lht: 定义默认排序方式
ordering = ('-id',)

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
#lht:指定应用的名称Django会根据这个名称找到对应的应用目录
name = 'accounts'

@ -0,0 +1,136 @@
from django import forms
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.exceptions import ValidationError
from django.forms import widgets
from django.utils.translation import gettext_lazy as _
from . import utils
from .models import BlogUser
class LoginForm(AuthenticationForm):
#lht: 登录表单继承Django内置认证表单
def __init__(self, *args, **kwargs):
#lht: 调用父类构造函数
super(LoginForm, self).__init__(*args, **kwargs)
#lht: 自定义用户名和密码字段的显示样式
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):
#lht: 用户注册表单继承Django内置用户创建表单
def __init__(self, *args, **kwargs):
#lht: 调用父类构造函数
super(RegisterForm, self).__init__(*args, **kwargs)
#lht: 自定义各字段的显示样式
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
self.fields['email'].widget = widgets.EmailInput(
attrs={'placeholder': "email", "class": "form-control"})
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
self.fields['password2'].widget = widgets.PasswordInput(
attrs={'placeholder': "repeat password", "class": "form-control"})
def clean_email(self):
#lht: 验证邮箱唯一性
email = self.cleaned_data['email']
if get_user_model().objects.filter(email=email).exists():
raise ValidationError(_("email already exists"))
return email
class Meta:
#lht: 指定模型和字段
model = get_user_model()
fields = ("username", "email")
class ForgetPasswordForm(forms.Form):
#lht: 忘记密码表单
#lht: 新密码字段
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'placeholder': _("New password")
}
),
)
#lht: 确认新密码字段
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
attrs={
"class": "form-control",
'placeholder': _("Confirm password")
}
),
)
#lht: 邮箱字段
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': _("Email")
}
),
)
#lht: 验证码字段
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': _("Code")
}
),
)
def clean_new_password2(self):
#lht: 验证两次输入的密码是否一致
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"))
#lht: 验证密码强度
password_validation.validate_password(password2)
return password2
def clean_email(self):
#lht: 验证邮箱是否存在
user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter(
email=user_email
).exists():
#lht: todo 这里的报错提示可以判断一个邮箱是不是注册过,如果不想暴露可以修改
raise ValidationError(_("email does not exist"))
return user_email
def clean_code(self):
#lht: 验证验证码是否正确
code = self.cleaned_data.get("code")
#lht: 调用工具函数验证验证码
error = utils.verify(
email=self.cleaned_data.get("email"),
code=code,
)
if error:
raise ValidationError(error)
return code
class ForgetPasswordCodeForm(forms.Form):
#lht: 忘记密码时获取验证码的表单
#lht: 邮箱字段
email = forms.EmailField(
label=_('Email'),
)

@ -0,0 +1,43 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from djangoblog.utils import get_current_site
#lht: Create your models here.
class BlogUser(AbstractUser):
#lht: 用户昵称字段
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
#lht: 用户创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
#lht: 用户最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
#lht: 用户来源标识(如通过注册、后台创建等)
source = models.CharField(_('create source'), max_length=100, blank=True)
def get_absolute_url(self):
#lht: 返回用户个人页面的URL
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
def __str__(self):
#lht: 字符串表示,返回用户邮箱
return self.email
def get_full_url(self):
#lht: 获取用户页面的完整URL
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
class Meta:
#lht: 模型元数据配置
ordering = ['-id'] #lht: 默认按ID倒序排列
verbose_name = _('user') #lht: 单数名称
verbose_name_plural = verbose_name #lht: 复数名称
get_latest_by = 'id' #lht: 获取最新记录的字段

@ -0,0 +1,275 @@
from django.test import Client, RequestFactory, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.models import BlogUser
from blog.models import Article, Category
from djangoblog.utils import *
from . import utils
#lht: Create your tests here.
class AccountTest(TestCase):
#lht: """
#lht: 账户功能测试类
#lht: 继承Django的TestCase用于测试账户相关的各种功能
#lht: """
def setUp(self):
#lht: """
#lht: 测试前的准备工作
#lht: 每个测试方法执行前都会调用此方法
#lht: """
self.client = Client() #lht: 创建测试客户端用于模拟HTTP请求
self.factory = RequestFactory() #lht: 创建请求工厂,用于创建请求对象
#lht: 创建一个测试用户,用于后续的测试
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
password="12345678"
)
self.new_test = "xxx123--=" #lht: 设置测试用的新密码
def test_validate_account(self):
#lht: """
#lht: 测试账户验证功能
#lht: 包括超级用户创建、登录验证、管理员权限等
#lht: """
site = get_current_site().domain
#lht: 创建超级用户用于测试
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
testuser = BlogUser.objects.get(username='liangliangyy1')
#lht: 测试用户登录功能
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True) #lht: 验证登录成功
response = self.client.get('/admin/') #lht: 访问管理后台
self.assertEqual(response.status_code, 200) #lht: 验证访问成功
#lht: 创建分类和文章用于测试
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.save()
#lht: 测试能否正常访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
def test_validate_register(self):
#lht: """
#lht: 测试用户注册流程
#lht: 包括注册、邮箱验证、登录、权限设置等完整流程
#lht: """
#lht: 验证目标邮箱尚未注册
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
#lht: 模拟用户注册请求
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',
})
#lht: 验证用户已成功创建
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
#lht: 获取新创建的用户并验证邮箱链接
user = BlogUser.objects.filter(email='user123@user.com')[0]
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
url = '{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)
#lht: 使用新用户登录
self.client.login(username='user1233', password='password123!q@wE#R$T')
user = BlogUser.objects.filter(email='user123@user.com')[0]
#lht: 设置用户为超级用户和员工,以便访问管理功能
user.is_superuser = True
user.is_staff = True
user.save()
delete_sidebar_cache()
#lht: 创建分类和文章
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
article = Article()
article.category = category
article.title = "nicetitle333"
article.body = "nicecontentttt"
article.author = user
article.type = 'a'
article.status = 'p'
article.save()
#lht: 验证能够访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
#lht: 测试用户登出功能
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
#lht: 登出后应无法访问管理页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
#lht: 测试使用错误密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
})
self.assertIn(response.status_code, [301, 302, 200])
#lht: 登录失败后仍无法访问管理页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
def test_verify_email_code(self):
#lht: """
#lht: 测试邮箱验证码验证功能
#lht: """
to_email = "admin@admin.com"
code = generate_code() #lht: 生成验证码
utils.set_code(to_email, code) #lht: 设置验证码
utils.send_verify_email(to_email, code) #lht: 发送验证码(模拟)
#lht: 验证正确的验证码能通过验证
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
#lht: 验证错误的验证码不能通过验证
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
def test_forget_password_email_code_success(self):
#lht: """
#lht: 测试忘记密码时成功获取验证码
#lht: """
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@admin.com")
)
#lht: 验证请求成功且返回"ok"
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
def test_forget_password_email_code_fail(self):
#lht: """
#lht: 测试忘记密码时获取验证码失败的情况
#lht: """
#lht: 测试没有提供邮箱的情况
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict()
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
#lht: 测试提供无效邮箱的情况
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):
#lht: """
#lht: 测试成功重置密码的完整流程
#lht: """
code = generate_code()
utils.set_code(self.blog_user.email, code) #lht: 设置验证码
#lht: 准备重置密码的数据
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code=code,
)
#lht: 发送重置密码请求
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
self.assertEqual(resp.status_code, 302) #lht: 重定向表示成功
#lht: 验证用户密码是否修改成功
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() #lht: 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):
#lht: """
#lht: 测试为不存在的用户重置密码的情况
#lht: """
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email="123@123.com", #lht: 不存在的邮箱
code="123456",
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
#lht: 应该返回200状态码而不是重定向因为验证失败
self.assertEqual(resp.status_code, 200)
def test_forget_password_email_code_error(self):
#lht: """
#lht: 测试使用错误验证码重置密码的情况
#lht: """
code = generate_code()
utils.set_code(self.blog_user.email, code)
#lht: 使用错误的验证码
data = dict(
new_password1=self.new_test,
new_password2=self.new_test,
email=self.blog_user.email,
code="111111", #lht: 错误的验证码
)
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
#lht: 应该返回200状态码而不是重定向因为验证失败
self.assertEqual(resp.status_code, 200)

@ -0,0 +1,35 @@
from django.urls import path
from django.urls import re_path
from . import views
from .forms import LoginForm
app_name = "accounts" #lht: 应用命名空间
urlpatterns = [
#lht: 登录URL
re_path(r'^login/$',
views.LoginView.as_view(success_url='/'),
name='login',
kwargs={'authentication_form': LoginForm}),
#lht: 注册URL
re_path(r'^register/$',
views.RegisterView.as_view(success_url="/"),
name='register'),
#lht: 登出URL
re_path(r'^logout/$',
views.LogoutView.as_view(),
name='logout'),
#lht: 账户操作结果页面
path(r'account/result.html',
views.account_result,
name='result'),
#lht: 忘记密码页面
re_path(r'^forget_password/$',
views.ForgetPasswordView.as_view(),
name='forget_password'),
#lht: 获取忘记密码验证码
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'),
]

@ -0,0 +1,28 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
#lht: """
#lht: 允许使用用户名或邮箱登录
#lht: """
def authenticate(self, request, username=None, password=None, **kwargs):
#lht: 根据输入内容判断是邮箱还是用户名
if '@' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = get_user_model().objects.get(**kwargs)
if user.check_password(password):
return user
except get_user_model().DoesNotExist:
return None
def get_user(self, username):
#lht: 根据用户名获取用户对象
try:
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
return None

@ -0,0 +1,49 @@
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
_code_ttl = timedelta(minutes=5) #lht: 验证码有效期5分钟
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
#lht: """发送重设密码验证码
#lht: Args:
#lht: to_mail: 接受邮箱
#lht: subject: 邮件主题
#lht: code: 验证码
#lht: """
html_content = _(
"You are resetting the password, the verification code is%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
send_email([to_mail], subject, html_content)
def verify(email: str, code: str) -> typing.Optional[str]:
#lht: """验证code是否有效
#lht: Args:
#lht: email: 请求邮箱
#lht: code: 验证码
#lht: Return:
#lht: 如果有错误就返回错误str
#lht: Node:
#lht: 这里的错误处理不太合理应该采用raise抛出
#lht: 否测调用方也需要对error进行处理
#lht: """
cache_code = get_code(email)
if cache_code != code:
return gettext("Verification code error")
def set_code(email: str, code: str):
#lht: """设置code"""
cache.set(email, code, _code_ttl.seconds)
def get_code(email: str) -> typing.Optional[str]:
#lht: """获取code"""
return cache.get(email)

@ -0,0 +1,218 @@
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import get_user_model
from django.contrib.auth import logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
from .models import BlogUser
logger = logging.getLogger(__name__)
#lht: Create your views here.
class RegisterView(FormView):
#lht: 用户注册视图
form_class = RegisterForm
template_name = 'account/registration_form.html'
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
#lht: 处理请求分发添加CSRF保护装饰器
return super(RegisterView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
#lht: 表单验证成功时的处理逻辑
if form.is_valid():
user = form.save(False)
user.is_active = False #lht: 新注册用户默认不激活
user.source = 'Register' #lht: 标记来源为注册
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'
path = reverse('account:result')
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
#lht: 构造验证邮件内容
content = """
<p>请点击下面链接验证您的邮箱</p>
<a href="{url}" rel="bookmark">{url}</a>
再次感谢您
<br />
如果上面链接无法打开请将此链接复制至浏览器
{url}
""".format(url=url)
send_email(
emailto=[
user.email,
],
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):
#lht: 用户登出视图
url = '/login/'
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
#lht: 处理请求分发,添加不缓存装饰器
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
#lht: 处理GET请求执行登出操作
logout(request)
delete_sidebar_cache() #lht: 清除侧边栏缓存
return super(LogoutView, self).get(request, *args, **kwargs)
class LoginView(FormView):
#lht: 用户登录视图
form_class = LoginForm
template_name = 'account/login.html'
success_url = '/'
redirect_field_name = REDIRECT_FIELD_NAME
login_ttl = 2626560 #lht: 登录会话保持时间(一个月)
@method_decorator(sensitive_post_parameters('password'))
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
#lht: 处理请求分发添加敏感参数保护、CSRF保护和不缓存装饰器
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
#lht: 获取重定向URL
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
redirect_to = '/'
kwargs['redirect_to'] = redirect_to
return super(LoginView, self).get_context_data(**kwargs)
def form_valid(self, form):
#lht: 表单验证成功时的处理逻辑
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)
else:
return self.render_to_response({
'form': form
})
def get_success_url(self):
#lht: 获取登录成功后的跳转URL
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
return redirect_to
def account_result(request):
#lht: 账户操作结果页面
type = request.GET.get('type')
id = request.GET.get('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.save()
content = '''
恭喜您已经成功的完成邮箱验证您现在可以使用您的账号来登录本站
'''
title = '验证成功'
return render(request, 'account/result.html', {
'title': title,
'content': content
})
else:
return HttpResponseRedirect('/')
class ForgetPasswordView(FormView):
#lht: 忘记密码视图
form_class = ForgetPasswordForm
template_name = 'account/forget_password.html'
def form_valid(self, form):
#lht: 表单验证成功时的处理逻辑
if form.is_valid():
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.save()
return HttpResponseRedirect('/login/')
else:
return self.render_to_response({'form': form})
class ForgetPasswordEmailCode(View):
#lht: 发送忘记密码验证码视图
def post(self, request: HttpRequest):
#lht: 处理POST请求发送验证码邮件
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")

Binary file not shown.
Loading…
Cancel
Save