当前位置: 首页 > news >正文

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

    • 前言
      • 准备工作
      • 第一部分:回顾 Django 内置的 `User` 模型
      • 第二部分:设计并创建 `Role` 和 `UserProfile` 模型
      • 第三部分:创建 Serializers
      • 第四部分:创建 ViewSets
      • 第五部分:注册 API 路由
      • 第六部分:后端初步测试
    • 总结

前言

在上一篇《【用户认证】安全第一步:基于 JWT 的前后端分离认证方案》中,我们成功地为测试平台集成了用户注册和登录功能,并通过 JWT 保护了我们的 API,使用户可以安全地访问平台了。但是,一个成熟的系统通常还需要对用户进行管理,并根据用户的不同职责赋予不同的操作权限。

这篇文章,我们将聚焦于后端基础的搭建

  1. 回顾并利用 Django 内置的 User 模型。
  2. 设计并创建一个自定义的 Role (角色) 模型。
  3. 创建 UserProfile 模型,将其与 User 模型一对一关联,并在 UserProfile 上建立与 Role 模型的多对多关联。
  4. 创建相应的 Serializer 和 ViewSet,提供用户列表和角色管理的基础 API。

为什么需要角色 (Role)?

直接给每个用户单独分配权限会非常繁琐且难以维护。通过引入“角色”的概念,我们可以:

  1. 将一系列相关的权限打包成一个角色 (例如,“测试工程师”、“项目经理”、“管理员”)。
  2. 然后将角色分配给用户。
  3. 当需要调整某类用户的权限时,只需修改对应角色的权限即可,所有拥有该角色的用户权限会自动更新。

这种基于角色的访问控制 (RBAC - Role-Based Access Control) 是一种非常常见且有效的权限管理模型。

准备工作

  1. Django 后端项目已就绪: 确保你的 test-platform/backend 项目结构完整,用户认证(JWT)已配置。
  2. 数据库迁移已同步: 之前的模型更改都已 migrate
  3. Postman 或其他 API 测试工具: 用于测试新创建的 API。
  4. 基础模型 (BaseModel) 已定义。

第一部分:回顾 Django 内置的 User 模型

Django 自带了一个强大的用户认证系统,其核心就是 django.contrib.auth.models.User 模型。这个模型已经包含了我们常用的用户字段,如 username, password, email等。我们在上一篇实现用户注册时,就是通过 UserSerializerUserCreateView 与这个模型交互的。

第二部分:设计并创建 RoleUserProfile 模型

我们将创建自定义的 Role 模型,并通过一个 UserProfile 模型来扩展 Django 内置的 User 模型,并关联角色。

  1. api/models.py 中定义 RoleUserProfile 模型:
    在这里插入图片描述

    在这里插入图片描述

    # test-platform/api/models.py
    import uuid
    from django.db import models
    from django.contrib.auth.models import User # 导入 User 模型# ... (Project, Module, TestCase, TestPlan, TestRun, TestCaseRun 模型保持不变) ...class Role(BaseModel): """角色模型继承自 BaseModel 以获得 name, description, create_time, update_time 字段。"""# permissions 字段将在后续权限篇章中添加# permissions = models.ManyToManyField('Permission', blank=True, verbose_name="角色权限")class Meta:verbose_name = "角色"verbose_name_plural = "角色列表"ordering = ['name'] # 按照名称排序class UserProfile(models.Model):"""用户配置模型,用于扩展内置 User 模型并关联角色"""user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', verbose_name="关联用户")roles = models.ManyToManyField(Role, blank=True, verbose_name="用户角色", related_name="user_profiles")# 你可以在这里添加其他用户扩展字段,例如:# avatar = models.ImageField(upload_to='avatars/', null=True, blank=True, verbose_name="头像")# phone_number = models.CharField(max_length=20, null=True, blank=True, verbose_name="电话号码")class Meta:verbose_name = "用户配置"verbose_name_plural = "用户配置列表"def __str__(self):return self.user.username# 使用信号机制,在创建 User 时自动创建 UserProfile
    from django.db.models.signals import post_save
    from django.dispatch import receiver@receiver(post_save, sender=User)
    def create_or_update_user_profile(sender, instance, created, **kwargs):if created:UserProfile.objects.create(user=instance)profile, profile_created = UserProfile.objects.get_or_create(user=instance)
    

    代码解释:

    • Role 模型:继承自 BaseModel,拥有了名称、描述等通用字段。我们暂时不添加 permissions 字段,将在后续专门的权限篇中处理。
    • UserProfile 模型:
      • user = models.OneToOneField(User, ...): 与 Django 内置的 User 模型建立一对一的关联。这意味着每个 User 对象最多只能有一个 UserProfile 对象,反之亦然。related_name='profile' 允许我们通过 user_instance.profile 来访问其关联的 UserProfile
      • roles = models.ManyToManyField(Role, ...): 将 UserProfileRole 模型建立多对多关系。这样,每个用户 (通过其 Profile) 就可以拥有多个角色。related_name='user_profiles' 允许我们通过 role_instance.user_profiles.all() 来获取拥有该角色的所有用户配置。
    • 信号 (Signal):
      • @receiver(post_save, sender=User): 这是一个 Django 信号接收器。它会在 User 模型实例每次保存 (包括创建和更新) 后被触发。
      • create_or_update_user_profile 函数:
        • if created:: 如果 User 实例是新创建的,则 UserProfile.objects.create(user=instance) 会自动为其创建一个关联的 UserProfile 对象。
        • UserProfile.objects.get_or_create(user=instance): 这是一个更健壮的做法,确保即使用户已存在但由于某种原因没有 Profile,也会为其创建。
  2. 生成并应用数据库迁移:
    在终端中运行:

    python manage.py makemigrations api
    python manage.py migrate
    

