3.2 后台 admin 添加数据
1)注册模型类到 admin:
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)
2)登录 admin 系统,添加模块数据
访问:http://127.0.0.1:8000/admin/,进入模块信息表,添加数据。


3.3 定义路由
新增应用 urls.py 的路由配置:
from django.urls import 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"),
]
路由地址"module"对应的视图函数,指向 views.py 中的 module 方法,下一步我们添加下该方法的处理。
3.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
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 @login_required
27 def project(request):
28 print("request.user.is_authenticated: ", request.user.is_authenticated)
29 projects = Project.objects.filter().order_by('-id')
30 print("projects:", projects)
31 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
32
33
34 @login_required
35 def module(request):
36 if request.method == "GET": # 请求get时候,id倒序查询所有的模块数据
37 modules = Module.objects.filter().order_by('-id')
38 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
39 else: # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目
40 proj_name = request.POST['proj_name']
41 projects = Project.objects.filter(name__contains=proj_name.strip())
42 projs = [proj.id for proj in projects]
43 modules = Module.objects.filter(belong_project__in=projs) # 把项目中所有的模块都找出来
44 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
45
46
47 # 默认页的视图函数
48 @login_required
49 def index(request):
50 return render(request, 'index.html')
51
52
53 # 登录页的视图函数
54 def login(request):
55 print("request.session.items(): {}".format(request.session.items())) # 打印session信息
56 if request.session.get('is_login', None):
57 return redirect('/')
58 # 如果是表单提交行为,则进行登录校验
59 if request.method == "POST":
60 login_form = UserForm(request.POST)
61 message = "请检查填写的内容!"
62 if login_form.is_valid():
63 username = login_form.cleaned_data['username']
64 password = login_form.cleaned_data['password']
65 try:
66 # 使用django提供的身份验证功能
67 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象
68 if user is not None:
69 print("用户【%s】登录成功" % username)
70 auth.login(request, user)
71 request.session['is_login'] = True
72 # 登录成功,跳转主页
73 return redirect('/')
74 else:
75 message = "用户名不存在或者密码不正确!"
76 except:
77 traceback.print_exc()
78 message = "登录程序出现异常"
79 # 用户名或密码为空,返回登录页和错误提示信息
80 else:
81 return render(request, 'login.html', locals())
82 # 不是表单提交,代表只是访问登录页
83 else:
84 login_form = UserForm()
85 return render(request, 'login.html', locals())
86
87
88 # 注册页的视图函数
89 def register(request):
90 return render(request, 'register.html')
91
92
93 # 登出的视图函数:重定向至login视图函数
94 @login_required
95 def logout(request):
96 auth.logout(request)
97 request.session.flush()
98 return redirect("/login/")
3.5 定义模板
1)新增 templates/module.html 模板:
1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模块{% endblock %}
4
5 {% block content %}
6 <form action="{% url 'module'%}" method="POST">
7 {% csrf_token %}
8 <input style="margin-left: 5px;" type="text" name="proj_name" value="{{ proj_name }}" placeholder="输入项目名称搜索模块">
9 <input type="submit" value="搜索">
10 </form>
11
12 <div class="table-responsive">
13
14 <table class="table table-striped">
15 <thead>
16 <tr>
17 <th>id</th>
18 <th>模块名称</th>
19 <th>所属项目</th>
20 <th>测试负责人</th>
21 <th>模块描述</th>
22 <th>创建时间</th>
23 <th>更新时间</th>
24 <th>测试结果统计</th>
25 </tr>
26 </thead>
27 <tbody>
28
29 {% for module in modules %}
30 <tr>
31 <td>{{ module.id }}</td>
32 <td><a href="">{{ module.name }}</a></td>
33 <td>{{ module.belong_project.name }}</td>
34 <td>{{ module.test_owner }}</td>
35 <td>{{ module.desc }}</td>
36 <td>{{ module.create_time|date:"Y-n-d H:i" }}</td>
37 <td>{{ module.update_time|date:"Y-n-d H:i" }}</td>
38 <td><a href="">查看</a></td>
39 </tr>
40 {% endfor %}
41
42 </tbody>
43 </table>
44 </div>
45
46 {# 实现分页标签的代码 #}
47 {# 这里使用 bootstrap 渲染页面 #}
48 <div id="pages" class="text-center">
49 <nav>
50 <ul class="pagination">
51 <li class="step-links">
52 {% if modules.has_previous %}
53 <a class='active' href="?page={{ modules.previous_page_number }}">上一页</a>
54 {% endif %}
55
56 <span class="current">
57 第 {{ modules.number }} 页 / 共 {{ modules.paginator.num_pages }} 页</span>
58
59 {% if modules.has_next %}
60 <a class='active' href="?page={{ modules.next_page_number }}">下一页</a>
61 {% endif %}
62 </li>
63 </ul>
64 </nav>
65 </div>
66 {% endblock %}
2)修改 base.html 模板,新增模块菜单栏:
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 </ul>
43 <ul class="nav navbar-nav navbar-right">
44 {% if request.user.is_authenticated %}
45 <li><a href="#">当前在线:{{ request.user.username }}</a></li>
46 <li><a href="/logout">登出</a></li>
47 {% else %}
48 <li><a href="/login">登录</a></li>
49
50 {% endif %}
51 </ul>
52 </div><!-- /.navbar-collapse -->
53 </div><!-- /.container-fluid -->
54 </nav>
55
56 {% block content %}{% endblock %}
57
58
59 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
60 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
61 <!-- Include all compiled plugins (below), or include individual files as needed -->
62 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
63 </body>
64 </html>
4. 测试用例

4.1 定义模型类
1)在应用 models.py 中增加 TestCase 模型类:
1 from django.db import models
2 from smart_selects.db_fields import GroupedForeignKey # 后台级联选择
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 = '测试用例表'
GroupedForeignKey 可以支持在 admin 新增数据时,展示该模型类的关联表数据。(需提前安装:pip install django-smart-selects)
2)数据迁移
在项目目录下,执行以下两个命令进行数据迁移(将模型类转换成数据库表):
python manage.py makemigrations # 生成迁移文件(模型类的信息)
python manage.py migrate # 执行开始迁移(将模型类信息转换成数据库表)

