Django接口自动化平台实现(四)

5. 用例集合

预期效果如下:

5.1 定义模型类

1)models.py新增 case_suite 模型类

复制代码
 1 from django.db import models
 2 from smart_selects.db_fields import GroupedForeignKey  # pip install django-smart-selects:后台级联选择
 3 from django.contrib.auth.models import User
 4 
 5 
 6 class Project(models.Model):
 7     id = models.AutoField(primary_key=True)
 8     name = models.CharField('项目名称', max_length=50, unique=True, null=False)
 9     proj_owner = models.CharField('项目负责人', max_length=20, null=False)
10     test_owner = models.CharField('测试负责人', max_length=20, null=False)
11     dev_owner = models.CharField('开发负责人', max_length=20, null=False)
12     desc = models.CharField('项目描述', max_length=100, null=True)
13     create_time = models.DateTimeField('项目创建时间', auto_now_add=True)
14     update_time = models.DateTimeField('项目更新时间', auto_now=True, null=True)
15 
16     def __str__(self):
17         return self.name
18 
19     class Meta:
20         verbose_name = '项目信息表'
21         verbose_name_plural = '项目信息表'
22 
23 
24 class Module(models.Model):
25     id = models.AutoField(primary_key=True)
26     name = models.CharField('模块名称', max_length=50, null=False)
27     belong_project = models.ForeignKey(Project, on_delete=models.CASCADE)
28     test_owner = models.CharField('测试负责人', max_length=50, null=False)
29     desc = models.CharField('简要描述', max_length=100, null=True)
30     create_time = models.DateTimeField('创建时间', auto_now_add=True)
31     update_time = models.DateTimeField('更新时间', auto_now=True, null=True)
32 
33     def __str__(self):
34         return self.name
35 
36     class Meta:
37         verbose_name = '模块信息表'
38         verbose_name_plural = '模块信息表'
39 
40 
41 class TestCase(models.Model):
42     id = models.AutoField(primary_key=True)
43     case_name = models.CharField('用例名称', max_length=50, null=False)  # 如 register
44     belong_project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所属项目')
45     belong_module = GroupedForeignKey(Module, "belong_project", on_delete=models.CASCADE, verbose_name='所属模块')
46     request_data = models.CharField('请求数据', max_length=1024, null=False, default='')
47     uri = models.CharField('接口地址', max_length=1024, null=False, default='')
48     assert_key = models.CharField('断言内容', max_length=1024, null=True)
49     maintainer = models.CharField('编写人员', max_length=1024, null=False, default='')
50     extract_var = models.CharField('提取变量表达式', max_length=1024, null=True)  # 示例:userid||userid": (\d+)
51     request_method = models.CharField('请求方式', max_length=1024, null=True)
52     status = models.IntegerField(null=True, help_text="0:表示有效,1:表示无效,用于软删除")
53     created_time = models.DateTimeField('创建时间', auto_now_add=True)
54     updated_time = models.DateTimeField('更新时间', auto_now=True, null=True)
55     user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='责任人', null=True)
56 
57     def __str__(self):
58         return self.case_name
59 
60     class Meta:
61         verbose_name = '测试用例表'
62         verbose_name_plural = '测试用例表'
63 
64 
65 class CaseSuite(models.Model):
66     id = models.AutoField(primary_key=True)
67     suite_desc = models.CharField('用例集合描述', max_length=100, blank=True, null=True)
68     if_execute = models.IntegerField(verbose_name='是否执行', null=False, default=0, help_text='0:执行;1:不执行')
69     test_case_model = models.CharField('测试执行模式', max_length=100, blank=True, null=True, help_text='data/keyword')
70     creator = models.CharField(max_length=50, blank=True, null=True)
71     create_time = models.DateTimeField('创建时间', auto_now=True)  # 创建时间-自动获取当前时间
72 
73     class Meta:
74         verbose_name = '用例集合表'
75         verbose_name_plural = '用例集合表'

2)数据迁移

在项目目录下,执行以下两个命令进行数据迁移(将模型类转换成数据库表):

复制代码
python manage.py makemigrations  # 生成迁移文件(模型类的信息)
python manage.py migrate  # 执行开始迁移(将模型类信息转换成数据库表)

5.2 后台 admin 添加数据

1)注册模型类到 admin.py

复制代码
 1 from django.contrib import admin
 2 from .import models
 3 
 4 
 5 class ProjectAdmin(admin.ModelAdmin):
 6     list_display = ("id", "name", "proj_owner", "test_owner", "dev_owner", "desc", "create_time", "update_time")
 7 
 8 admin.site.register(models.Project, ProjectAdmin)
 9 
10 
11 class ModuleAdmin(admin.ModelAdmin):
12     list_display = ("id", "name", "belong_project", "test_owner", "desc", "create_time", "update_time")
13 
14 admin.site.register(models.Module, ModuleAdmin)
15 
16 
17 class TestCaseAdmin(admin.ModelAdmin):
18     list_display = (
19         "id", "case_name", "belong_project", "belong_module", "request_data", "uri", "assert_key", "maintainer",
20         "extract_var", "request_method", "status", "created_time", "updated_time", "user")
21 
22 admin.site.register(models.TestCase, TestCaseAdmin)
23 
24 
25 class CaseSuiteAdmin(admin.ModelAdmin):
26     list_display = ("id", "suite_desc", "creator", "create_time")
27 
28 admin.site.register(models.CaseSuite, CaseSuiteAdmin)

2)登录 admin 系统,进入用例集合表,添加数据:

5.3 定义路由

复制代码
from django.urls import path, re_path
from . import views


urlpatterns = [
    path('', views.index),
    path('login/', views.login),
    path('logout/', views.logout),
    path('project/', views.project, name='project'),
    path('module/', views.module, name='module'),
    path('test_case/', views.test_case, name="test_case"),
    re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),
    re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),
    path('test_suite/', views.test_suite, name="test_suite"),
]

5.4 定义视图