在这里插入图片描述

第三部分:创建 Serializers

我们需要为 RoleUser (及其关联的 UserProfile) 创建 Serializer,以便在 API 中进行数据转换。

  1. 创建 RoleSerializer (api/serializers.py):

    在这里插入图片描述

    # test-platform/api/serializers.py
    from rest_framework import serializers
    from .models import Project, Module, TestCase, TestPlan, TestRun, TestCaseRun, Role, UserProfile, User # 确保导入 Role, UserProfile, User
    from django.contrib.auth.models import Userclass RoleSerializer(serializers.ModelSerializer):class Meta:model = Rolefields = ['id', 'name', 'description', 'create_time', 'update_time'] # 基于 BaseModelread_only_fields = ('create_time', 'update_time')
    
  2. 创建/修改 UserSerializer 以处理角色信息 (api/serializers.py):
    我们将创建一个 UserDetailSerializer,用于用户列表的展示和用户信息的更新,其中会包含角色的处理。
    在这里插入图片描述

    # test-platform/api/serializers.py
    from typing import List # 用于类型提示# UserDetailSerializer (用于用户列表展示和更新,包含角色信息)
    class UserDetailSerializer(serializers.ModelSerializer):# 通过 UserProfile 获取角色名称列表,用于 GET 请求展示roles = serializers.SerializerMethodField(read_only=True)# 用于 PUT/PATCH 请求时,接收角色 ID 列表来更新用户的角色role_ids = serializers.ListField(child=serializers.IntegerField(), write_only=True, required=False, # 更新用户时不强制要求传递此字段allow_empty=True, # 允许传递空列表以清空用户角色help_text="用户角色ID列表,用于更新用户角色")class Meta:model = Userfields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'is_staff', 'is_superuser', 'date_joined', 'last_login','roles',      # 用于 GET 时展示角色名称'role_ids'    # 用于 PUT/PATCH 时提交角色 ID)read_only_fields = ('date_joined', 'last_login') # is_superuser 通常不应通过此 API 修改,由 Django admin 或命令行管理# is_staff 可以根据需求决定是否只读def get_roles(self, obj: User) -> List[str]:"""获取用户关联的角色名称列表。obj 是 User 实例。"""# 确保 UserProfile 存在if hasattr(obj, 'profile') and obj.profile is not None:return [role.name for role in obj.profile.roles.all()]return []def update(self, instance: User, validated_data: dict) -> User:"""自定义更新逻辑,以处理密码和角色的更新。"""# 1. 处理密码更新 (如果提供了新密码)password = validated_data.pop('password', None)if password:instance.set_password(password) # 使用 set_password 来正确哈希# 2. 处理角色更新 (如果提供了 role_ids)role_ids = validated_data.pop('role_ids', None)if role_ids is not None: # 检查 role_ids 是否被传递 (即使是空列表)# 确保 UserProfile 存在profile, created = UserProfile.objects.get_or_create(user=instance)profile.roles.set(role_ids) # 使用 set() 方法直接用 ID 列表更新多对多关系# 3. 更新 User 模型的其他普通字段for attr, value in validated_data.items():setattr(instance, attr, value)instance.save()return instance
    

    UserDetailSerializer 关键点解释:

    • roles = serializers.SerializerMethodField(read_only=True): 这个字段用于在API响应中(GET请求)显示用户所拥有的角色名称。它的值由 get_roles 方法提供。
    • get_roles(self, obj: User) -> List[str]: 这个方法接收一个 User 实例 (obj),然后通过 obj.profile.roles.all() 获取该用户关联的所有 Role 对象,并返回这些角色名称的列表。
    • role_ids = serializers.ListField(...): 这个字段用于在API请求中(PUT/PATCH请求)接收一个包含角色ID的列表,以便更新用户的角色分配。
      • write_only=True: 表示这个字段只在反序列化(输入)时使用,不会包含在序列化(输出)的响应中。
      • required=False: 表示在更新用户信息时,这个字段不是必须的。
      • allow_empty=True: 允许传递一个空列表 [],这通常意味着要移除用户的所有角色。
    • update(self, instance: User, validated_data: dict) -> User: 重写了 ModelSerializerupdate 方法以实现自定义的更新逻辑:
      • 密码更新: 如果 validated_data 中包含 password,则使用 instance.set_password(password) 来安全地更新密码(进行哈希处理)。
      • 角色更新: 如果 validated_data 中包含 role_ids,则获取或创建用户的 UserProfile,然后调用 profile.roles.set(role_ids)。Django ORM 的 set() 方法会智能地处理多对多关系的更新:它会移除不再列表中的关联,并添加列表中新增的关联。
      • 其他字段更新: 对于其他普通字段,通过遍历 validated_data.items() 并使用 setattr() 来更新。

