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

五、【API 开发篇(下)】:使用 Django REST Framework构建测试用例模型的 CRUD API

【API 开发篇】:使用 Django REST Framework构建测试用例模型的 CRUD API

    • 前言
      • 第一步:增强 Serializers (序列化器) - 处理关联和选择项
      • 第二步:创建 TestCaseViewSet (视图集) - 支持过滤
      • 第三步:注册 TestCaseViewSet 到 Router
      • 第四步:测试 TestCase API
    • 总结

前言

在上一篇项目与模块的 API 开发中,我们掌握了 ModelSerializerModelViewSet 的基本用法,并利用 DefaultRouter 快速生成了 URL。

对于 TestCase 模型,我们可能会遇到以下需求:

  1. 关联数据显示: 在获取测试用例列表或详情时,我们可能希望不仅仅看到所属模块的 ID (module_id),还想直接看到模块的名称,甚至项目的名称。
  2. 写入时处理关联: 创建或更新测试用例时,前端可能会传递模块的 ID,后端需要正确处理这种关联。
  3. 更复杂的字段处理: TestCase 模型中有 choices 类型的字段 (如 priority, case_type),还有可能需要特殊处理的文本字段 (如 steps_text)。
  4. 特定业务逻辑的过滤: 例如,我们可能需要根据项目 ID 来筛选测试用例,或者根据模块 ID 来筛选测试用例。

第一步:增强 Serializers (序列化器) - 处理关联和选择项

我们需要为 TestCase 模型创建一个序列化器,并考虑如何更好地展示关联数据和处理选择项。

打开 api/serializers.py 文件,在 ModuleSerializer 之后添加 TestCaseSerializer
在这里插入图片描述

# test-platform/api/serializers.pyfrom rest_framework import serializers
from .models import Project, Module, TestCase# ... (ProjectSerializer 和 ModuleSerializer 代码保持不变) ...class TestCaseSerializer(serializers.ModelSerializer):"""测试用例序列化器"""# 1. 显示关联对象的详细信息 (只读)# 使用 SerializerMethodField 来自定义序列化输出module_name = serializers.CharField(source='module.name', read_only=True)project_name = serializers.CharField(source='module.project.name', read_only=True)project_id = serializers.IntegerField(source='module.project.id', read_only=True) # 方便前端筛选# 2. 对于 choices 字段,我们可以让前端直接看到可选项的描述文本 (只读)# DRF 默认会返回 choice 的实际存储值 (如 'P0')# 如果需要返回描述文本 (如 'P0 - 最高'),可以使用 `SerializerMethodField` 或 `ChoiceField`# 这里我们选择在前端处理显示,后端保持原始值,但可以添加一个 `get_xxx_display` 的方法到模型中,DRF 会自动识别# 或者,更简单的方式是,让前端直接获取这些 choices,这里我们暂时保持默认。# 如果想在序列化时直接获得 display 值,可以这样做:priority_display = serializers.CharField(source='get_priority_display', read_only=True)case_type_display = serializers.CharField(source='get_case_type_display', read_only=True)class Meta:model = TestCase# fields = '__all__' # 默认会包含 module (仅ID), create_time, update_time 等# 我们明确指定字段,并包含自定义的只读字段fields = ['id', 'name', 'description', 'module', 'module_name', 'project_id', 'project_name','priority', 'priority_display', 'precondition', 'steps_text', 'expected_result','case_type', 'case_type_display', 'maintainer','create_time', 'update_time']# 3. 写入时只接受 module_id# read_only_fields 用于指定哪些字段仅在序列化输出时显示,不在反序列化(创建/更新)时接受输入# 我们已经在自定义字段上加了 read_only=True,这里可以不用再写# read_only_fields = ['module_name', 'project_name', 'project_id', 'priority_display', 'case_type_display', 'create_time', 'update_time']# 如果想在创建/更新时只允许传入 module 的 id,而 module_name 等是只读的,# 并且希望在API文档中明确,可以像下面这样配置 extra_kwargsextra_kwargs = {'create_time': {'read_only': True},'update_time': {'read_only': True},'module': {'write_only': False, 'help_text': "关联的模块ID"}, # module 字段本身可读可写 (ID)}