复制代码
  1 from django.shortcuts import render, redirect, HttpResponse
  2 from django.contrib import auth  # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
  3 from django.contrib.auth.decorators import login_required
  4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
  5 from .form import UserForm
  6 import traceback
  7 from .models import Project, Module, TestCase, CaseSuite
  8 
  9 
 10 # 封装分页处理
 11 def get_paginator(request, data):
 12     paginator = Paginator(data, 10)  # 默认每页展示10条数据
 13     # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
 14     page = request.GET.get('page')
 15     try:
 16         paginator_pages = paginator.page(page)
 17     except PageNotAnInteger:
 18         # 如果请求的页数不是整数, 返回第一页。
 19         paginator_pages = paginator.page(1)
 20     except InvalidPage:
 21         # 如果请求的页数不存在, 重定向页面
 22         return HttpResponse('找不到页面的内容')
 23     return paginator_pages
 24 
 25 
 26 # 项目菜单
 27 @login_required
 28 def project(request):
 29     print("request.user.is_authenticated: ", request.user.is_authenticated)
 30     projects = Project.objects.filter().order_by('-id')
 31     print("projects:", projects)
 32     return render(request, 'project.html', {'projects': get_paginator(request, projects)})
 33 
 34 
 35 # 模块菜单
 36 @login_required
 37 def module(request):
 38     if request.method == "GET":  # 请求get时候,id倒序查询所有的模块数据
 39         modules = Module.objects.filter().order_by('-id')
 40         return render(request, 'module.html', {'modules': get_paginator(request, modules)})
 41     else:  # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目
 42         proj_name = request.POST['proj_name']
 43         projects = Project.objects.filter(name__contains=proj_name.strip())
 44         projs = [proj.id for proj in projects]
 45         modules = Module.objects.filter(belong_project__in=projs)  # 把项目中所有的模块都找出来
 46         return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
 47 
 48 
 49 # 测试用例菜单
 50 @login_required
 51 def test_case(request):
 52     print("request.session['is_login']: {}".format(request.session['is_login']))
 53     test_cases = ""
 54     if request.method == "GET":
 55         test_cases = TestCase.objects.filter().order_by('id')
 56         print("testcases in testcase: {}".format(test_cases))
 57     elif request.method == "POST":
 58         print("request.POST: {}".format(request.POST))
 59         test_case_id_list = request.POST.getlist('testcases_list')
 60         if test_case_id_list:
 61             test_case_id_list.sort()
 62             print("test_case_id_list: {}".format(test_case_id_list))
 63         test_cases = TestCase.objects.filter().order_by('id')
 64     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
 65 
 66 
 67 # 用例详情页
 68 @login_required
 69 def test_case_detail(request, test_case_id):
 70     test_case_id = int(test_case_id)
 71     test_case = TestCase.objects.get(id=test_case_id)
 72     print("test_case: {}".format(test_case))
 73     print("test_case.id: {}".format(test_case.id))
 74     print("test_case.belong_project: {}".format(test_case.belong_project))
 75 
 76     return render(request, 'test_case_detail.html', {'test_case': test_case})
 77 
 78 
 79 # 模块页展示测试用例
 80 @login_required
 81 def module_test_cases(request, module_id):
 82     module = ""
 83     if module_id:  # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现
 84         module = Module.objects.get(id=int(module_id))
 85     test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
 86     print("test_case in module_test_cases: {}".format(test_cases))
 87     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
 88 
 89 
 90 @login_required
 91 def case_suite(request):
 92     case_suites = CaseSuite.objects.filter()
 93     return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
 94 
 95 
 96 # 默认页的视图函数
 97 @login_required
 98 def index(request):
 99     return render(request, 'index.html')
100 
101 
102 # 登录页的视图函数
103 def login(request):
104     print("request.session.items(): {}".format(request.session.items()))  # 打印session信息
105     if request.session.get('is_login', None):
106         return redirect('/')
107     # 如果是表单提交行为,则进行登录校验
108     if request.method == "POST":
109         login_form = UserForm(request.POST)
110         message = "请检查填写的内容!"
111         if login_form.is_valid():
112             username = login_form.cleaned_data['username']
113             password = login_form.cleaned_data['password']
114             try:
115                 # 使用django提供的身份验证功能
116                 user = auth.authenticate(username=username, password=password)  # 从auth_user表中匹配信息,匹配到则返回用户对象
117                 if user is not None:
118                     print("用户【%s】登录成功" % username)
119                     auth.login(request, user)
120                     request.session['is_login'] = True
121                     # 登录成功,跳转主页
122                     return redirect('/')
123                 else:
124                     message = "用户名不存在或者密码不正确!"
125             except:
126                 traceback.print_exc()
127                 message = "登录程序出现异常"
128         # 用户名或密码为空,返回登录页和错误提示信息
129         else:
130             return render(request, 'login.html', locals())
131     # 不是表单提交,代表只是访问登录页
132     else:
133         login_form = UserForm()
134         return render(request, 'login.html', locals())
135 
136 
137 # 注册页的视图函数
138 def register(request):
139     return render(request, 'register.html')
140 
141 
142 # 登出的视图函数:重定向至login视图函数
143 @login_required
144 def logout(request):
145     auth.logout(request)
146     request.session.flush()
147     return redirect("/login/")

5.5 定义模板

新增 templates/case_suite.html 模板:

复制代码
 1 {% extends 'base.html' %}
 2 {% load static %}
 3 {% block title %}测试集合{% endblock %}
 4 {% block content %}
 5 <form action="" method="POST">
 6     {% csrf_token %}
 7 
 8     <div class="table-responsive">
 9         <table class="table table-striped">
