diff --git a/doc/开源软件的质量分析报告文档.docx b/doc/开源软件的质量分析报告文档.docx new file mode 100644 index 0000000..d941537 Binary files /dev/null and b/doc/开源软件的质量分析报告文档.docx differ diff --git a/doc/编码规范.docx b/doc/编码规范.docx new file mode 100644 index 0000000..15e67b5 Binary files /dev/null and b/doc/编码规范.docx differ diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py index 32e483c..7212922 100644 --- a/src/DjangoBlog-master/accounts/admin.py +++ b/src/DjangoBlog-master/accounts/admin.py @@ -9,15 +9,22 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """ + 自定义用户创建表单,用于在Admin后台添加新用户 + 继承自ModelForm,提供密码验证和哈希处理功能 + """ password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) class Meta: - model = BlogUser - fields = ('email',) + model = BlogUser # 指定使用的模型 + fields = ('email',) # 表单中显示的字段,这里只显示邮箱 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,35 +32,51 @@ 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"]) + """ + 保存用户信息,对密码进行哈希处理 + commit参数控制是否立即保存到数据库 + """ + user = super().save(commit=False) # 创建用户对象但不保存到数据库 + user.set_password(self.cleaned_data["password1"]) # 对密码进行哈希处理 if commit: - user.source = 'adminsite' - user.save() + user.source = 'adminsite' # 标记用户来源为管理员后台 + user.save() # 保存到数据库 return user class BlogUserChangeForm(UserChangeForm): + """ + 自定义用户信息修改表单 + 继承自Django内置的UserChangeForm,用于在Admin后台编辑用户信息 + """ 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 + """ + 自定义用户管理类,配置Admin后台的用户管理界面 + 继承自Django内置的UserAdmin类 + """ + form = BlogUserChangeForm # 设置用户编辑表单 + add_form = BlogUserCreationForm # 设置用户添加表单 + + # 列表页面显示的字段 list_display = ( 'id', - 'nickname', - 'username', - 'email', - 'last_login', - 'date_joined', - 'source') - list_display_links = ('id', 'username') - ordering = ('-id',) + 'nickname', # 昵称 + 'username', # 用户名 + 'email', # 邮箱 + 'last_login', # 最后登录时间 + 'date_joined', # 注册时间 + 'source' # 用户来源 + ) + + list_display_links = ('id', 'username') # 可点击跳转到编辑页面的字段 + ordering = ('-id',) # 按ID倒序排列,最新的用户显示在最前面 \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/apps.py b/src/DjangoBlog-master/accounts/apps.py index 9b3fc5a..89b9e88 100644 --- a/src/DjangoBlog-master/accounts/apps.py +++ b/src/DjangoBlog-master/accounts/apps.py @@ -2,4 +2,27 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): + """ + 账户应用的配置类 + Django应用配置类,用于配置accounts应用的元数据和行为 + 继承自Django的AppConfig基类 + """ + + # 应用的Python路径,Django使用这个属性来识别应用 + # 这应该与应用的目录名一致 name = 'accounts' + + # 其他常用但未在此定义的配置选项包括: + # - verbose_name: 应用的易读名称(用于管理后台显示) + # - default_auto_field: 默认的主键字段类型 + # - label: 应用的简短标签(用于替代name) + # - path: 应用的文件系统路径 + + # 示例:如果需要配置verbose_name,可以这样添加: + # verbose_name = '用户账户管理' + + # 示例:如果需要自定义ready方法,可以这样添加: + # def ready(self): + # # 应用启动时执行的代码 + # # 通常用于信号注册等初始化操作 + # import accounts.signals diff --git a/src/DjangoBlog-master/accounts/forms.py b/src/DjangoBlog-master/accounts/forms.py index fce4137..50f8cd1 100644 --- a/src/DjangoBlog-master/accounts/forms.py +++ b/src/DjangoBlog-master/accounts/forms.py @@ -9,90 +9,116 @@ from .models import BlogUser class LoginForm(AuthenticationForm): + """自定义登录表单,继承自Django的AuthenticationForm""" + def __init__(self, *args, **kwargs): + """初始化方法,设置表单字段的widget属性""" 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): + """初始化方法,设置所有表单字段的widget属性""" super(RegisterForm, self).__init__(*args, **kwargs) + # 设置用户名字段的输入框属性 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): + """邮箱字段验证方法""" 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( + label=_("New password"), # 字段标签 + widget=forms.PasswordInput( # 密码输入框 attrs={ - "class": "form-control", - 'placeholder': _("New password") + "class": "form-control", # CSS类 + 'placeholder': _("New password") # 占位符文本 } ), ) + # 确认新密码字段 new_password2 = forms.CharField( - label="确认密码", - widget=forms.PasswordInput( + label="确认密码", # 字段标签 + widget=forms.PasswordInput( # 密码输入框 attrs={ - "class": "form-control", - 'placeholder': _("Confirm password") + "class": "form-control", # CSS类 + 'placeholder': _("Confirm password") # 占位符文本 } ), ) + # 邮箱字段 email = forms.EmailField( - label='邮箱', - widget=forms.TextInput( + label='邮箱', # 字段标签 + widget=forms.TextInput( # 文本输入框 attrs={ - 'class': 'form-control', - 'placeholder': _("Email") + 'class': 'form-control', # CSS类 + 'placeholder': _("Email") # 占位符文本 } ), ) + # 验证码字段 code = forms.CharField( - label=_('Code'), - widget=forms.TextInput( + label=_('Code'), # 字段标签 + widget=forms.TextInput( # 文本输入框 attrs={ - 'class': 'form-control', - 'placeholder': _("Code") + 'class': 'form-control', # CSS类 + 'placeholder': _("Code") # 占位符文本 } ), ) def clean_new_password2(self): + """确认密码字段验证方法""" password1 = self.data.get("new_password1") password2 = self.data.get("new_password2") + # 检查两次输入的密码是否一致 if password1 and password2 and password1 != password2: raise ValidationError(_("passwords do not match")) + # 使用Django的密码验证器验证密码强度 password_validation.validate_password(password2) return password2 def clean_email(self): + """邮箱字段验证方法""" user_email = self.cleaned_data.get("email") + # 检查邮箱对应的用户是否存在 if not BlogUser.objects.filter( email=user_email ).exists(): @@ -101,7 +127,9 @@ class ForgetPasswordForm(forms.Form): return user_email def clean_code(self): + """验证码字段验证方法""" code = self.cleaned_data.get("code") + # 使用utils模块验证邮箱和验证码是否匹配 error = utils.verify( email=self.cleaned_data.get("email"), code=code, @@ -112,6 +140,8 @@ class ForgetPasswordForm(forms.Form): class ForgetPasswordCodeForm(forms.Form): + """忘记密码验证码请求表单(仅包含邮箱字段)""" + email = forms.EmailField( - label=_('Email'), - ) + label=_('Email'), # 邮箱字段标签 + ) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/migrations/0001_initial.py b/src/DjangoBlog-master/accounts/migrations/0001_initial.py index d2fbcab..be13df7 100644 --- a/src/DjangoBlog-master/accounts/migrations/0001_initial.py +++ b/src/DjangoBlog-master/accounts/migrations/0001_initial.py @@ -7,43 +7,89 @@ import django.utils.timezone class Migration(migrations.Migration): + """ + Django数据库迁移文件 + 用于创建BlogUser模型的数据库表结构 + 这是一个初始迁移文件(initial migration) + """ + # 标记为初始迁移,Django使用这个标志来识别应用的第一个迁移 initial = True + # 依赖关系:此迁移依赖于auth应用的特定迁移 + # 确保在创建用户表之前,权限相关的表已经存在 dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), ] + # 迁移操作列表:定义要执行的具体数据库操作 operations = [ + # 创建BlogUser模型的数据库表 migrations.CreateModel( - name='BlogUser', + name='BlogUser', # 模型名称 fields=[ + # 主键字段:使用BigAutoField作为自增主键 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + + # 密码字段:存储加密后的密码,最大长度128字符 ('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={ - 'verbose_name': '用户', - 'verbose_name_plural': '用户', - 'ordering': ['-id'], - 'get_latest_by': 'id', + 'verbose_name': '用户', # 单数名称(用于管理后台) + 'verbose_name_plural': '用户', # 复数名称(用于管理后台) + 'ordering': ['-id'], # 默认排序:按ID降序(最新的在前) + 'get_latest_by': 'id', # 指定获取最新记录时使用的字段 }, + # 模型管理器 managers=[ + # 使用Django默认的UserManager来管理用户对象 ('objects', django.contrib.auth.models.UserManager()), ], ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py index 1a9f509..0cb6fc9 100644 --- a/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py +++ b/src/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py @@ -5,42 +5,82 @@ import django.utils.timezone class Migration(migrations.Migration): + """ + Django数据库迁移文件 + 用于修改BlogUser模型的结构和字段定义 + 这是一个数据模型重构迁移,主要更新字段命名和国际化 + """ + # 依赖关系:此迁移依赖于accounts应用的初始迁移 + # 确保在修改表结构之前,初始表已经创建 dependencies = [ - ('accounts', '0001_initial'), + ('accounts', '0001_initial'), # 依赖于accounts应用的第一个迁移文件 ] + # 迁移操作列表:定义要执行的具体数据库结构修改 operations = [ + # 修改模型的元选项(主要是国际化显示名称) migrations.AlterModelOptions( - name='bloguser', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'}, + 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', + model_name='bloguser', # 目标模型 + name='created_time', # 要删除的字段名 ), + + # 删除旧的最后修改时间字段 migrations.RemoveField( - model_name='bloguser', - name='last_mod_time', + 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'), + 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'), + 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, verbose_name='nick name'), + 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, verbose_name='create source'), + 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/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py index 3baddbb..44ae74b 100644 --- a/src/DjangoBlog-master/accounts/models.py +++ b/src/DjangoBlog-master/accounts/models.py @@ -9,27 +9,61 @@ 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,用于Django的通用视图和模板中 + 返回用户详情页的URL + """ return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): + """ + 定义模型的字符串表示形式 + 在管理后台和其他显示对象的地方使用 + 这里使用邮箱作为标识 + """ return self.email def get_full_url(self): - site = get_current_site().domain + """ + 获取用户的完整URL(包含域名) + 用于生成完整的用户主页链接 + """ + site = get_current_site().domain # 获取当前站点域名 url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: + """模型的元数据配置""" + + # 默认按ID降序排列(最新的用户排在前面) ordering = ['-id'] + + # 在管理后台中显示的单数名称 verbose_name = _('user') + + # 在管理后台中显示的复数名称 verbose_name_plural = verbose_name - get_latest_by = 'id' + + # 指定获取最新记录时使用的字段 + get_latest_by = 'id' \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/tests.py b/src/DjangoBlog-master/accounts/tests.py index 6893411..6df4beb 100644 --- a/src/DjangoBlog-master/accounts/tests.py +++ b/src/DjangoBlog-master/accounts/tests.py @@ -13,172 +13,215 @@ from . import utils class AccountTest(TestCase): def setUp(self): - self.client = Client() - self.factory = RequestFactory() + """测试用例初始化方法,每个测试方法执行前都会运行""" + self.client = Client() # 创建测试客户端,用于模拟HTTP请求 + 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") testuser = BlogUser.objects.get(username='liangliangyy1') + # 测试登录功能 loginresult = self.client.login( username='liangliangyy1', password='qwer!@#$ggg') - self.assertEqual(loginresult, True) + self.assertEqual(loginresult, True) # 断言登录成功 + + # 测试管理员页面访问 response = self.client.get('/admin/') - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) # 断言可以访问管理员页面 + # 创建测试分类 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.type = 'a' # 文章类型 + article.status = 'p' # 发布状态 article.save() + # 测试文章管理页面访问 response = self.client.get(article.get_admin_url()) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) # 断言可以访问文章管理页面 def test_validate_register(self): + """测试用户注册流程,包括注册、邮箱验证、登录和权限管理""" + # 验证注册前用户不存在 self.assertEquals( 0, len( BlogUser.objects.filter( email='user123@user.com'))) + + # 发送注册请求 response = self.client.post(reverse('account:register'), { 'username': 'user1233', 'email': 'user123@user.com', 'password1': 'password123!q@wE#R$T', 'password2': 'password123!q@wE#R$T', }) + + # 验证注册后用户已创建 self.assertEquals( 1, len( BlogUser.objects.filter( email='user123@user.com'))) user = BlogUser.objects.filter(email='user123@user.com')[0] + + # 生成邮箱验证签名 sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) 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) + # 测试登录功能 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() 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() + # 测试文章管理页面访问 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]) + self.assertIn(response.status_code, [301, 302, 200]) # 登出通常会有重定向 + # 登出后测试文章管理页面访问(应该被拒绝) response = self.client.get(article.get_admin_url()) - self.assertIn(response.status_code, [301, 302, 200]) + self.assertIn(response.status_code, [301, 302, 200]) # 应该重定向到登录页 + # 重新登录测试(使用错误密码) response = self.client.post(reverse('account:login'), { 'username': 'user1233', - 'password': 'password123' + 'password': 'password123' # 错误的密码 }) self.assertIn(response.status_code, [301, 302, 200]) + # 登录后再次测试文章管理页面访问 response = self.client.get(article.get_admin_url()) self.assertIn(response.status_code, [301, 302, 200]) def test_verify_email_code(self): + """测试邮箱验证码功能""" to_email = "admin@admin.com" - code = generate_code() + 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) + self.assertEqual(err, None) # 应该没有错误 + # 测试错误的邮箱验证 err = utils.verify("admin@123.com", code) - self.assertEqual(type(err), str) + self.assertEqual(type(err), str) # 应该返回错误信息字符串 def test_forget_password_email_code_success(self): + """测试成功发送忘记密码验证码""" resp = self.client.post( path=reverse("account:forget_password_code"), data=dict(email="admin@admin.com") ) self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.content.decode("utf-8"), "ok") + self.assertEqual(resp.content.decode("utf-8"), "ok") # 断言返回成功消息 def test_forget_password_email_code_fail(self): + """测试忘记密码验证码发送失败的情况""" + # 测试空邮箱参数 resp = self.client.post( path=reverse("account:forget_password_code"), data=dict() ) self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") + # 测试无效邮箱格式 resp = self.client.post( path=reverse("account:forget_password_code"), - data=dict(email="admin@com") + data=dict(email="admin@com") # 无效的邮箱格式 ) self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱") def test_forget_password_email_success(self): + """测试成功重置密码""" code = generate_code() - utils.set_code(self.blog_user.email, code) + utils.set_code(self.blog_user.email, code) # 设置验证码到缓存 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, code=code, ) + # 提交密码重置请求 resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.status_code, 302) # 重置成功应该重定向 # 验证用户密码是否修改成功 blog_user = BlogUser.objects.filter( 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) # 用户应该存在 + self.assertEqual(blog_user.check_password(data["new_password1"]), True) # 密码应该匹配 def test_forget_password_email_not_user(self): + """测试重置密码时用户不存在的情况""" data = dict( new_password1=self.new_test, new_password2=self.new_test, - email="123@123.com", + email="123@123.com", # 不存在的邮箱 code="123456", ) resp = self.client.post( @@ -186,22 +229,21 @@ class AccountTest(TestCase): data=data ) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 200) # 应该返回错误页面而不是重定向 def test_forget_password_email_code_error(self): + """测试重置密码时验证码错误的情况""" code = generate_code() - utils.set_code(self.blog_user.email, code) + utils.set_code(self.blog_user.email, code) # 设置正确的验证码 data = dict( new_password1=self.new_test, new_password2=self.new_test, email=self.blog_user.email, - code="111111", + code="111111", # 错误的验证码 ) resp = self.client.post( path=reverse("account:forget_password"), data=data ) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 200) # 应该返回错误页面而不是重定向 \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/urls.py b/src/DjangoBlog-master/accounts/urls.py index 107a801..4d273a8 100644 --- a/src/DjangoBlog-master/accounts/urls.py +++ b/src/DjangoBlog-master/accounts/urls.py @@ -4,25 +4,46 @@ from django.urls import re_path from . import views from .forms import LoginForm +# 定义应用的命名空间,用于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'), - ] +# URL配置列表,定义所有用户账户相关的路由 +urlpatterns = [ + # 登录路由 - 使用正则表达式匹配以login/结尾的URL + re_path(r'^login/$', + # 使用基于类的视图,登录成功后重定向到首页 + views.LoginView.as_view(success_url='/'), + name='login', # URL名称,用于反向解析 + kwargs={'authentication_form': LoginForm}), # 传递自定义登录表单类 + + # 注册路由 - 使用正则表达式匹配以register/结尾的URL + re_path(r'^register/$', + # 注册视图,注册成功后重定向到首页 + views.RegisterView.as_view(success_url="/"), + name='register'), # URL名称 + + # 登出路由 - 使用正则表达式匹配以logout/结尾的URL + re_path(r'^logout/$', + # 登出视图,处理用户退出登录 + views.LogoutView.as_view(), + name='logout'), # URL名称 + + # 账户操作结果页面 - 使用path匹配精确路径 + path(r'account/result.html', + # 使用函数视图显示账户操作结果(如注册成功、密码重置成功等) + views.account_result, + name='result'), # URL名称 + + # 忘记密码页面 - 使用正则表达式匹配以forget_password/结尾的URL + re_path(r'^forget_password/$', + # 忘记密码视图,显示密码重置页面 + views.ForgetPasswordView.as_view(), + name='forget_password'), # URL名称 + + # 忘记密码验证码接口 - 使用正则表达式匹配以forget_password_code/结尾的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/DjangoBlog-master/accounts/user_login_backend.py b/src/DjangoBlog-master/accounts/user_login_backend.py index 73cdca1..d339c68 100644 --- a/src/DjangoBlog-master/accounts/user_login_backend.py +++ b/src/DjangoBlog-master/accounts/user_login_backend.py @@ -4,23 +4,58 @@ from django.contrib.auth.backends import ModelBackend class EmailOrUsernameModelBackend(ModelBackend): """ - 允许使用用户名或邮箱登录 + 自定义认证后端,允许用户使用用户名或邮箱登录 + Extends ModelBackend to allow authentication using either username or email. """ def authenticate(self, request, username=None, password=None, **kwargs): + """ + 用户认证方法 + Authenticate a user based on username/email and password. + + Args: + request: HTTP请求对象 + username: 用户输入的用户名或邮箱 + password: 用户输入的密码 + **kwargs: 其他参数 + + Returns: + User: 认证成功的用户对象 + None: 认证失败 + """ + # 判断输入的是邮箱还是用户名 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: + # 用户不存在时返回None return None - def get_user(self, username): + def get_user(self, user_id): + """ + 根据用户ID获取用户对象 + Get a user by their primary key. + + Args: + user_id: 用户ID + + Returns: + User: 用户对象 + None: 用户不存在 + """ try: - return get_user_model().objects.get(pk=username) + # 通过主键查找用户 + return get_user_model().objects.get(pk=user_id) except get_user_model().DoesNotExist: - return None + # 用户不存在时返回None + return None \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py index 4b94bdf..3d434df 100644 --- a/src/DjangoBlog-master/accounts/utils.py +++ b/src/DjangoBlog-master/accounts/utils.py @@ -7,43 +7,58 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import send_email +# 验证码的生存时间(Time To Live),设置为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: 接收邮箱地址 + subject: 邮件主题,默认为"Verify Email" + code: 需要发送的验证码 """ + # 生成邮件HTML内容,包含验证码信息,并支持国际化 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]: - """验证code是否有效 + """验证邮箱和验证码是否匹配 Args: - email: 请求邮箱 - code: 验证码 + email: 需要验证的邮箱地址 + code: 用户输入的验证码 Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + 如果验证失败返回错误信息字符串,验证成功返回None + Note: + 当前错误处理方式不够合理,建议改为抛出异常的方式, + 这样调用方可以通过try-except来处理错误,而不是检查返回值 """ + # 从缓存中获取该邮箱对应的验证码 cache_code = get_code(email) + # 比较缓存中的验证码和用户输入的验证码 if cache_code != code: return gettext("Verification code error") + # 验证成功返回None def set_code(email: str, code: str): - """设置code""" + """将验证码存储到缓存中 + Args: + email: 作为缓存键的邮箱地址 + code: 需要存储的验证码 + """ cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) + """从缓存中获取验证码 + Args: + email: 作为缓存键的邮箱地址 + Return: + 返回缓存中的验证码,如果不存在或已过期则返回None + """ + return cache.get(email) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py index ae67aec..9933d75 100644 --- a/src/DjangoBlog-master/accounts/views.py +++ b/src/DjangoBlog-master/accounts/views.py @@ -29,31 +29,47 @@ from .models import BlogUser logger = logging.getLogger(__name__) -# Create your views here. - class RegisterView(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' + + # 构建验证URL 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 +80,8 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + + # 发送验证邮件 send_email( emailto=[ user.email, @@ -71,134 +89,195 @@ 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/' + """ + 用户登出视图 + 处理用户登出操作并清理相关缓存 + """ + url = '/login/' # 登出后重定向的URL @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) # 执行登出操作 + 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 # 一个月的时间 + success_url = '/' # 登录成功后默认重定向的URL + redirect_field_name = REDIRECT_FIELD_NAME # 重定向字段名 + login_ttl = 2626560 # 一个月的时间(秒),用于"记住我"功能 - @method_decorator(sensitive_post_parameters('password')) - @method_decorator(csrf_protect) - @method_decorator(never_cache) + @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): + """向模板上下文添加重定向URL""" 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): + """处理有效的登录表单""" + # 使用Django的AuthenticationForm进行认证 form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): + # 认证成功,清理缓存并记录日志 delete_sidebar_cache() logger.info(self.redirect_field_name) + # 登录用户 auth.login(self.request, form.get_user()) + + # 处理"记住我"功能 if self.request.POST.get("remember"): self.request.session.set_expiry(self.login_ttl) + return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') else: + # 认证失败,重新显示表单 return self.render_to_response({ 'form': form }) def get_success_url(self): - + """获取登录成功后重定向的URL""" redirect_to = self.request.POST.get(self.redirect_field_name) + + # 验证重定向URL的安全性 if not url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts=[ self.request.get_host()]): - redirect_to = self.success_url + redirect_to = self.success_url # 不安全的URL使用默认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 + + # 获取用户对象,如果不存在返回404 user = get_object_or_404(get_user_model(), id=id) logger.info(type) + + # 如果用户已激活,直接重定向到首页 if user.is_active: return HttpResponseRedirect('/') + + # 处理注册和验证操作 if type and type in ['register', 'validation']: if type == 'register': + # 注册成功页面 content = ''' 恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。 ''' 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() + 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): + """ + 忘记密码视图 + 处理密码重置请求 + """ 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() + blog_user.save() # 保存新密码 + + # 重定向到登录页面 return HttpResponseRedirect('/login/') else: + # 表单无效,重新显示表单 return self.render_to_response({'form': form}) class ForgetPasswordEmailCode(View): + """ + 发送忘记密码验证码视图 + 处理密码重置验证码的发送 + """ def post(self, request: HttpRequest): + """处理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) + utils.send_verify_email(to_email, code) # 发送验证邮件 + utils.set_code(to_email, code) # 存储验证码(通常在缓存中) - return HttpResponse("ok") + return HttpResponse("ok") # 返回成功响应 \ No newline at end of file diff --git a/src/DjangoBlog-master/owntracks/admin.py b/src/DjangoBlog-master/owntracks/admin.py index 655b535..50ca1fc 100644 --- a/src/DjangoBlog-master/owntracks/admin.py +++ b/src/DjangoBlog-master/owntracks/admin.py @@ -1,7 +1,27 @@ +# 导入 Django 内置的 Admin 核心模块 +# django.contrib.admin 提供了完整的后台管理界面生成、数据CRUD、权限控制等功能 from django.contrib import admin # Register your models here. - +# 说明:该注释为 Django 自动生成,提示开发者在此处注册需要通过后台管理的模型 +# 注册方式:使用 admin.site.register(模型类, 自定义Admin类) 关联模型与管理配置 class OwnTrackLogsAdmin(admin.ModelAdmin): + """ + 自定义 Admin 配置类:继承自 Django 内置的 ModelAdmin + 作用:配置 OwnTrackLog 模型在后台管理界面的展示形式、操作权限、数据筛选等功能 + 若需扩展后台功能,可在此类中添加属性/方法(如列表显示字段、搜索框、过滤条件等) + """ + # pass 关键字:表示当前类暂未定义额外配置,完全使用 ModelAdmin 的默认行为 + # 默认效果: + # 1. 列表页显示模型的所有字段(id、tid、lat、lon、creation_time) + # 2. 支持点击主键(id)进入详情页编辑数据 + # 3. 支持批量删除、简单搜索(默认搜索主键字段) + # 4. 按模型 Meta 中定义的 ordering 排序(即 creation_time 升序) pass + +# 【注】当前代码缺少模型注册语句,需补充以下代码才能在后台看到该模型(否则配置不生效) +# 需先导入 OwnTrackLog 模型(从对应的 models.py 中),再注册关联 +# 完整注册代码示例: +# from .models import OwnTrackLog # 从当前应用的 models.py 导入模型类 +# admin.site.register(OwnTrackLog, OwnTrackLogsAdmin) # 关联模型与自定义Admin配置 diff --git a/src/DjangoBlog-master/owntracks/apps.py b/src/DjangoBlog-master/owntracks/apps.py index 1bc5f12..e75abb4 100644 --- a/src/DjangoBlog-master/owntracks/apps.py +++ b/src/DjangoBlog-master/owntracks/apps.py @@ -1,5 +1,26 @@ +# 导入 Django 应用配置核心类 AppConfig +# django.apps.AppConfig 是 Django 管理应用元数据的基础类,用于定义应用的名称、初始化逻辑、信号绑定等 from django.apps import AppConfig class OwntracksConfig(AppConfig): + """ + 自定义应用配置类:继承自 Django 内置的 AppConfig + 作用:管理 'owntracks' 应用的核心配置,包括应用名称、初始化行为、模型注册、信号监听等 + 每个 Django 应用建议创建独立的 AppConfig 类,便于后续扩展应用功能(如添加启动时初始化逻辑) + """ + + # 应用名称:Django 识别应用的唯一标识,必须与应用目录名一致(此处为 'owntracks') + # 作用: + # 1. 作为应用的核心标识,用于迁移命令(如 python manage.py migrate owntracks)、权限控制等 + # 2. 关联 models、views、admin 等模块,确保 Django 能正确识别应用内的组件 + # 3. 若需跨应用引用模型,需通过该名称定位(如 from owntracks.models import OwnTrackLog) name = 'owntracks' + + # 【可选扩展配置】若需添加更多应用级配置,可在此处补充(示例): + # 1. 应用verbose名称(后台管理界面显示的应用名称,支持中文) + # verbose_name = '用户轨迹管理' + # 2. 定义应用初始化逻辑(如启动时加载数据、绑定信号) + # def ready(self): + # # 导入信号处理模块(避免循环导入,需在 ready 方法内导入) + # import owntracks.signals \ No newline at end of file diff --git a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py index 9eee55c..23733e5 100644 --- a/src/DjangoBlog-master/owntracks/migrations/0001_initial.py +++ b/src/DjangoBlog-master/owntracks/migrations/0001_initial.py @@ -1,31 +1,64 @@ # Generated by Django 4.1.7 on 2023-03-02 07:14 +# 说明:该文件为 Django 自动生成的数据迁移文件,用于创建数据库表结构 +# 生成条件:执行 makemigrations 命令时,Django 检测到 models.py 中新增 OwnTrackLog 模型后自动生成 +# 兼容版本:Django 4.1.7(迁移文件与 Django 版本强相关,修改需注意兼容性) +# 导入 Django 迁移核心模块和模型字段类 from django.db import migrations, models +# 导入 Django 时区工具(用于处理时间字段的时区一致性) import django.utils.timezone class Migration(migrations.Migration): + """ + 数据迁移类:Django 迁移系统的核心载体,用于定义数据库结构变更逻辑 + 每个迁移类对应一次数据库操作(如建表、改字段、删索引等) + """ + # 标记是否为初始迁移(首次创建模型时为 True,后续修改为 False) initial = True + # 依赖迁移列表:当前迁移依赖的其他迁移文件(为空表示无依赖) + # 若需依赖其他 app 的迁移,格式为 ['其他app名称.迁移文件名前缀'] dependencies = [ ] + # 迁移操作列表:定义具体的数据库变更操作 operations = [ + # 创建数据库表操作:对应 models.py 中的 OwnTrackLog 模型 migrations.CreateModel( + # 模型名称(必须与 models.py 中定义的类名一致) name='OwnTrackLog', + # 字段配置:与模型类中的 field 定义一一对应,决定表的列结构 fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tid', models.CharField(max_length=100, verbose_name='用户')), + # 主键字段:BigAutoField 为自增bigint类型,Django 默认主键类型 + ('id', models.BigAutoField( + auto_created=True, # 自动创建,无需手动赋值 + primary_key=True, # 标记为主键 + serialize=False, # 不序列化(主键默认不参与序列化) + verbose_name='ID' # 后台管理界面显示的字段名称 + )), + # 用户标识字段:CharField 对应数据库 varchar 类型 + ('tid', models.CharField( + max_length=100, # 最大长度100(必填参数) + verbose_name='用户' # 后台显示名称,支持中文 + )), + # 纬度字段:FloatField 对应数据库 float 类型,存储地理纬度(如 39.9042) ('lat', models.FloatField(verbose_name='纬度')), + # 经度字段:FloatField 对应数据库 float 类型,存储地理经度(如 116.4074) ('lon', models.FloatField(verbose_name='经度')), - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + # 创建时间字段:DateTimeField 对应数据库 datetime 类型 + ('created_time', models.DateTimeField( + default=django.utils.timezone.now, # 默认值:当前时区的当前时间 + verbose_name='创建时间' # 后台显示名称 + )), ], + # 模型元数据配置:对应模型类中的 Meta 内部类,影响表的整体属性 options={ - 'verbose_name': 'OwnTrackLogs', - 'verbose_name_plural': 'OwnTrackLogs', - 'ordering': ['created_time'], - 'get_latest_by': 'created_time', + 'verbose_name': 'OwnTrackLogs', # 单数形式的表名称(后台显示) + 'verbose_name_plural': 'OwnTrackLogs', # 复数形式的表名称(后台列表页显示) + 'ordering': ['created_time'], # 默认排序:按创建时间升序排列(-created_time 表示降序) + 'get_latest_by': 'created_time', # 支持使用 Model.objects.latest() 方法,默认按创建时间取最新记录 }, ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py index b4f8dec..1b5dfaf 100644 --- a/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py +++ b/src/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py @@ -1,22 +1,44 @@ # Generated by Django 4.2.5 on 2023-09-06 13:19 +# 说明:该文件为 Django 自动生成的**增量迁移文件**,用于更新数据库表结构 +# 生成条件:修改 models.py 中 OwnTrackLog 模型的 Meta 配置(排序字段、最新记录字段)和字段名(created_time → creation_time)后,执行 makemigrations 命令生成 +# 兼容版本:Django 4.2.5(与初始迁移文件 0001_initial 版本需匹配,避免迁移冲突) +# 核心作用:1. 重命名字段 created_time 为 creation_time;2. 更新模型元数据的排序和最新记录查询字段 +# 导入 Django 迁移核心模块(仅需 migrations,无需额外字段类,因无新增字段) from django.db import migrations class Migration(migrations.Migration): + """ + 数据迁移类:定义数据库结构的增量变更操作 + 本次迁移依赖初始迁移文件,仅修改字段名称和模型元数据,不改变表结构核心逻辑 + """ + # 依赖迁移列表:当前迁移必须在 'owntracks' 应用的 0001_initial 迁移执行后才能运行 + # 格式:['应用名称.迁移文件前缀'],确保迁移顺序正确,避免字段不存在导致的报错 dependencies = [ - ('owntracks', '0001_initial'), + ('owntracks', '0001_initial'), # 依赖初始迁移(创建 OwnTrackLog 表的迁移) ] + # 迁移操作列表:包含两个核心变更操作,按顺序执行 operations = [ + # 操作1:修改模型的元数据配置(对应 models.py 中 OwnTrackLog 类的 Meta 内部类) migrations.AlterModelOptions( - name='owntracklog', - options={'get_latest_by': 'creation_time', 'ordering': ['creation_time'], 'verbose_name': 'OwnTrackLogs', 'verbose_name_plural': 'OwnTrackLogs'}, + name='owntracklog', # 目标模型名称(必须与 models.py 中类名一致) + options={ + 'get_latest_by': 'creation_time', # 更新「查询最新记录」的字段:从 created_time 改为新字段 creation_time + # 影响 Model.objects.latest() 方法的默认查询逻辑 + 'ordering': ['creation_time'], # 更新默认排序字段:从 created_time 改为 creation_time(升序) + 'verbose_name': 'OwnTrackLogs', # 单数显示名称(未修改,与初始迁移一致) + 'verbose_name_plural': 'OwnTrackLogs', # 复数显示名称(未修改) + }, ), + # 操作2:重命名模型的字段(数据库表中对应列名也会同步修改) migrations.RenameField( - model_name='owntracklog', - old_name='created_time', - new_name='creation_time', + model_name='owntracklog', # 目标模型名称 + old_name='created_time', # 旧字段名(原模型中定义的字段名) + new_name='creation_time', # 新字段名(修改后的字段名) + # 说明:该操作会同步更新数据库表中对应的列名(created_time → creation_time),且保留原有字段数据 + # 无需手动处理数据迁移,Django 会自动完成字段名映射和数据保留 ), - ] + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/owntracks/models.py b/src/DjangoBlog-master/owntracks/models.py index 760942c..206522e 100644 --- a/src/DjangoBlog-master/owntracks/models.py +++ b/src/DjangoBlog-master/owntracks/models.py @@ -1,20 +1,54 @@ +# 导入 Django ORM 核心模块:models 用于定义数据模型,对应数据库表结构 from django.db import models +# 导入 Django 时区工具:now() 用于获取当前时区的时间(避免时区不一致问题) from django.utils.timezone import now # Create your models here. +# 说明:该注释为 Django 自动生成,提示开发者在此处定义数据模型类 +# 模型类与数据库表的映射关系:每个模型类对应一张数据库表,类属性对应表字段 class OwnTrackLog(models.Model): - tid = models.CharField(max_length=100, null=False, verbose_name='用户') - lat = models.FloatField(verbose_name='纬度') + """ + 轨迹数据模型类:继承自 Django 内置的 models.Model(所有数据模型的基类) + 核心作用:存储用户的地理轨迹信息(用户标识、经纬度、创建时间) + 映射数据库表名:默认生成规则为「应用名_模型名小写」→ owntracks_owntracklog + """ + + # 1. 用户标识字段:存储用户唯一标识(如设备ID、用户名等) + tid = models.CharField( + max_length=100, # 字段最大长度(CharField 必填参数),适配多数用户标识场景 + null=False, # 数据库层面不允许为空(必填字段),确保数据完整性 + verbose_name='用户' # Django 后台管理界面显示的字段名称(支持中文) + ) + + # 2. 纬度字段:存储地理纬度值(如 39.9042,支持正负值,适配全球地理坐标) + lat = models.FloatField(verbose_name='纬度') # FloatField 对应数据库 float 类型,满足精度需求 + + # 3. 经度字段:存储地理经度值(如 116.4074,与纬度配合定位地理坐标) lon = models.FloatField(verbose_name='经度') - creation_time = models.DateTimeField('创建时间', default=now) + + # 4. 创建时间字段:记录轨迹数据的生成时间 + creation_time = models.DateTimeField( + '创建时间', # verbose_name 的简写形式(第一个参数直接指定后台显示名称) + default=now # 默认值:当前时区的当前时间(now 是可调用对象,每次创建记录时动态获取) + # 注:区别于 datetime.datetime.now(),django.utils.timezone.now() 包含时区信息,符合 Django 时区配置 + ) def __str__(self): + """ + 模型实例的字符串表示方法: + 作用:在 Django 后台、终端打印实例时,显示直观的标识(而非默认的