代码解释:

  1. 显示关联对象的名称 (如 module_name, project_name):

    • module_name = serializers.CharField(source='module.name', read_only=True):
      • 我们定义了一个新的字段 module_name
      • source='module.name' 告诉 DRF 这个字段的值应该从当前 TestCase 实例的 module 属性的 name 属性获取 (即 testcase_instance.module.name)。这利用了 Django 模型反向查询的特性。
      • read_only=True 表示这个字段只用于序列化输出(即 GET 请求的响应),不能用于反序列化输入(即 POST 或 PUT 请求的请求体)。当我们创建或更新测试用例时,我们仍然通过传递 module 字段(模块的 ID)来指定其所属模块。
    • project_name = serializers.CharField(source='module.project.name', read_only=True): 类似地,获取项目名称。
    • project_id = serializers.IntegerField(source='module.project.id', read_only=True): 获取项目ID,方便前端进行筛选或构建链接。
  2. 显示 Choices 字段的描述文本 (如 priority_display):

    • priority_display = serializers.CharField(source='get_priority_display', read_only=True):
      • Django 模型字段如果定义了 choices,会自动拥有一个 get_FIELD_display() 方法(例如,priority 字段有 get_priority_display() 方法)。这个方法会返回该字段当前值的可读描述。
      • 通过 source='get_priority_display',我们可以直接在序列化器中调用这个方法来获取描述文本。
      • read_only=True 同样表示这是只读的。
  3. Meta 类中的配置:

    • fields = [...]: 我们明确列出了所有希望在 API 中暴露的字段,包括我们自定义的只读字段。
    • extra_kwargs:
      • 'module': {'write_only': False, 'help_text': "关联的模块ID"}:
        • write_only=False (默认值) 意味着 module 字段(它代表模块的 ID)在读取和写入时都有效。
        • help_text 会在 DRF 的可浏览 API 界面中显示为提示信息,方便 API 使用者理解。
      • 我们已经为自定义的 xxx_namexxx_display 字段设置了 read_only=True,所以它们自然不会在写入时被接受。
      • create_timeupdate_time 通常也应该是只读的,因为它们由 auto_now_addauto_now 自动管理。

更新 ModuleSerializer 以包含项目名称 (可选但推荐)

为了保持一致性,我们也可以在 ModuleSerializer 中添加 project_name 字段,这样在查看模块列表或详情时也能直接看到项目名称。

修改 api/serializers.py 中的 ModuleSerializer
在这里插入图片描述

# test-platform/api/serializers.py# ... (ProjectSerializer 保持不变) ...class ModuleSerializer(serializers.ModelSerializer):"""模块序列化器"""# 添加 project_name 字段,使其在序列化输出时包含项目名称project_name = serializers.CharField(source='project.name', read_only=True)class Meta:model = Modulefields = ['id', 'name', 'description', 'project', 'project_name', 'create_time', 'update_time']extra_kwargs = {'project': {'write_only': False, 'help_text': "关联的项目ID"},'create_time': {'read_only': True},'update_time': {'read_only': True},}# ... (TestCaseSerializer 保持不变) ...

现在,当获取模块信息时,响应中也会包含 project_name

第二步:创建 TestCaseViewSet (视图集) - 支持过滤

接下来,创建 TestCaseViewSet。我们将继承 ModelViewSet 并可能添加一些自定义的过滤逻辑。

打开 api/views.py 文件,添加 TestCaseViewSet
在这里插入图片描述

# test-platform/api/views.pyfrom rest_framework import viewsets
# 如果需要更细致的权限控制,可以导入 permissions
# from rest_framework import permissions
from .models import Project, Module, TestCase
from .serializers import ProjectSerializer, ModuleSerializer, TestCaseSerializer # 导入 TestCaseSerializer# ... (ProjectViewSet 和 ModuleViewSet 代码保持不变) ...class TestCaseViewSet(viewsets.ModelViewSet):"""测试用例管理视图集提供用例列表、创建、详情、更新、删除等接口支持通过查询参数 `module_id` 或 `project_id` 进行过滤"""queryset = TestCase.objects.all() # 默认查询所有测试用例serializer_class = TestCaseSerializer# permission_classes = [permissions.IsAuthenticated] # 示例:可以添加权限控制,要求用户已登录# 自定义 get_queryset 方法以支持动态过滤def get_queryset(self):"""重写get_queryset方法,根据请求参数动态过滤查询集"""queryset = super().get_queryset() # 获取基础查询集# 1. 根据 module_id 过滤module_id = self.request.query_params.get('module_id', None)if module_id is not None:# 确保 module_id 是有效的整数try:module_id = int(module_id)queryset = queryset.filter(module_id=module_id)except ValueError:# 如果 module_id 不是有效的整数,可以忽略或返回错误pass # 或者 raise serializers.ValidationError("module_id 必须是整数")# 2. 根据 project_id 过滤 (通过模块关联到项目)project_id = self.request.query_params.get('project_id', None)if project_id is not None:# 确保 project_id 是有效的整数try:project_id = int(project_id)# TestCase -> Module -> Projectqueryset = queryset.filter(module__project_id=project_id)except ValueError:passreturn queryset.order_by('-create_time') # 默认按创建时间降序