4.2 后台 admin 添加数据
1)注册模型类到 admin
应用 admin.py 文件中增加如下代码:注册 TestCase 模型类到 admin 后台系统。
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)
2)登录 admin 系统,添加用例数据
访问 http://127.0.0.1:8000/admin/,进入测试用例表,添加数据:

添加用例数据时,页面如下:
- 所属项目和所属模块下拉选项是根据模型类中的 GroupedForeignKey 属性生成的,方便我们正确的关联数据。
- 请求数据、断言内容、提取变量表达式等字段的定义,需要根据接口业务逻辑,以及后续运行逻辑的设计来输入。

添加数据后如下所示:

4.3 定义路由
应用 urls.py:
from django.urls import 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"),
]
4.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
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 index(request):
70 return render(request, 'index.html')
71
72
73 # 登录页的视图函数
74 def login(request):
75 print("request.session.items(): {}".format(request.session.items())) # 打印session信息
76 if request.session.get('is_login', None):
77 return redirect('/')
78 # 如果是表单提交行为,则进行登录校验
79 if request.method == "POST":
80 login_form = UserForm(request.POST)
81 message = "请检查填写的内容!"
82 if login_form.is_valid():
83 username = login_form.cleaned_data['username']
84 password = login_form.cleaned_data['password']
85 try:
86 # 使用django提供的身份验证功能
87 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象
88 if user is not None:
89 print("用户【%s】登录成功" % username)
90 auth.login(request, user)
91 request.session['is_login'] = True
92 # 登录成功,跳转主页
93 return redirect('/')
94 else:
95 message = "用户名不存在或者密码不正确!"
96 except:
97 traceback.print_exc()
98 message = "登录程序出现异常"
99 # 用户名或密码为空,返回登录页和错误提示信息
100 else:
101 return render(request, 'login.html', locals())
102 # 不是表单提交,代表只是访问登录页
103 else:
104 login_form = UserForm()
105 return render(request, 'login.html', locals())
106
107
108 # 注册页的视图函数
109 def register(request):
110 return render(request, 'register.html')
111
112
113 # 登出的视图函数:重定向至login视图函数
114 @login_required
115 def logout(request):
116 auth.logout(request)
117 request.session.flush()
118 return redirect("/login/")
4.5 定义模板文件
1)新增 templates/test_case.html 模板:
1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}测试用例{% endblock %}
4
5 {% block content %}
6 <form action="" method="POST">
7 {% csrf_token %}
8 <div class="table-responsive">
9 <table class="table table-striped">
10 <thead>
11 <tr>
12 <th>用例名称</th>
13 <th>所属项目</th>
14 <th>所属模块</th>
15 <th>接口地址</th>
16 <th>请求方式</th>
17 <th>请求数据</th>
18 <th>断言key</th>
19 <th>提取变量表达式</th>
20 </tr>
21 </thead>
22 <tbody>
23
24 {% for test_case in test_cases %}
25 <tr>
26 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
27 <td>{{ test_case.belong_project.name }}</td>
28 <td>{{ test_case.belong_module.name }}</td>
29 <td>{{ test_case.uri }}</td>
30 <td>{{ test_case.request_method }}</td>
31 <td>{{ test_case.request_data }}</td>
32 <td>{{ test_case.assert_key }}</td>
33 <td>{{ test_case.extract_var }}</td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38
39 </div>
40 </form>
41 {# 实现分页标签的代码 #}
42 {# 这里使用 bootstrap 渲染页面 #}
43 <div id="pages" class="text-center">
44 <nav>
45 <ul class="pagination">
46 <li class="step-links">
47 {% if test_cases.has_previous %}
48 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a>
49 {% endif %}
50
51 <span class="current">
52 第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span>
53
54 {% if test_cases.has_next %}
55 <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a>
56 {% endif %}
57 </li>
58 </ul>
59 </nav>
60 </div>
61 {% endblock %}
2)修改 base.html 模板:增加测试用例菜单。
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 </ul>
44 <ul class="nav navbar-nav navbar-right">
45 {% if request.user.is_authenticated %}
46 <li><a href="#">当前在线:{{ request.user.username }}</a></li>
47 <li><a href="/logout">登出</a></li>
48 {% else %}
49 <li><a href="/login">登录</a></li>
50
51 {% endif %}
52 </ul>
53 </div><!-- /.navbar-collapse -->
54 </div><!-- /.container-fluid -->
55 </nav>
56
57 {% block content %}{% endblock %}
58
59
60 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
61 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
62 <!-- Include all compiled plugins (below), or include individual files as needed -->
63 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
64 </body>
65 </html>
4.6 用例详情
目前用例列表中的字段仅包含用例的基本信息,下面继续加一下(点击用例名称跳转)用例详情页面,用于展示用例的全部字段信息,如创建时间、更新时间、维护人、创建人。

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"),
]
由于用例详情路由地址需要传路由变量"test_case_id",该变量需要通过正则表达式进行匹配,在 Django2.0 后,路由地址用到正则时需要用到 re_path 来解析,便于 Django 正确的匹配视图函数,以及在浏览器地址栏正确的展示 url 地址。
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
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 index(request):
82 return render(request, 'index.html')
83
84
85 # 登录页的视图函数
86 def login(request):
87 print("request.session.items(): {}".format(request.session.items())) # 打印session信息
88 if request.session.get('is_login', None):
89 return redirect('/')
90 # 如果是表单提交行为,则进行登录校验
91 if request.method == "POST":
92 login_form = UserForm(request.POST)
93 message = "请检查填写的内容!"
94 if login_form.is_valid():
95 username = login_form.cleaned_data['username']
96 password = login_form.cleaned_data['password']
97 try:
98 # 使用django提供的身份验证功能
99 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象
100 if user is not None:
101 print("用户【%s】登录成功" % username)
102 auth.login(request, user)
103 request.session['is_login'] = True
104 # 登录成功,跳转主页
105 return redirect('/')
106 else:
107 message = "用户名不存在或者密码不正确!"
108 except:
109 traceback.print_exc()
110 message = "登录程序出现异常"
111 # 用户名或密码为空,返回登录页和错误提示信息
112 else:
113 return render(request, 'login.html', locals())
114 # 不是表单提交,代表只是访问登录页
115 else:
116 login_form = UserForm()
117 return render(request, 'login.html', locals())
118
119
120 # 注册页的视图函数
121 def register(request):
122 return render(request, 'register.html')
123
124
125 # 登出的视图函数:重定向至login视图函数
126 @login_required
127 def logout(request):
128 auth.logout(request)
129 request.session.flush()
130 return redirect("/login/")
3)新增 templates/test_case_detail.html 模板
1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例详情{% endblock %}
4
5 {% block content %}
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th width="3%">id</th>
11 <th width="4%">接口名称</th>
12 <th width="6%">所属项目</th>
13 <th width="6%">所属模块</th>
14 <th width="6%">接口地址</th>
15 <th width="10%">请求数据</th>
16 <th width="8%">断言内容</th>
17 <th width="4%">编写人员</th>
18 <th width="8%">提取变量表达式</th>
19 <th width="4%">维护人</th>
20 <th width="4%">创建人</th>
21 <th width="6%">创建时间</th>
22 <th width="6%">更新时间</th>
23 </tr>
24 </thead>
25 <tbody>
26 <tr>
27 <td>{{ test_case.id }}</td>
28 <td>{{ test_case.case_name }}</td>
29 <td>{{ test_case.belong_project }}</td>
30 <td>{{ test_case.belong_module }}</td>
31 <td>{{ test_case.uri }}</td>
32 <td>{{ test_case.request_data }}</td>
33 <td>{{ test_case.assert_key }}</td>
34 <td>{{ test_case.maintainer }}</td>
35 <td>{{ test_case.extract_var }}</td>
36 <td>{{ test_case.maintainer }}</td>
37 <td>{{ test_case.user.username }}</td>
38 <td>{{ test_case.created_time|date:"Y-n-d H:i" }}</td>
39 <td>{{ test_case.updated_time|date:"Y-n-d H:i" }}</td>
40 </tr>
41 </tbody>
42 </table>
43 </div>
44 {% endblock %}
4)修改 test_case.html 模板
添加用例名称的链接为用例详情路由地址:
1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}测试用例{% endblock %}
4
5 {% block content %}
6 <form action="" method="POST">
7 {% csrf_token %}
8 <div class="table-responsive">
9 <table class="table table-striped">
10 <thead>
11 <tr>
12 <th>用例名称</th>
13 <th>所属项目</th>
14 <th>所属模块</th>
15 <th>接口地址</th>
16 <th>请求方式</th>
17 <th>请求数据</th>
18 <th>断言key</th>
19 <th>提取变量表达式</th>
20 </tr>
21 </thead>
22 <tbody>
23
24 {% for test_case in test_cases %}
25 <tr>
26 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
27 <td>{{ test_case.belong_project.name }}</td>
28 <td>{{ test_case.belong_module.name }}</td>
29 <td>{{ test_case.uri }}</td>
30 <td>{{ test_case.request_method }}</td>
31 <td>{{ test_case.request_data }}</td>
32 <td>{{ test_case.assert_key }}</td>
33 <td>{{ test_case.extract_var }}</td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38
39 </div>
40 </form>
41 {# 实现分页标签的代码 #}
42 {# 这里使用 bootstrap 渲染页面 #}
43 <div id="pages" class="text-center">
44 <nav>
45 <ul class="pagination">
46 <li class="step-links">
47 {% if test_cases.has_previous %}
48 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a>
49 {% endif %}
50
51 <span class="current">
52 第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span>
53
54 {% if test_cases.has_next %}
55 <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a>
56 {% endif %}
57 </li>
58 </ul>
59 </nav>
60 </div>
61 {% endblock %}
4.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"),
]
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
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 index(request):
93 return render(request, 'index.html')
94
95
96 # 登录页的视图函数
97 def login(request):
98 print("request.session.items(): {}".format(request.session.items())) # 打印session信息
99 if request.session.get('is_login', None):
100 return redirect('/')
101 # 如果是表单提交行为,则进行登录校验
102 if request.method == "POST":
103 login_form = UserForm(request.POST)
104 message = "请检查填写的内容!"
105 if login_form.is_valid():
106 username = login_form.cleaned_data['username']
107 password = login_form.cleaned_data['password']
108 try:
109 # 使用django提供的身份验证功能
110 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象
111 if user is not None:
112 print("用户【%s】登录成功" % username)
113 auth.login(request, user)
114 request.session['is_login'] = True
115 # 登录成功,跳转主页
116 return redirect('/')
117 else:
118 message = "用户名不存在或者密码不正确!"
119 except:
120 traceback.print_exc()
121 message = "登录程序出现异常"
122 # 用户名或密码为空,返回登录页和错误提示信息
123 else:
124 return render(request, 'login.html', locals())
125 # 不是表单提交,代表只是访问登录页
126 else:
127 login_form = UserForm()
128 return render(request, 'login.html', locals())
129
130
131 # 注册页的视图函数
132 def register(request):
133 return render(request, 'register.html')
134
135
136 # 登出的视图函数:重定向至index视图函数
137 @login_required
138 def logout(request):
139 auth.logout(request)
140 request.session.flush()
141 return redirect("/login/")
3)修改模块的模板文件:在模块名称链接中,添加对应测试用例的路由地址。
1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模块{% endblock %}
4
5 {% block content %}
6 <form action="{% url 'module'%}" method="POST">
7 {% csrf_token %}
8 <input style="margin-left: 5px;" type="text" name="proj_name" value="{{ proj_name }}" placeholder="输入项目名称搜索模块">
9 <input type="submit" value="搜索">
10 </form>
11
12 <div class="table-responsive">
13
14 <table class="table table-striped">
15 <thead>
16 <tr>
17 <th>id</th>
18 <th>模块名称</th>
19 <th>所属项目</th>
20 <th>测试负责人</th>
21 <th>模块描述</th>
22 <th>创建时间</th>
23 <th>更新时间</th>
24 <th>测试结果统计</th>
25 </tr>
26 </thead>
27 <tbody>
28
29 {% for module in modules %}
30 <tr>
31 <td>{{ module.id }}</td>
32 <td><a href="{% url 'module_test_cases' module.id %}">{{ module.name }}</a></td>
33 <td>{{ module.belong_project.name }}</td>
34 <td>{{ module.test_owner }}</td>
35 <td>{{ module.desc }}</td>
36 <td>{{ module.create_time|date:"Y-n-d H:i" }}</td>
37 <td>{{ module.update_time|date:"Y-n-d H:i" }}</td>
38 <td><a href="">查看</a></td>
39 </tr>
40 {% endfor %}
41
42 </tbody>
43 </table>
44 </div>
45
46 {# 实现分页标签的代码 #}
47 {# 这里使用 bootstrap 渲染页面 #}
48 <div id="pages" class="text-center">
49 <nav>
50 <ul class="pagination">
51 <li class="step-links">
52 {% if modules.has_previous %}
53 <a class='active' href="?page={{ modules.previous_page_number }}">上一页</a>
54 {% endif %}
55
56 <span class="current">
57 第 {{ modules.number }} 页 / 共 {{ modules.paginator.num_pages }} 页</span>
58
59 {% if modules.has_next %}
60 <a class='active' href="?page={{ modules.next_page_number }}">下一页</a>
61 {% endif %}
62 </li>
63 </ul>
64 </nav>
65 </div>
66 {% endblock %}