10             <thead>
11             <tr>
12                 <th>id</th>
13                 <th>测试集合名称</th>
14                 <th>创建者</th>
15                 <th>创建时间</th>
16                 <th>查看/删除测试用例</th>
17                 <th>添加测试用例</th>
18                 <th>用例集合执行结果</th>
19             </tr>
20             </thead>
21             <tbody>
22 
23             {% for case_suite in case_suites %}
24             <tr>
25                 <td>{{ case_suite.id }}</td>
26                 <td>{{ case_suite.suite_desc }}</td>
27                 <td>{{ case_suite.creator }}</td>
28                 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td>
29                 <td><a href="">查看/删除测试用例</a></td>
30                 <td><a href="">添加测试用例</a></td>
31                 <td><a href="">查看用例集合执行结果</a></td>
32             </tr>
33             {% endfor %}
34             </tbody>
35         </table>
36     </div>
37 </form>
38 
39 {# 实现分页标签的代码 #}
40 {# 这里使用 bootstrap 渲染页面 #}
41 <div id="pages" class="text-center">
42     <nav>
43         <ul class="pagination">
44             <li class="step-links">
45                 {% if case_suites.has_previous %}
46                 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a>
47                 {% endif %}
48 
49                 <span class="current">
50                     第 {{ case_suites.number }} 页/共 {{ case_suites.paginator.num_pages }} 页</span>
51 
52                 {% if case_suites.has_next %}
53                 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a>
54                 {% endif %}
55             </li>
56         </ul>
57     </nav>
58 </div>
59 {% endblock %}

2)修改 base 模板:菜单栏新增"用例集合"。

复制代码
 1 <!DOCTYPE html>
 2 <html lang="zh-CN">
 3 {% load static %}
 4 <head>
 5     <meta charset="utf-8">
 6     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 7     <meta name="viewport" content="width=device-width, initial-scale=1">
 8     <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
 9     <title>{% block title %}base{% endblock %}</title>
10 
11     <!-- Bootstrap -->
12     <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
13 
14 
15     <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
16     <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
17     <!--[if lt IE 9]>
18     <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
19     <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
20     <![endif]-->
21     {% block css %}{% endblock %}
22 </head>
23 <body>
24 <nav class="navbar navbar-default">
25     <div class="container-fluid">
26         <!-- Brand and toggle get grouped for better mobile display -->
27         <div class="navbar-header">
28             <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav"
29                     aria-expanded="false">
30                 <span class="sr-only">切换导航条</span>
31                 <span class="icon-bar"></span>
32                 <span class="icon-bar"></span>
33                 <span class="icon-bar"></span>
34             </button>
35             <a class="navbar-brand" href="/">自动化测试平台</a>
36         </div>
37 
38         <div class="collapse navbar-collapse" id="my-nav">
39             <ul class="nav navbar-nav">
40                 <li class="active"><a href="/project/">项目</a></li>
41                 <li class="active"><a href="/module/">模块</a></li>
42                 <li class="active"><a href="/test_case/">测试用例</a></li>
43                 <li class="active"><a href="/case_suite/">用例集合</a></li>
44             </ul>
45             <ul class="nav navbar-nav navbar-right">
46                 {% if request.user.is_authenticated %}
47                 <li><a href="#">当前在线:{{ request.user.username }}</a></li>
48                 <li><a href="/logout">登出</a></li>
49                 {% else %}
50                 <li><a href="/login">登录</a></li>
51 
52                 {% endif %}
53             </ul>
54         </div><!-- /.navbar-collapse -->
55     </div><!-- /.container-fluid -->
56 </nav>
57 
58 {% block content %}{% endblock %}
59 
60 
61 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
62 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
63 <!-- Include all compiled plugins (below), or include individual files as needed -->
64 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
65 </body>
66 </html>

6. 用例集合添加测试用例

预期效果如下:

6.1 定义模型类

1)在 models.py 中,增加模型类 SuiteCase,记录用例集合所关联的用例。

复制代码
 1 from django.db import models
 2 from smart_selects.db_fields import GroupedForeignKey  # pip install django-smart-selects:后台级联选择
 3 from django.contrib.auth.models import User
 4 
 5 
 6 class Project(models.Model):
 7     id = models.AutoField(primary_key=True)
 8     name = models.CharField('项目名称', max_length=50, unique=True, null=False)
 9     proj_owner = models.CharField('项目负责人', max_length=20, null=False)
10     test_owner = models.CharField('测试负责人', max_length=20, null=False)
11     dev_owner = models.CharField('开发负责人', max_length=20, null=False)
12     desc = models.CharField('项目描述', max_length=100, null=True)
13     create_time = models.DateTimeField('项目创建时间', auto_now_add=True)
14     update_time = models.DateTimeField('项目更新时间', auto_now=True, null=True)
15 
16     def __str__(self):
17         return self.name
18 
19     class Meta:
20         verbose_name = '项目信息表'
21         verbose_name_plural = '项目信息表'
22 
23 
24 class Module(models.Model):
25     id = models.AutoField(primary_key=True)
26     name = models.CharField('模块名称', max_length=50, null=False)
27     belong_project = models.ForeignKey(Project, on_delete=models.CASCADE)
28     test_owner = models.CharField('测试负责人', max_length=50, null=False)
29     desc = models.CharField('简要描述', max_length=100, null=True)
30     create_time = models.DateTimeField('创建时间', auto_now_add=True)
31     update_time = models.DateTimeField('更新时间', auto_now=True, null=True)
32 
33     def __str__(self):
34         return self.name
35 
36     class Meta:
37         verbose_name = '模块信息表'
38         verbose_name_plural = '模块信息表'
39 
40 
41 class TestCase(models.Model):
42     id = models.AutoField(primary_key=True)
43     case_name = models.CharField('用例名称', max_length=50, null=False)  # 如 register
44     belong_project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所属项目')
45     belong_module = GroupedForeignKey(Module, "belong_project", on_delete=models.CASCADE, verbose_name='所属模块')
46     request_data = models.CharField('请求数据', max_length=1024, null=False, default='')
47     uri = models.CharField('接口地址', max_length=1024, null=False, default='')
48     assert_key = models.CharField('断言内容', max_length=1024, null=True)
49     maintainer = models.CharField('编写人员', max_length=1024, null=False, default='')
50     extract_var = models.CharField('提取变量表达式', max_length=1024, null=True)  # 示例:userid||userid": (\d+)
51     request_method = models.CharField('请求方式', max_length=1024, null=True)
52     status = models.IntegerField(null=True, help_text="0:表示有效,1:表示无效,用于软删除")
53     created_time = models.DateTimeField('创建时间', auto_now_add=True)
54     updated_time = models.DateTimeField('更新时间', auto_now=True, null=True)
55     user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='责任人', null=True)
56 
57     def __str__(self):
58         return self.case_name
59 
60     class Meta:
61         verbose_name = '测试用例表'
62         verbose_name_plural = '测试用例表'
63 
64 
65 class CaseSuite(models.Model):
66     id = models.AutoField(primary_key=True)
67     suite_desc = models.CharField('用例集合描述', max_length=100, blank=True, null=True)
68     if_execute = models.IntegerField(verbose_name='是否执行', null=False, default=0, help_text='0:执行;1:不执行')
69     test_case_model = models.CharField('测试执行模式', max_length=100, blank=True, null=True, help_text='data/keyword')
70     creator = models.CharField(max_length=50, blank=True, null=True)
71     create_time = models.DateTimeField('创建时间', auto_now=True)  # 创建时间-自动获取当前时间
72 
73     class Meta:
74         verbose_name = "用例集合表"
75         verbose_name_plural = '用例集合表'
76 
77 
78 class SuiteCase(models.Model):
79     id = models.AutoField(primary_key=True)
80     case_suite = models.ForeignKey(CaseSuite, on_delete=models.CASCADE, verbose_name='用例集合')
81     test_case = models.ForeignKey(TestCase, on_delete=models.CASCADE, verbose_name='测试用例')
82     status = models.IntegerField(verbose_name='是否有效', null=False, default=1, help_text='0:有效,1:无效')
83     create_time = models.DateTimeField('创建时间', auto_now=True)  # 创建时间-自动获取当前时间