代码解释:

  1. queryset = TestCase.objects.all(): 默认情况下,视图集将操作所有 TestCase 对象。
  2. serializer_class = TestCaseSerializer: 指定使用我们刚刚创建的 TestCaseSerializer
  3. def get_queryset(self):: 我们重写了 ModelViewSetget_queryset 方法。这个方法在每次需要获取查询集(例如,列表视图或详情视图)时都会被调用。
    • queryset = super().get_queryset(): 首先调用父类的 get_queryset 方法获取基础查询集(即 TestCase.objects.all())。
    • module_id = self.request.query_params.get('module_id', None):
      • self.request 是 DRF 封装的 HTTP 请求对象。
      • query_params 是一个类似字典的对象,包含了 URL 中的查询参数 (例如 ?module_id=1&name=test)。
      • .get('module_id', None) 尝试获取名为 module_id 的查询参数,如果不存在则返回 None
    • if module_id is not None: ... queryset = queryset.filter(module_id=module_id): 如果 module_id 参数存在,就使用 Django ORM 的 filter() 方法来筛选出属于该模块的测试用例。
    • project_id = self.request.query_params.get('project_id', None): 类似地获取 project_id 参数。
    • if project_id is not None: ... queryset = queryset.filter(module__project_id=project_id):
      • 如果 project_id 参数存在,我们使用 module__project_id=project_id 来进行过滤。
      • 这里的 module__project_id 是 Django ORM 的跨关系查询语法,意思是“通过 TestCasemodule 字段,找到关联的 Module 对象,再通过该 Module 对象的 project 字段,找到关联的 Project 对象,并筛选出其 id 等于 project_id 的那些 TestCase”。
    • return queryset.order_by('-create_time'): 最后返回经过筛选和排序的查询集。

通过这种方式,我们的 /api/testcases/ 端点现在可以接受 module_idproject_id 作为查询参数来进行动态过滤。例如:

  • /api/testcases/?module_id=5:获取模块 ID 为 5 的所有测试用例。
  • /api/testcases/?project_id=2:获取项目 ID 为 2 的所有测试用例。
  • /api/testcases/?project_id=2&module_id=5:获取项目 ID 为 2 且模块 ID 为 5 的所有测试用例 (虽然此时 project_id 是冗余的,因为模块已确定项目)。

第三步:注册 TestCaseViewSet 到 Router

现在,我们需要将新的 TestCaseViewSet 添加到我们的 URL 路由中。
在这里插入图片描述

打开 api/urls.py 文件,修改如下:

# test-platform/api/urls.pyfrom django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProjectViewSet, ModuleViewSet, TestCaseViewSet # 导入 TestCaseViewSet# 创建一个 DefaultRouter 实例
router = DefaultRouter()# 注册视图集
router.register(r'projects', ProjectViewSet, basename='project')
router.register(r'modules', ModuleViewSet, basename='module')
router.register(r'testcases', TestCaseViewSet, basename='testcase') # 新增 TestCaseViewSet 注册urlpatterns = [path('', include(router.urls)),
]

我们只是在 router.register(...) 中添加了一行来注册 TestCaseViewSet。DRF 的 Router 会自动为它生成所有标准的 CRUD URL。

第四步:测试 TestCase API

