diff --git a/accounts/admin.py b/accounts/admin.py index 32e483c..84d0af3 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,23 +1,34 @@ 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.contrib.auth.forms import UserChangeForm, UsernameField from django.utils.translation import gettext_lazy as _ -# Register your models here. +# 引入自定义用户模型 from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): - password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) - password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) + """ + 后台创建用户表单 + 提供两个密码输入框,确保管理员在后台创建用户时输入的密码一致。 + """ + 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',) # 后台创建用户表单只要求输入 email def clean_password2(self): - # Check that the two password entries match + """ + 验证两次输入的密码是否一致 + """ password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: @@ -25,28 +36,41 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): - # Save the provided password in hashed format + """ + 保存用户,并将密码以哈希形式存储 + """ user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) + user.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} # 指定 username 字段类型 def __init__(self, *args, **kwargs): + """ + 可以在此处扩展初始化逻辑,目前直接调用父类初始化 + """ super().__init__(*args, **kwargs) class BlogUserAdmin(UserAdmin): - form = BlogUserChangeForm - add_form = BlogUserCreationForm + """ + 自定义后台管理 BlogUser 的显示和表单配置 + """ + form = BlogUserChangeForm # 修改用户时使用的表单 + add_form = BlogUserCreationForm # 创建用户时使用的表单 + + # 在列表页显示的字段 list_display = ( 'id', 'nickname', @@ -54,6 +78,7 @@ 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/accounts/models.py b/accounts/models.py index 3baddbb..e1cf766 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -9,27 +9,64 @@ from djangoblog.utils import get_current_site # Create your models here. class BlogUser(AbstractUser): - nickname = models.CharField(_('nick name'), max_length=100, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - source = models.CharField(_('create source'), max_length=100, blank=True) + """ + 博客用户模型,继承 Django 的 AbstractUser, + 添加了博客系统需要的额外字段,如昵称、创建时间、修改时间和来源。 + """ + + nickname = models.CharField( + _('nick name'), # 字段在 admin 或表单中的显示名称(可翻译) + max_length=100, # 昵称最大长度为 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,用于在模板或视图中直接获取用户个人主页链接。 + 这里使用 username 作为参数。 + """ return reverse( 'blog:author_detail', kwargs={ - 'author_name': self.username}) + 'author_name': self.username + } + ) def __str__(self): + """ + 返回对象的字符串表示,这里使用 email,方便在 admin 或调试时查看。 + """ return self.email def get_full_url(self): - site = get_current_site().domain - url = "https://{site}{path}".format(site=site, - path=self.get_absolute_url()) + """ + 返回带域名的完整用户详情页 URL。 + 例如:https://example.com/blog/author/username + """ + site = get_current_site().domain # 获取当前站点域名 + url = "https://{site}{path}".format( + site=site, + path=self.get_absolute_url() + ) return url class Meta: - ordering = ['-id'] - verbose_name = _('user') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-id'] # 默认按 ID 降序排列 + verbose_name = _('user') # 在 admin 中显示的名称 + verbose_name_plural = verbose_name # 复数形式 + get_latest_by = 'id' # get_latest 方法默认按 ID 获取最新对象 diff --git a/accounts/tests.py b/accounts/tests.py index 6893411..450930c 100644 --- a/accounts/tests.py +++ b/accounts/tests.py @@ -12,37 +12,59 @@ from . import utils # Create your tests here. class AccountTest(TestCase): + """ + 针对账户注册、登录、密码找回、邮箱验证等功能的测试类。 + 使用 Django 的 TestCase 提供的测试客户端和工厂方法进行测试。 + """ + def setUp(self): - self.client = Client() - self.factory = RequestFactory() + """ + 初始化测试所需的对象。 + 每个测试方法运行前都会执行。 + """ + self.client = Client() # Django 测试客户端,用于模拟请求 + self.factory = RequestFactory() # 用于构造请求对象 + # 创建一个普通测试用户 self.blog_user = BlogUser.objects.create_user( username="test", email="admin@admin.com", password="12345678" ) - self.new_test = "xxx123--=" + self.new_test = "xxx123--=" # 用于密码重置测试的新密码 def test_validate_account(self): - site = get_current_site().domain + """ + 测试账户验证、登录以及文章管理功能 + """ + site = get_current_site().domain # 获取当前站点域名 + + # 创建超级管理员账户 user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", - password="qwer!@#$ggg") + password="qwer!@#$ggg" + ) testuser = BlogUser.objects.get(username='liangliangyy1') + # 测试登录功能 loginresult = self.client.login( username='liangliangyy1', - password='qwer!@#$ggg') - self.assertEqual(loginresult, True) + password='qwer!@#$ggg' + ) + self.assertEqual(loginresult, True) # 登录应成功 + + # 测试访问 Django admin response = self.client.get('/admin/') self.assertEqual(response.status_code, 200) + # 创建一个文章分类 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() category.last_modify_time = timezone.now() category.save() + # 创建一篇文章 article = Article() article.title = "nicetitleaaa" article.body = "nicecontentaaa" @@ -52,38 +74,52 @@ class AccountTest(TestCase): article.status = 'p' article.save() + # 测试访问文章的后台管理页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) def test_validate_register(self): + """ + 测试用户注册、激活、登录及文章管理流程 + """ + # 验证注册前用户不存在 self.assertEquals( - 0, len( - BlogUser.objects.filter( - email='user123@user.com'))) + 0, len(BlogUser.objects.filter(email='user123@user.com')) + ) + + # 通过客户端 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', }) + + # 验证注册后用户已创建 self.assertEquals( - 1, len( - BlogUser.objects.filter( - email='user123@user.com'))) + 1, len(BlogUser.objects.filter(email='user123@user.com')) + ) + user = BlogUser.objects.filter(email='user123@user.com')[0] + # 生成邮箱验证签名 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) path = reverse('accounts:result') url = '{path}?type=validation&id={id}&sign={sign}'.format( - path=path, id=user.id, sign=sign) + path=path, id=user.id, sign=sign + ) + + # 测试访问邮箱验证链接 response = self.client.get(url) self.assertEqual(response.status_code, 200) + # 登录用户 self.client.login(username='user1233', password='password123!q@wE#R$T') - user = BlogUser.objects.filter(email='user123@user.com')[0] user.is_superuser = True user.is_staff = True user.save() - delete_sidebar_cache() + delete_sidebar_cache() # 清理缓存,避免后台界面异常 + + # 创建分类与文章 category = Category() category.name = "categoryaaa" category.creation_time = timezone.now() @@ -95,20 +131,23 @@ class AccountTest(TestCase): article.title = "nicetitle333" article.body = "nicecontentttt" article.author = user - article.type = 'a' article.status = 'p' article.save() + # 测试访问文章后台管理页面 response = self.client.get(article.get_admin_url()) self.assertEqual(response.status_code, 200) + # 测试注销 response = self.client.get(reverse('account:logout')) self.assertIn(response.status_code, [301, 302, 200]) + # 注销后访问后台页面可能重定向 response = self.client.get(article.get_admin_url()) self.assertIn(response.status_code, [301, 302, 200]) + # 测试错误密码登录 response = self.client.post(reverse('account:login'), { 'username': 'user1233', 'password': 'password123' @@ -119,27 +158,37 @@ class AccountTest(TestCase): self.assertIn(response.status_code, [301, 302, 200]) def test_verify_email_code(self): + """ + 测试邮箱验证码生成、发送与验证 + """ to_email = "admin@admin.com" - code = generate_code() - utils.set_code(to_email, code) - utils.send_verify_email(to_email, code) + code = generate_code() # 生成验证码 + utils.set_code(to_email, code) # 设置验证码缓存 + utils.send_verify_email(to_email, code) # 发送验证码邮件 + # 验证正确邮箱与验证码 err = utils.verify("admin@admin.com", code) self.assertEqual(err, None) + # 验证错误邮箱 err = utils.verify("admin@123.com", code) self.assertEqual(type(err), str) def test_forget_password_email_code_success(self): + """ + 测试发送忘记密码验证码成功 + """ resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@admin.com") ) - self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content.decode("utf-8"), "ok") def test_forget_password_email_code_fail(self): + """ + 测试发送忘记密码验证码失败 + """ resp = self.client.post( path=reverse("account:forget_password_code"), data=dict() @@ -153,6 +202,9 @@ class AccountTest(TestCase): self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") def test_forget_password_email_success(self): + """ + 测试通过邮箱验证码重置密码成功 + """ code = generate_code() utils.set_code(self.blog_user.email, code) data = dict( @@ -168,13 +220,14 @@ class AccountTest(TestCase): self.assertEqual(resp.status_code, 302) # 验证用户密码是否修改成功 - blog_user = BlogUser.objects.filter( - email=self.blog_user.email, - ).first() # type: BlogUser + blog_user = BlogUser.objects.filter(email=self.blog_user.email).first() self.assertNotEqual(blog_user, None) self.assertEqual(blog_user.check_password(data["new_password1"]), True) def test_forget_password_email_not_user(self): + """ + 测试不存在的用户尝试重置密码 + """ data = dict( new_password1=self.new_test, new_password2=self.new_test, @@ -185,23 +238,22 @@ class AccountTest(TestCase): path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) - def test_forget_password_email_code_error(self): + """ + 测试密码重置时使用错误验证码 + """ code = generate_code() utils.set_code(self.blog_user.email, code) data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111", # 错误验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) - diff --git a/djangoblog/settings.py b/djangoblog/settings.py index ac553fd..30f9ac5 100644 --- a/djangoblog/settings.py +++ b/djangoblog/settings.py @@ -111,7 +111,7 @@ DATABASES = { 'ENGINE': 'django.db.backends.mysql', 'NAME': os.environ.get('DJANGO_MYSQL_DATABASE') or 'djangoblog', 'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root', - 'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or '2315304109', + 'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or '123456', 'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1', 'PORT': int( os.environ.get('DJANGO_MYSQL_PORT') or 3306), diff --git a/models_from_db.py b/models_from_db.py new file mode 100644 index 0000000..42b8d43 Binary files /dev/null and b/models_from_db.py differ