3.2?后臺?admin 添加數據
1)注冊模型類到?admin:
?
1 from django.contrib import admin2 from . import models3 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 viewsurlpatterns = [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, HttpResponse2 from django.contrib import auth # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module8 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 models2 from smart_selects.db_fields import GroupedForeignKey # 后臺級聯選擇3 from django.contrib.auth.models import User4 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 admin2 from . import models3 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 viewsurlpatterns = [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, HttpResponse2 from django.contrib import auth # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase8 9 10 # 封裝分頁處理11 def get_paginator(request, data):12 paginator = Paginator(data, 10) # 默認每頁展示10條數據13 # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 114 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_pages24 25 26 # 項目菜單27 @login_required28 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_required37 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_required51 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_required69 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'] = True92 # 登錄成功,跳轉主頁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 viewsurlpatterns = [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, HttpResponse2 from django.contrib import auth # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase8 9 10 # 封裝分頁處理11 def get_paginator(request, data):12 paginator = Paginator(data, 10) # 默認每頁展示10條數據13 # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 114 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_pages24 25 26 # 項目菜單27 @login_required28 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_required37 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_required51 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_required69 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_required81 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 viewsurlpatterns = [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, HttpResponse2 from django.contrib import auth # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase8 9 10 # 封裝分頁處理11 def get_paginator(request, data):12 paginator = Paginator(data, 10) # 默認每頁展示10條數據13 # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 114 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_pages24 25 26 # 項目菜單27 @login_required28 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_required37 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_required51 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_required69 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_required81 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_required92 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 %}
?
??
?