第四部分:创建 ViewSets

现在,我们需要为 RoleUser 创建 ViewSet,以提供 API 端点。

  1. 创建 RoleViewSet (api/views.py):
    这个 ViewSet 用于管理角色,通常只有管理员有权限操作。

    在这里插入图片描述
    在这里插入图片描述

    # test-platform/api/views.py
    from .models import Role # 导入 Role
    from .serializers import RoleSerializer # 导入 RoleSerializer
    # ... (其他导入) ...class RoleViewSet(viewsets.ModelViewSet):"""角色管理 API"""queryset = Role.objects.all().order_by('name')serializer_class = RoleSerializerpermission_classes = [permissions.IsAdminUser] # 仅限管理员访问# 如果角色数量不多,可以不设置分页器,以便前端一次性获取所有角色用于选择# pagination_class = None 
    

    权限: permission_classes = [permissions.IsAdminUser] 确保只有 Django 管理员(通常是 is_staff=Trueis_superuser=True 的用户)才能访问这些 API 来管理角色。

  2. 创建 UserViewSet (api/views.py):
    这个 ViewSet 用于管理员管理用户列表、查看用户详情和更新用户信息(包括分配角色)。
    在这里插入图片描述
    在这里插入图片描述

    # test-platform/api/views.py
    from .serializers import UserDetailSerializer # 确保 UserDetailSerializer 已导入
    # ... (其他导入) ...class UserViewSet(viewsets.ModelViewSet):"""用户管理 API (通常仅限管理员访问)"""queryset = User.objects.all().order_by('username').prefetch_related('profile__roles')serializer_class = UserDetailSerializerpermission_classes = [permissions.IsAdminUser] # 仅限管理员访问# 添加过滤、搜索、排序功能filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]filterset_fields = { # 精确匹配'is_active': ['exact'],'is_staff': ['exact'],'profile__roles__id': ['exact'], # 按角色ID筛选用户 (profile__roles__id=role_id)}search_fields = ['username', 'email', 'first_name', 'last_name'] # 模糊搜索ordering_fields = ['username', 'email', 'date_joined', 'last_login']# 通常不通过此ViewSet处理用户创建 (已有 /api/register/)# 如果要禁用创建,可以设置 http_method_names# http_method_names = ['get', 'put', 'patch', 'delete', 'head', 'options']
    

    关键点:

    • queryset = User.objects.all().order_by('username').prefetch_related('profile__roles'):
      • prefetch_related('profile__roles'): 非常重要!这会提前加载每个用户的 Profile 及其关联的所有 Role 对象,避免了在序列化 roles 字段时(通过 get_roles 方法)对每个用户都进行额外的数据库查询(即 N+1 问题),从而显著提高性能。
    • serializer_class = UserDetailSerializer: 使用我们上面定义的,能够处理角色信息的 Serializer。
    • permission_classes = [permissions.IsAdminUser]: 限制只有管理员可以访问。
    • 过滤、搜索和排序: 添加了常用的 filter_backends 以支持前端对用户列表进行更灵活的查询。
      • filterset_fields: 允许通过 is_active, is_staff 以及 profile__roles__id (例如,查找所有拥有某个角色的用户) 进行精确过滤。
      • search_fields: 允许根据用户名、邮箱等进行模糊搜索。
      • ordering_fields: 允许前端指定排序字段。