2)数据迁移

在项目目录下,执行以下两个命令进行数据迁移(将模型类转换成数据库表):

复制代码
python manage.py makemigrations  # 生成迁移文件(模型类的信息)
python manage.py migrate  # 执行开始迁移(将模型类信息转换成数据库表)

6.2 定义路由

复制代码
from django.urls import path, re_path
from . import views


urlpatterns = [
    path('', views.index),
    path('login/', views.login),
    path('logout/', views.logout),
    path('project/', views.project, name='project'),
    path('module/', views.module, name='module'),
    path('test_case/', views.test_case, name="test_case"),
    re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),
    re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),
    path('case_suite/', views.case_suite, name="case_suite"),
    re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),
]

6.3 定义视图

复制代码
  1 from django.shortcuts import render, redirect, HttpResponse
  2 from django.contrib import auth  # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
  3 from django.contrib.auth.decorators import login_required
  4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
  5 from .form import UserForm
  6 import traceback
  7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase
  8 
  9 
 10 # 封装分页处理
 11 def get_paginator(request, data):
 12     paginator = Paginator(data, 10)  # 默认每页展示10条数据
 13     # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
 14     page = request.GET.get('page')
 15     try:
 16         paginator_pages = paginator.page(page)
 17     except PageNotAnInteger:
 18         # 如果请求的页数不是整数, 返回第一页。
 19         paginator_pages = paginator.page(1)
 20     except InvalidPage:
 21         # 如果请求的页数不存在, 重定向页面
 22         return HttpResponse('找不到页面的内容')
 23     return paginator_pages
 24 
 25 
 26 # 项目菜单
 27 @login_required
 28 def project(request):
 29     print("request.user.is_authenticated: ", request.user.is_authenticated)
 30     projects = Project.objects.filter().order_by('-id')
 31     print("projects:", projects)
 32     return render(request, 'project.html', {'projects': get_paginator(request, projects)})
 33 
 34 
 35 # 模块菜单
 36 @login_required
 37 def module(request):
 38     if request.method == "GET":  # 请求get时候,id倒序查询所有的模块数据
 39         modules = Module.objects.filter().order_by('-id')
 40         return render(request, 'module.html', {'modules': get_paginator(request, modules)})
 41     else:  # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目
 42         proj_name = request.POST['proj_name']
 43         projects = Project.objects.filter(name__contains=proj_name.strip())
 44         projs = [proj.id for proj in projects]
 45         modules = Module.objects.filter(belong_project__in=projs)  # 把项目中所有的模块都找出来
 46         return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
 47 
 48 
 49 # 测试用例页
 50 @login_required
 51 def test_case(request):
 52     print("request.session['is_login']: {}".format(request.session['is_login']))
 53     test_cases = ""
 54     if request.method == "GET":
 55         test_cases = TestCase.objects.filter().order_by('id')
 56         print("testcases in testcase: {}".format(test_cases))
 57     elif request.method == "POST":
 58         print("request.POST: {}".format(request.POST))
 59         test_case_id_list = request.POST.getlist('testcases_list')
 60         if test_case_id_list:
 61             test_case_id_list.sort()
 62             print("test_case_id_list: {}".format(test_case_id_list))
 63         test_cases = TestCase.objects.filter().order_by('id')
 64     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
 65 
 66 
 67 # 用例详情页
 68 @login_required
 69 def test_case_detail(request, test_case_id):
 70     test_case_id = int(test_case_id)
 71     test_case = TestCase.objects.get(id=test_case_id)
 72     print("test_case: {}".format(test_case))
 73     print("test_case.id: {}".format(test_case.id))
 74     print("test_case.belong_project: {}".format(test_case.belong_project))
 75 
 76     return render(request, 'test_case_detail.html', {'test_case': test_case})
 77 
 78 
 79 # 模块页展示测试用例
 80 @login_required
 81 def module_test_cases(request, module_id):
 82     module = ""
 83     if module_id:  # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现
 84         module = Module.objects.get(id=int(module_id))
 85     test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
 86     print("test_case in module_test_cases: {}".format(test_cases))
 87     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
 88 
 89 
 90 # 用例集合页
 91 @login_required
 92 def case_suite(request):
 93     case_suites = CaseSuite.objects.filter()
 94     return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
 95 
 96 
 97 # 用例集合-添加测试用例页
 98 @login_required
 99 def add_case_in_suite(request, suite_id):