万事俱备,只欠测试!

  1. 确保数据库中有一些关联数据:

    • 如果你还没有,请先通过 Django Admin (http://127.0.0.1:8000/admin/) 或之前创建的 Project/Module API (http://127.0.0.1:8000/api/projects/http://127.0.0.1:8000/api/modules/) 创建至少:
      • 一个项目 (例如,Project A, ID=1)
      • 在该项目下的一个模块 (例如,Module X under Project A, ID=1, project=1)
      • 另一个项目 (例如,Project B, ID=2)
      • 在该项目下的一个模块 (例如,Module Y under Project B, ID=2, project=2)
  2. 启动 Django 开发服务器:

    python manage.py runserver
    
  3. 访问 DRF 的可浏览 API 界面:
    在浏览器中访问 http://127.0.0.1:8000/api/。你能看到新添加的 testcases API 端点。
    在这里插入图片描述

  4. 测试 TestCase API - GET /api/testcases/ (列表):
    点击 http://127.0.0.1:8000/api/testcases/ 链接。

    • 注意看响应中,会有之前数据,会包含我们定义的 module_name, project_name, priority_display 等字段。
      在这里插入图片描述
  5. 测试 TestCase API - POST /api/testcases/ (创建):
    http://127.0.0.1:8000/api/testcases/ 页面的底部表单中:

    • 输入名称、描述、选择所属模块等字段,
    • 点击 “POST”。

    在这里插入图片描述

    如果成功,你会看到返回的创建后的用例数据,其中包含了 module_name, project_name 等。并且列表会刷新。
    在这里插入图片描述

  6. 测试 TestCase API - GET /api/testcases/?module_id={id} (按模块过滤):

    • 假设你创建的 Module X 的 ID 是 1。在浏览器地址栏输入 http://127.0.0.1:8000/api/testcases/?module_id=1 并回车。
    • 你只能看到属于 Module 1 的测试用例。
      在这里插入图片描述
  7. 测试 TestCase API - GET /api/testcases/?project_id={id} (按项目过滤):

    • 假设你创建的 Project A 的 ID 是 1。在浏览器地址栏输入 http://127.0.0.1:8000/api/testcases/?project_id=1 并回车。
    • 你只能看到属于 Project 1 下所有模块的测试用例。
      在这里插入图片描述
  8. 测试其他操作:

    • GET /api/testcases/{id}/ (详情): 点击列表中的某个用例链接。
    • PUT /api/testcases/{id}/ (更新): 在详情页面修改数据并提交。
    • PATCH /api/testcases/{id}/ (部分更新): 类似 PUT,但只提供需要修改的字段。
    • DELETE /api/testcases/{id}/ (删除): 在详情页面点击删除按钮。

通过这些测试,你能验证 TestCase API 的所有功能,包括关联数据显示和动态过滤。

总结

在这篇文章中,我们成功地为 TestCase 模型构建了功能更完善的 API 接口:

  • ✅ 增强了 TestCaseSerializer,使其能够:
    • 通过 source 参数和模型方法 (get_FIELD_display) 显示关联对象的名称和 choices 字段的可读描述。
    • 明确了哪些字段是只读的,哪些是可写的。
  • ✅ 更新了 ModuleSerializer 以包含 project_name
  • ✅ 创建了 TestCaseViewSet,并重写了 get_queryset 方法,以支持根据 module_idproject_id URL 查询参数进行动态过滤。
  • ✅ 将 TestCaseViewSet 注册到了 DRF Router 中。
  • ✅ 通过 DRF 的可浏览 API 界面全面测试了 TestCase API 的创建、读取(包括过滤)、更新和删除功能。

至此,我们测试平台的后端核心数据(项目、模块、测试用例)的 CRUD API 已经基本完成!这些 API 将为我们接下来的前端开发提供坚实的数据基础。

http://www.xdnf.cn/news/7797.html

相关文章:

  • 云原生安全之PaaS:从基础到实践的技术指南
  • 谈谈 Kotlin 中的构造方法,有哪些注意事项?
  • 【Django系统】Python+Django携程酒店评论情感分析系统
  • 【Java微服务组件】异步通信P2—Kafka与消息
  • [杂学笔记]浏览器多进程与多线程架构、wstring类型、哈希表、红黑树与哈希表的对比、C++标准库Random类
  • 影响镍钯金PCB表面处理价格的因素有哪些?
  • Spring事务简单操作
  • 【低代码】如何使用明道云调用 Flask 视图函数并传参(POST 方法实践)
  • vue-cli 构建打包优化(JeecgBoot-Vue2 配置优化篇)
  • Hadoop-HA高可用集群启动nameNode莫名挂掉,排错解决
  • digitalworld.local: FALL靶场
  • Mysql-数据闪回工具MyFlash
  • SQL查询, 响应体临时字段报: Unknown column ‘data_json_map‘ in ‘field list‘
  • leetcode 92. Reverse Linked List II
  • 张 Prompt Tuning--中文数据准确率提升:理性与冲动识别新突破
  • 分类算法 Kmeans、KNN、Meanshift 实战
  • maven之pom.xml
  • 【25软考网工】第七章(3) UOS Linux防火墙配置和Web应用服务配置
  • OpenHarmony外设驱动使用 (九),Pin_auth
  • 国产化Excel处理组件Spire.XLS for .NET系列教程:通过 C# 将 TXT 文本转换为 Excel 表格
  • 物业后勤小程序源码介绍
  • 【项目记录】准备工作及查询部门
  • python-leetcode 71.每日温度
  • Vue 3.0中核心的Composition API
  • 打造一个支持MySQL查询的MCP同步插件:Java实现
  • PCB智能报价系统——————仙盟创梦IDE
  • Python实例题:PyOt实现简易浏览器
  • leetcode字符串篇【公共前缀】:14-最长公共前缀
  • C语言-9.指针
  • “交互式“ PDF 与“静态“ PDF 表单的区别