第五部分:注册 API 路由

将新创建的 RoleViewSetUserViewSet 添加到 API 路由中。

  1. api/urls.py 中注册 ViewSets:
    在这里插入图片描述

    # test-platform/api/urls.py
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from .views import (ProjectViewSet, ModuleViewSet, TestCaseViewSet, TestPlanViewSet,TestRunViewSet, TestCaseRunViewSet,UserCreateView, RoleViewSet, UserViewSet  # 导入新的 ViewSets
    )router = DefaultRouter()router.register(r'projects', ProjectViewSet, basename='project')
    router.register(r'modules', ModuleViewSet, basename='module')
    router.register(r'testcases', TestCaseViewSet, basename='testcase')
    router.register(r'testplans', TestPlanViewSet, basename='testplan')
    router.register(r'testruns', TestRunViewSet, basename='testrun')
    router.register(r'testcaseruns', TestCaseRunViewSet, basename='testcaserun')
    router.register(r'roles', RoleViewSet, basename='role') # 新增角色路由
    router.register(r'users', UserViewSet, basename='user') # 新增用户管理路由urlpatterns = [path('', include(router.urls)),path('register/', UserCreateView.as_view(), name='user_register'),path('', include(router.urls)),  # 用户注册接口保持不变
    ]
    

    现在,我们有了 /api/roles//api/users/ 这两个新的 API 端点。

第六部分:后端初步测试

在进行前端开发之前,强烈建议先通过 API 测试工具(如 Postman、Apifox)来验证后端 API 是否按预期工作。

  1. 启动 Django 开发服务器。

  2. 获取管理员 Token:

    • 你需要一个管理员账户。如果还没有,通过 python manage.py createsuperuser 创建一个。
    • 使用该管理员账户登录 (POST /api/token/) 获取 JWT Access Token。
      在这里插入图片描述
  3. 在后续所有请求的 Authorization 头部添加 Bearer <admin_access_token>

  4. 测试角色 API (/api/roles/):

    • 创建角色 (POST):
      在这里插入图片描述

      • Body: {"name": "Tester", "description": "负责执行测试"}
      • Body: {"name": "Manager", "description": "项目经理"}
      • 应返回 201 Created。记下创建的角色的 ID。
    • 获取角色列表 (GET): 应能看到刚创建的角色。
      在这里插入图片描述

    • 获取单个角色详情 (GET /api/roles/{role_id}/)

    • 更新角色 (PUT/PATCH /api/roles/{role_id}/)
      在这里插入图片描述

    • 删除角色 (DELETE /api/roles/{role_id}/)

  5. 测试用户 API (/api/users/):

    • 获取用户列表 (GET):

      • 你应该能看到所有用户(包括创建的超级用户和其他已注册的用户)。
      • 检查每个用户的响应数据中是否包含一个名为 roles 的字段,其值是一个角色名称的数组)。
    • 获取单个用户详情 (GET /api/users/{user_id}/)

    • 更新用户信息及分配角色 (PUT /api/users/{user_id}/):

      • 选择一个普通用户 (不是超级管理员,除非你确定要修改超管角色) 的 ID。
      • Body:
        {	"username": "blues","email": "blues_C@example.com", "is_active": true,"role_ids": [1, 2] 
        }
        

      在这里插入图片描述

    • 尝试移除用户角色: PUT /api/users/{user_id}/,Body: {"role_ids": []}。再次 GET 用户详情,roles 应为空列表。

    • 测试用户删除 (DELETE /api/users/{user_id}/): 要小心操作。