100     # 查询指定的用例集合
101     case_suite = CaseSuite.objects.get(id=suite_id)
102     # 根据id号查询所有的用例
103     test_cases = TestCase.objects.filter().order_by('id')
104     if request.method == "GET":
105         print("test cases:", test_cases)
106     elif request.method == "POST":
107         test_cases_list = request.POST.getlist('testcases_list')
108         # 如果页面勾选了用例
109         if test_cases_list:
110             print("勾选用例id:", test_cases_list)
111             # 根据页面勾选的用例与查询出的所有用例一一比较
112             for test_case in test_cases_list:
113                 test_case = TestCase.objects.get(id=int(test_case))
114                 # 匹配成功则添加用例
115                 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
116         # 未勾选用例
117         else:
118             print("添加测试用例失败")
119             return HttpResponse("添加的测试用例为空,请选择用例后再添加!")
120     return render(request, 'add_case_in_suite.html',
121           {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
122 
123 
124 # 默认页的视图函数
125 @login_required
126 def index(request):
127     return render(request, 'index.html')
128 
129 
130 # 登录页的视图函数
131 def login(request):
132     print("request.session.items(): {}".format(request.session.items()))  # 打印session信息
133     if request.session.get('is_login', None):
134         return redirect('/')
135     # 如果是表单提交行为,则进行登录校验
136     if request.method == "POST":
137         login_form = UserForm(request.POST)
138         message = "请检查填写的内容!"
139         if login_form.is_valid():
140             username = login_form.cleaned_data['username']
141             password = login_form.cleaned_data['password']
142             try:
143                 # 使用django提供的身份验证功能
144                 user = auth.authenticate(username=username, password=password)  # 从auth_user表中匹配信息,匹配到则返回用户对象
145                 if user is not None:
146                     print("用户【%s】登录成功" % username)
147                     auth.login(request, user)
148                     request.session['is_login'] = True
149                     # 登录成功,跳转主页
150                     return redirect('/')
151                 else:
152                     message = "用户名不存在或者密码不正确!"
153             except:
154                 traceback.print_exc()
155                 message = "登录程序出现异常"
156         # 用户名或密码为空,返回登录页和错误提示信息
157         else:
158             return render(request, 'login.html', locals())
159     # 不是表单提交,代表只是访问登录页
160     else:
161         login_form = UserForm()
162         return render(request, 'login.html', locals())
163 
164 
165 # 注册页的视图函数
166 def register(request):
167     return render(request, 'register.html')
168 
169 
170 # 登出的视图函数:重定向至login视图函数
171 @login_required
172 def logout(request):
173     auth.logout(request)
174     request.session.flush()
175     return redirect("/login/")

6.4 定义模板文件

1)新增添加测试用例页的模板文件 templates/add_case_in_suite.html:

复制代码
 1 {% extends 'base.html' %}
 2 {% load static %}
 3 {% block title %}管理测试集合{% endblock %}
 4 {% block content %}
 5 
 6 <script type="text/javascript">
 7     //页面加载的时候,所有的复选框都是未选中的状态
 8     function checkOrCancelAll() {
 9         var all_check = document.getElementById("all_check");//1.获取all的元素对象
10         var all_check = all_check.checked;//2.获取选中状态
11         var allCheck = document.getElementsByName("testcases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消
12         //4.循环遍历取出每一个复选框中的元素
13         if (all_check)//全选
14         {
15             for (var i = 0; i < allCheck.length; i++) {
16                 //设置复选框的选中状态
17                 allCheck[i].checked = true;
18             }
19         } else//取消全选
20         {
21             for (var i = 0; i < allCheck.length; i++) {
22                 allCheck[i].checked = false;
23             }
24         }
25     }
26 
27     function ischecked() {
28         var allCheck = document.getElementsByName("testcases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消
29         for (var i = 0; i < allCheck.length; i++) {
30             if (allCheck[i].checked == true) {
31                 alert("成功添加所选测试用例至测试集合【{{case_suite.suite_desc}}】");
32                 return true
33             }
34         }
35         alert("请选择要添加的测试用例!")
36         return false
37     }
38 
39 
40 
41 </script>
42 <form action="" method="POST">
43     {% csrf_token %}
44     <input type="submit" id="all_check1" value='添加测试用例' onclick="return ischecked()"/>
45     <div class="table-responsive">
46         <table class="table table-striped">
47             <thead>
48             <tr>
49                 <th><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>id</th>
50                 <th>用例名称</th>
51                 <th>所属项目</th>
52                 <th>所属模块</th>
53                 <th>编写人员</th>
54                 <th>创建时间</th>
55                 <th>更新时间</th>
56                 <th>创建用例用户名</th>
57             </tr>
58             </thead>
59             <tbody>
60             {% for test_case in test_cases %}
61             <tr>
62                 <td><input type="checkbox" value="{{ test_case.id }}" name="testcases_list"> {{ test_case.id }}</td>
63                 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
64                 <td>{{ test_case.belong_project.name }}</td>
65                 <td>{{ test_case.belong_module.name }}</td>
66                 <td>{{ test_case.maintainer }}</td>
67                 <td>{{ test_case.created_time|date:"Y-n-d H:i" }}</td>
68                 <td>{{ test_case.updated_time|date:"Y-n-d H:i" }}</td>
69                 <td>{{ test_case.user.username }}</td>
70             </tr>
71             {% endfor %}
72             </tbody>
73         </table>
74     </div>
75 </form>
76 {# 实现分页标签的代码 #}
77 {# 这里使用 bootstrap 渲染页面 #}
78 <div id="pages" class="text-center">
79     <nav>
80         <ul class="pagination">
81             <li class="step-links">
82                 {% if test_cases.has_previous %}
83                 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a>
84                 {% endif %}
85                 <span class="current">
86                     第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span>
87                 {% if test_cases.has_next %}
88                 <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a>
89                 {% endif %}
90             </li>
91         </ul>
92     </nav>
93 </div>
94 {% endblock %}

2)修改用例集合模板文件 templates/case_suite.html:修改"添加测试用例"的链接地址。

复制代码
 1 {% extends 'base.html' %}
 2 {% load static %}
 3 {% block title %}用例集合{% endblock %}
 4 {% block content %}
 5 <form action="" method="POST">
 6     {% csrf_token %}
 7 
 8     <div class="table-responsive">
 9         <table class="table table-striped">
10             <thead>
11             <tr>
12                 <th>id</th>
13                 <th>测试集合名称</th>
14                 <th>创建者</th>
15                 <th>创建时间</th>
16                 <th>查看/删除测试用例</th>
17                 <th>添加测试用例</th>
18                 <th>用例集合执行结果</th>
19             </tr>
20             </thead>
21             <tbody>
22 
23             {% for case_suite in case_suites %}
24             <tr>
25                 <td>{{ case_suite.id }}</td>
26                 <td>{{ case_suite.suite_desc }}</td>
27                 <td>{{ case_suite.creator }}</td>
28                 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td>
29                 <td><a href="">查看/删除测试用例</a></td>
30                 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">添加测试用例</a></td>
31                 <td><a href="">查看用例集合执行结果</a></td>
32             </tr>
33             {% endfor %}
34             </tbody>
35         </table>
36     </div>
37 </form>
38 
39 {# 实现分页标签的代码 #}
40 {# 这里使用 bootstrap 渲染页面 #}
41 <div id="pages" class="text-center">
42     <nav>
43         <ul class="pagination">
44             <li class="step-links">
45                 {% if case_suites.has_previous %}
46                 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a>
47                 {% endif %}
48 
49                 <span class="current">
50                     第 {{ case_suites.number }} 页 / 共 {{ case_suites.paginator.num_pages }} 页</span>
51 
52                 {% if case_suites.has_next %}
53                 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a>
54                 {% endif %}
55             </li>
56         </ul>
57     </nav>
58 </div>
59 {% endblock %}

7. 用例集合查看/删除测试用例

预期效果如下:

7.1 定义路由

复制代码
from django.urls import path, re_path
from . import views


urlpatterns = [
    path('', views.index),
    path('login/', views.login),
    path('logout/', views.logout),
    path('project/', views.project, name='project'),
    path('module/', views.module, name='module'),
    path('test_case/', views.test_case, name="test_case"),
    re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),
    re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),
    path('case_suite/', views.case_suite, name="case_suite"),
    re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),
    re_path('show_and_delete_case_in_suite/(?P<suite_id>[0-9]+)', views.show_and_delete_case_in_suite, name="show_and_delete_case_in_suite"),
]

7.2 定义视图函数

复制代码
  1 from django.shortcuts import render, redirect, HttpResponse
  2 from django.contrib import auth  # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
  3 from django.contrib.auth.decorators import login_required
  4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
  5 from .form import UserForm
  6 import traceback
  7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer
  8 from .task import case_task
  9 
 10 
 11 # 封装分页处理
 12 def get_paginator(request, data):
 13     paginator = Paginator(data, 10)  # 默认每页展示10条数据
 14     # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
 15     page = request.GET.get('page')
 16     try:
 17         paginator_pages = paginator.page(page)
 18     except PageNotAnInteger:
 19         # 如果请求的页数不是整数, 返回第一页。
 20         paginator_pages = paginator.page(1)
 21     except InvalidPage:
 22         # 如果请求的页数不存在, 重定向页面
 23         return HttpResponse('找不到页面的内容')
 24     return paginator_pages
 25 
 26 
 27 # 项目页
 28 @login_required
 29 def project(request):
 30     print("request.user.is_authenticated: ", request.user.is_authenticated)
 31     projects = Project.objects.filter().order_by('-id')
 32     print("projects:", projects)
 33     return render(request, 'project.html', {'projects': get_paginator(request, projects)})
 34 
 35 
 36 # 模块页
 37 @login_required
 38 def module(request):
 39     if request.method == "GET":  # 请求get时候,id倒序查询所有的模块数据
 40         modules = Module.objects.filter().order_by('-id')
 41         return render(request, 'module.html', {'modules': get_paginator(request, modules)})
 42     else:  # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目
 43         proj_name = request.POST['proj_name']
 44         projects = Project.objects.filter(name__contains=proj_name.strip())
 45         projs = [proj.id for proj in projects]
 46         modules = Module.objects.filter(belong_project__in=projs)  # 把项目中所有的模块都找出来
 47         return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
 48 
 49 
 50 # 获取测试用例执行的接口地址
 51 def get_server_address(env):
 52     if env:  # 环境处理
 53         env_data = InterfaceServer.objects.filter(env=env[0])
 54         print("env_data: {}".format(env_data))
 55         if env_data:
 56             ip = env_data[0].ip
 57             port = env_data[0].port
 58             print("ip: {}, port: {}".format(ip, port))
 59             server_address = "http://{}:{}".format(ip, port)
 60             print("server_address: {}".format(server_address))
 61             return server_address
 62         else:
 63             return ""
 64     else:
 65         return ""
 66 
 67 
 68 # 测试用例页
 69 @login_required
 70 def test_case(request):
 71     print("request.session['is_login']: {}".format(request.session['is_login']))
 72     test_cases = ""
 73     if request.method == "GET":
 74         test_cases = TestCase.objects.filter().order_by('id')
 75         print("testcases in testcase: {}".format(test_cases))
 76     elif request.method == "POST":
 77         print("request.POST: {}".format(request.POST))
 78         test_case_id_list = request.POST.getlist('testcases_list')
 79         if test_case_id_list:
 80             test_case_id_list.sort()
 81             print("test_case_id_list: {}".format(test_case_id_list))
 82         test_cases = TestCase.objects.filter().order_by('id')
 83     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
 84 
 85 
 86 # 用例详情页
 87 @login_required
 88 def test_case_detail(request, test_case_id):
 89     test_case_id = int(test_case_id)
 90     test_case = TestCase.objects.get(id=test_case_id)
 91     print("test_case: {}".format(test_case))
 92     print("test_case.id: {}".format(test_case.id))
 93     print("test_case.belong_project: {}".format(test_case.belong_project))
 94 
 95     return render(request, 'test_case_detail.html', {'test_case': test_case})
 96 
 97 
 98 # 模块页展示测试用例
 99 @login_required
100 def module_test_cases(request, module_id):
101     module = ""
102     if module_id:  # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现
103         module = Module.objects.get(id=int(module_id))
104     test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
105     print("test_case in module_test_cases: {}".format(test_cases))
106     return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
107 
108 
109 # 用例集合页
110 @login_required
111 def case_suite(request):
112     case_suites = CaseSuite.objects.filter()
113     return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
114 
115 
116 # 用例集合-添加测试用例页
117 @login_required
118 def add_case_in_suite(request, suite_id):
119     # 查询指定的用例集合
120     case_suite = CaseSuite.objects.get(id=suite_id)
121     # 根据id号查询所有的用例
122     test_cases = TestCase.objects.filter().order_by('id')
123     if request.method == "GET":
124         print("test cases:", test_cases)
125     elif request.method == "POST":
126         test_cases_list = request.POST.getlist('testcases_list')
127         # 如果页面勾选了用例
128         if test_cases_list:
129             print("勾选用例id:", test_cases_list)
130             # 根据页面勾选的用例与查询出的所有用例一一比较
131             for test_case in test_cases_list:
132                 test_case = TestCase.objects.get(id=int(test_case))
133                 # 匹配成功则添加用例
134                 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
135         # 未勾选用例
136         else:
137             print("添加测试用例失败")
138             return HttpResponse("添加的测试用例为空,请选择用例后再添加!")
139     return render(request, 'add_case_in_suite.html',
140           {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
141 
142 
143 # 用例集合页-查看/删除用例
144 @login_required
145 def show_and_delete_case_in_suite(request, suite_id):
146     case_suite = CaseSuite.objects.get(id=suite_id)
147     test_cases = SuiteCase.objects.filter(case_suite=case_suite)
148     if request.method == "POST":
149         test_cases_list = request.POST.getlist('test_cases_list')
150         if test_cases_list:
151             print("勾选用例:", test_cases_list)
152             for test_case in test_cases_list:
153                 test_case = TestCase.objects.get(id=int(test_case))
154                 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
155         else:
156             print("测试用例删除失败")
157             return HttpResponse("所选测试用例为空,请选择用例后再进行删除!")
158     case_suite = CaseSuite.objects.get(id=suite_id)
159     return render(request, 'show_and_delete_case_in_suite.html',
160                   {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
161 
162 
163 # 默认页的视图函数
164 @login_required
165 def index(request):
166     return render(request, 'index.html')
167 
168 
169 # 登录页的视图函数
170 def login(request):
171     print("request.session.items(): {}".format(request.session.items()))  # 打印session信息
172     if request.session.get('is_login', None):
173         return redirect('/')
174     # 如果是表单提交行为,则进行登录校验
175     if request.method == "POST":
176         login_form = UserForm(request.POST)
177         message = "请检查填写的内容!"
178         if login_form.is_valid():
179             username = login_form.cleaned_data['username']
180             password = login_form.cleaned_data['password']
181             try:
182                 # 使用django提供的身份验证功能
183                 user = auth.authenticate(username=username, password=password)  # 从auth_user表中匹配信息,匹配到则返回用户对象
184                 if user is not None:
185                     print("用户【%s】登录成功" % username)
186                     auth.login(request, user)
187                     request.session['is_login'] = True
188                     # 登录成功,跳转主页
189                     return redirect('/')
190                 else:
191                     message = "用户名不存在或者密码不正确!"
192             except:
193                 traceback.print_exc()
194                 message = "登录程序出现异常"
195         # 用户名或密码为空,返回登录页和错误提示信息
196         else:
197             return render(request, 'login.html', locals())
198     # 不是表单提交,代表只是访问登录页
199     else:
200         login_form = UserForm()
201         return render(request, 'login.html', locals())
202 
203 
204 # 注册页的视图函数
205 def register(request):
206     return render(request, 'register.html')
207 
208 
209 # 登出的视图函数:重定向至login视图函数
210 @login_required
211 def logout(request):
212     auth.logout(request)
213     request.session.flush()
214     return redirect("/login/")

7.2 定义模板文件

1)新建 templates/show_and_delete_case_in_suite.html:

复制代码
  1 {% extends 'base.html' %}
  2 {% load static %}
  3 {% block title %}查看/删除测试用例{% endblock %}
  4 {% block content %}
  5 
  6 <script type="text/javascript">
  7     //页面加载的时候,所有的复选框都是未选中的状态
  8     function checkOrCancelAll() {
  9         var all_check = document.getElementById("all_check");//1.获取all的元素对象
 10         var all_check = all_check.checked;//2.获取选中状态
 11         var allCheck = document.getElementsByName("test_cases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消
 12         //4.循环遍历取出每一个复选框中的元素
 13         if (all_check)//全选
 14         {
 15 
 16             for (var i = 0; i < allCheck.length; i++) {
 17                 //设置复选框的选中状态
 18                 allCheck[i].checked = true;
 19             }
 20 
 21         } else//取消全选
 22         {
 23             for (var i = 0; i < allCheck.length; i++) {
 24                 allCheck[i].checked = false;
 25             }
 26         }
 27     }
 28 
 29     function ischecked() {
 30 
 31         var allCheck = document.getElementsByName("test_cases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消
 32         for (var i = 0; i < allCheck.length; i++) {
 33 
 34             if (allCheck[i].checked == true) {
 35                 alert("所选用例删除成功!");
 36                 return true
 37             }
 38         }
 39         alert("请选择要删除的测试用例!")
 40         return false
 41     }
 42 
 43 
 44 </script>
 45 
 46 <div><p style="margin-left: 5px;">测试集合名称:<b>{{case_suite.suite_desc}}</b></p>
 47     <div>
 48         <form action="" method="POST">
 49             {% csrf_token %}
 50             <input style="margin-left: 5px;" type="submit" id="all_check1" value='删除测试集合用例' onclick="return ischecked()"/>
 51             <div class="table-responsive">
 52                 <table class="table table-striped">
 53                     <thead>
 54                     <tr>
 55                         <th width="4%"><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>全选</th>
 56                         <th width="6%">用例序号</th>
 57                         <th>用例名称</th>
 58                         <th>所属项目</th>
 59                         <th>所属模块</th>
 60                         <th>编写人员</th>
 61                         <th>创建时间</th>
 62                         <th>更新时间</th>
 63                         <th>创建用例用户名</th>
 64                     </tr>
 65                     </thead>
 66                     <tbody>
 67 
 68                     {% for test_case in test_cases %}
 69                     <tr>
 70                         <td><input type="checkbox" value="{{ test_case.test_case.id }}" name="test_cases_list"></td>
 71                         <td>{{ test_case.test_case.id }}</td>
 72                         <td><a href="{% url 'test_case_detail' test_case.test_case.id%}">{{ test_case.test_case.case_name }}</a></td>
 73                         <td>{{ test_case.test_case.belong_project.name }}</td>
 74                         <td>{{ test_case.test_case.belong_module.name }}</td>
 75                         <td>{{ test_case.test_case.maintainer }}</td>
 76                         <td>{{ test_case.test_case.created_time|date:"Y-n-d H:i" }}</td>
 77                         <td>{{ test_case.test_case.updated_time|date:"Y-n-d H:i" }}</td>
 78                         <td>{{ test_case.test_case.user.username }}</td>
 79                     </tr>
 80                     {% endfor %}
 81                     </tbody>
 82                 </table>
 83             </div>
 84         </form>
 85 
 86         {# 实现分页标签的代码 #}
 87         {# 这里使用 bootstrap 渲染页面 #}
 88         <div id="pages" class="text-center">
 89             <nav>
 90                 <ul class="pagination">
 91                     <li class="step-links">
 92                         {% if test_cases.has_previous %}
 93                         <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a>
 94                         {% endif %}
 95 
 96                         <span class="current">
 97                     第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span>
 98 
 99                         {% if test_cases.has_next %}
100                         <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a>
101                         {% endif %}
102                     </li>
103                 </ul>
104             </nav>
105         </div>
106     </div>
107 </div>
108 {% endblock %}

2)修改 templates/case_suite.html:增加"查看/删除测试用例"链接

复制代码
 1 {% extends 'base.html' %}
 2 {% load static %}
 3 {% block title %}用例集合{% endblock %}
 4 {% block content %}
 5 <form action="" method="POST">
 6     {% csrf_token %}
 7 
 8     <div class="table-responsive">
 9         <table class="table table-striped">
10             <thead>
11             <tr>
12                 <th>id</th>
13                 <th>测试集合名称</th>
14                 <th>创建者</th>
15                 <th>创建时间</th>
16                 <th>查看/删除测试用例</th>
17                 <th>添加测试用例</th>
18                 <th>用例集合执行结果</th>
19             </tr>
20             </thead>
21             <tbody>
22 
23             {% for case_suite in case_suites %}
24             <tr>
25                 <td>{{ case_suite.id }}</td>
26                 <td>{{ case_suite.suite_desc }}</td>
27                 <td>{{ case_suite.creator }}</td>
28                 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td>
29                 <td><a href="{% url 'show_and_delete_case_in_suite' case_suite.id %}">查看/删除测试用例</a></td>
30                 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">添加测试用例</a></td>
31                 <td><a href="">查看用例集合执行结果</a></td>
32             </tr>
33             {% endfor %}
34             </tbody>
35         </table>
36     </div>
37 </form>
38 
39 {# 实现分页标签的代码 #}
40 {# 这里使用 bootstrap 渲染页面 #}
41 <div id="pages" class="text-center">
42     <nav>
43         <ul class="pagination">
44             <li class="step-links">
45                 {% if case_suites.has_previous %}
46                 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a>
47                 {% endif %}
48 
49                 <span class="current">
50                     第 {{ case_suites.number }} 页 / 共 {{ case_suites.paginator.num_pages }} 页</span>
51 
52                 {% if case_suites.has_next %}
53                 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a>
54                 {% endif %}
55             </li>
56         </ul>
57     </nav>
58 </div>
59 {% endblock %}
相关推荐
@MMiL25 分钟前
Python 中常见的数据管理高效方法
python·numpy·pandas·matplotlib
杨荧38 分钟前
基于大数据的旅游推荐系统 Python+Django+Hive+Vue.js
大数据·前端·vue.js·hive·python·开源·旅游
哪 吒43 分钟前
【2025B卷】华为OD机试九日集训第2期 - 按算法分类,由易到难,提升编程能力和解题技巧(Python/JS/C/C++)
python·算法·华为od·华为od机试·2025b卷
赴3351 小时前
Numpy库,矩阵形状与维度操作
开发语言·python·矩阵·numpy·resize·reshape
狗都不学爬虫_2 小时前
JS逆向 - 滴滴(dd03、dd05)WSGSIG
javascript·爬虫·python·网络爬虫·wasm
言之。2 小时前
Django Ninja
后端·python·django
西猫雷婶3 小时前
python学智能算法(二十七)|SVM-拉格朗日函数求解上
人工智能·python·算法·机器学习·支持向量机
talented_pure3 小时前
Python打卡Day18 基于聚类进一步推断类型
python·机器学习·聚类
耐达讯通信技术3 小时前
RS232转EtherCAT网关让电力仪表开启“躺赢“模式
人工智能·物联网·网络协议·自动化·信息与通信
我是苏苏3 小时前
Pytorch02:深度学习基础示例——猫狗识别
python·深度学习·机器学习