通过这些测试,你可以确保后端为用户和角色管理提供的基础 API 已经准备就绪。

总结

在本文中,我们成功地为测试平台搭建了用户和角色管理的后端基础:

  • 回顾了 Django 内置的 User 模型的重要性。
  • 创建了自定义的 Role 模型,并通过创建 UserProfile 模型(与 User 一对一关联)的方式,在 UserProfile 上建立了与 Role 的多对多关系。 同时使用信号机制确保了 UserProfile 在用户创建时的自动生成。
  • 定义了 RoleSerializer 用于角色的序列化。
  • 创建了核心的 UserDetailSerializer,它能够:
    • 在读取用户数据时,通过 SerializerMethodFieldget_roles 方法高效地(利用 prefetch_related)显示用户的角色名称列表。
    • 在更新用户数据时,接收一个 role_ids 列表来方便地设置用户的角色分配,并正确处理密码的更新。
  • 创建了 RoleViewSet (仅限管理员访问) 提供对角色的 CRUD 操作。
  • 创建了 UserViewSet (仅限管理员访问) 提供对用户的列表、详情获取和更新 (包括角色分配) 操作,并集成了过滤、搜索和排序功能。
  • 注册了相应的 API 路由 /api/roles//api/users/
  • 指导了如何使用管理员账户通过 API 测试工具验证这些新的后端 API 端点。

我们的后端已经具备了管理用户、角色以及用户与角色之间关联关系的基本能力。这为下一篇《【用户管理与权限 - 篇二】前端交互:用户管理界面的实现》中构建用户友好的管理界面打下了坚实的数据和接口基础。我们将在下一篇中把这些后端能力通过前端页面呈现出来。

相关文章:

  • 【OpenCV】相机标定之利用棋盘格信息标定
  • games101 hw1
  • 2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
  • WebRTC调研
  • CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
  • 初版BL程序一些细节整理(碎碎念)
  • Rust 学习笔记:关于共享状态并发的练习题
  • UNECE R152——解读自动驾驶相关标准法规(AEB)
  • 【向量库】Weaviate概述与架构解析
  • 0day同步!昇思MindSpore框架成功适配面壁MiniCPM4.0模型并上线魔乐社区
  • 树莓派4B, ubuntu20.04, 安装Ros Noetic[踩坑记录]
  • 云原生K8s+Docker+KubeSphere+DevOps
  • K8S认证|CKS题库+答案| 10. Trivy 扫描镜像安全漏洞
  • 数据可视化交互
  • go 里面的指针
  • 盲盒一番赏小程序:引领盲盒新潮流
  • Appuploader:在WindowsLinux上完成iOS APP上架的一种解决方案
  • LeetCode 高频 SQL 50 题(基础版)之 【高级字符串函数 / 正则表达式 / 子句】· 下
  • 手机平板能效生态设计指令EU 2023/1670标准解读
  • 打开网页即可远程控制手机,Linux系统亦可使用
  • 百度如何把网站做链接地址/网络营销公司名称
  • 宁波免费建站seo排名/九江seo优化
  • 武汉SEO网站宣传公司哪家好/怎样在浏览器上找网站
  • 支付网站费怎么做会计分录/北京seo优化费用
  • 个人网站建设方案书/企业员工培训课程内容
  • 梁山网站建设哪家好/北京搜索引擎优化管理专员