initial commit

This commit is contained in:
2025-04-29 18:09:00 +08:00
commit 4faed52de5
690 changed files with 13481 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
# 导入所有控制器
from apps.project.controllers.project import ProjectController
from apps.project.controllers.round import RoundController
from apps.project.controllers.dut import DutController
from apps.project.controllers.design import DesignController
from apps.project.controllers.testDemand import TestDemandController
from apps.project.controllers.case import CaseController
from apps.project.controllers.problem import ProblemController
from apps.project.controllers.treeOperation import TreeController
# 将导入的控制器以列表方式放入下面数组
__all__ = ['ProjectController', 'RoundController', 'DutController', 'DesignController', 'TestDemandController',
'CaseController', 'ProblemController', 'TreeController']

View File

@@ -0,0 +1,245 @@
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from ninja.errors import HttpError
from utils.chen_pagination import MyPagination
from django.db import transaction
from django.shortcuts import get_object_or_404
from typing import List
from utils.chen_response import ChenResponse
from utils.chen_crud import multi_delete_case
from apps.project.models import Design, Dut, Round, TestDemand, Case, CaseStep, Project, Problem
from apps.project.schemas.case import DeleteSchema, CaseModelOutSchema, CaseFilterSchema, CaseTreeReturnSchema, \
CaseTreeInputSchema, CaseCreateOutSchema, CaseCreateInputSchema, DemandNodeSchema
from utils.util import get_testType
from utils.codes import HTTP_INDEX_ERROR, HTTP_EXISTS_CASES
from apps.project.tools.copyCase import case_move_to_test, case_copy_to_test, case_to_case_copy_or_move
from utils.smallTools.interfaceTools import conditionNoneToBlank
# 导入case的schema
from apps.project.schemas.case import CaseModelOutSchemaWithoutProblem
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试用例接口'])
class CaseController(ControllerBase):
@route.get("/getCaseList", response=List[CaseModelOutSchema], exclude_none=True,
url_name="case-list")
@transaction.atomic
@paginate(MyPagination)
def get_case_list(self, data: CaseFilterSchema = Query(...)):
"""有id则查询一个case无id则查询多个"""
data_dict = data.dict()
case_id = data_dict.pop('id')
if case_id:
# 当传入了id则查询单个
qs = Case.objects.filter(id=case_id) # type:ignore
else:
conditionNoneToBlank(data)
test_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id])
qs = Case.objects.filter(project__id=data.project_id, test__key=test_key, # type:ignore
ident__icontains=data.ident,
name__icontains=data.name,
designPerson__icontains=data.designPerson,
testPerson__icontains=data.testPerson,
monitorPerson__icontains=data.monitorPerson,
summarize__icontains=data.summarize,
).order_by("key")
# 由于有嵌套query_set存在把每个用例的schema加上一个字段
query_list = []
for query_single in qs:
setattr(query_single, "testStep", query_single.step.all().values())
# 增加一个字段测试类型例如FT
setattr(query_single, 'testType', get_testType(query_single.test.testType, dict_code='testType'))
# 如果有问题单字段则添加上
related_problem: Problem = query_single.caseField.first()
if query_single.caseField.all():
setattr(query_single, 'problem', related_problem)
query_list.append(query_single)
return query_list
@route.get("/getCaseOne", response=CaseModelOutSchemaWithoutProblem, url_name='case-one')
@transaction.atomic
def get_case_one(self, key: str, projectId: int):
"""用于在用例树状页面获取promblem信息这里根据key获取信息"""
project_obj = get_object_or_404(Project, id=projectId)
case = project_obj.pcField.filter(key=key).first()
if case:
setattr(case, "testStep", case.step.all().values())
setattr(case, 'testType', get_testType(case.test.testType, dict_code='testType'))
return case
raise HttpError(500, "您获取的数据不存在")
# 处理树状数据
@route.get("/getCaseInfo", response=List[CaseTreeReturnSchema], url_name="case-info")
@transaction.atomic
def get_case_tree(self, payload: CaseTreeInputSchema = Query(...)):
qs = Case.objects.filter(project__id=payload.project_id, test__key=payload.key) # type:ignore
for q in qs:
# 遍历每个用例节点,查看是否有关联问题单
if q.caseField.count() > 0:
q.isRelatedProblem = True
# 遍历用例的step查看是否有未通过
q.isNotPassed = False
for step in q.step.all():
if step.passed == '2':
q.isNotPassed = True
return qs
# 添加测试用例
@route.post("/case/save", response=CaseCreateOutSchema, url_name="case-create")
@transaction.atomic
def create_case(self, payload: CaseCreateInputSchema):
asert_dict = payload.dict(exclude_none=True)
# 构造design_key
test_whole_key = "".join(
[payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key])
# 查询当前key应该为多少
case_count = Case.objects.filter(project__id=payload.project_id, # type:ignore
test__key=test_whole_key).count()
key_string = ''.join([test_whole_key, "-", str(case_count)])
# 查询当前各个前面节点的instance
round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key)
dut_instance = Dut.objects.get(project__id=payload.project_id, # type:ignore
key="".join([payload.round_key, "-", payload.dut_key]))
design_instance = Design.objects.get(project__id=payload.project_id, key="".join( # type:ignore
[payload.round_key, "-", payload.dut_key, '-', payload.design_key]))
test_instance = TestDemand.objects.get(project__id=payload.project_id, key="".join( # type:ignore
[payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key]))
# 直接把测试项的标识给前端处理显示
asert_dict['ident'] = test_instance.ident
# ~~~~~~~~~end~~~~~~~~~
asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'design': design_instance,
"test": test_instance, 'title': payload.name})
asert_dict.pop("round_key")
asert_dict.pop("dut_key")
asert_dict.pop("design_key")
asert_dict.pop("test_key")
asert_dict.pop("testStep")
qs = Case.objects.create(**asert_dict) # type:ignore
# 对testStep单独处理
data_list = []
for item in payload.dict()["testStep"]:
if not isinstance(item, dict):
item = item.dict()
item["case"] = qs
data_list.append(CaseStep(**item))
CaseStep.objects.bulk_create(data_list) # type:ignore
return qs
# 更新测试用例
@route.put("/case/update/{id}", response=CaseCreateOutSchema, url_name="case-update")
@transaction.atomic
def update_case(self, id: int, payload: CaseCreateInputSchema):
# 查到当前
case_qs = Case.objects.get(id=id) # type:ignore
for attr, value in payload.dict().items():
if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key' or attr == 'design_key' or attr == 'test_key':
continue
if attr == 'name':
setattr(case_qs, "title", value)
# testStep处理
if attr == 'testStep':
content_list = case_qs.step.all()
for content_single in content_list:
content_single.delete()
data_list = []
for item in value:
if item['operation'] or item['expect'] or item['result'] or item['passed'] or item['status']:
item["case"] = case_qs
data_list.append(CaseStep(**item))
CaseStep.objects.bulk_create(data_list) # type:ignore
setattr(case_qs, attr, value)
# 处理标识-统一设置为YL
case_qs.ident = case_qs.test.ident
# ~~~~~~~~~end~~~~~~~~~
case_qs.save()
return case_qs
# 删除测试用例
@route.delete("/case/delete", url_name="case-delete")
@transaction.atomic
def delete_case(self, data: DeleteSchema):
# 根据其中一个id查询出dut_id注意这里解决前端框架问题删除后还报错选择的行id
try:
case_single = Case.objects.filter(id=data.ids[0])[0] # type:ignore
except IndexError:
return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容')
test_id = case_single.test.id
test_key = case_single.test.key
multi_delete_case(data.ids, Case)
index = 0
case_all_qs = Case.objects.filter(test__id=test_id).order_by('id') # type:ignore
for single_qs in case_all_qs:
case_key = "".join([test_key, '-', str(index)])
single_qs.key = case_key
index = index + 1
single_qs.save()
return ChenResponse(message="测试用例删除成功!")
# 右键测试项,根据测试子项生成用例
@route.post("/case/create_by_demand", url_name='case-create-by-demand')
def create_case_by_demand(self, demand_node: DemandNodeSchema):
project_qs = get_object_or_404(Project, id=demand_node.project_id)
if demand_node.key and demand_node.key != '':
demand = get_object_or_404(TestDemand, key=demand_node.key, project=project_qs)
# 先查询当前测试项下面有无case
case_exists = demand.tcField.exists()
if case_exists:
return ChenResponse(status=500, code=HTTP_EXISTS_CASES, message='测试项下面有用例,请删除后生成')
# 查询所有测试子项
sub_items = demand.testQField.all()
# 每一个子项都创建一个用例先声明一个列表后面可以bulk_create
index = 0
for sub in sub_items:
user_name = self.context.request.user.name # type:ignore
case_dict = {
'ident': demand.ident,
'name': sub.subName,
'initialization': '软件正常启动,正常运行',
'premise': '软件正常启动,外部接口运行正常',
'summarize': demand.testDesciption,
'designPerson': user_name,
'testPerson': user_name,
'monitorPerson': user_name,
'project': project_qs,
'round': demand.round,
'dut': demand.dut,
'design': demand.design,
'test': demand,
'title': sub.subName,
'key': ''.join([demand_node.key, '-', str(index)]),
'level': '4',
}
case_model = Case.objects.create(**case_dict) # type:ignore
# 创建用例步骤
for demand_step_obj in sub.testStepField.all():
operation = demand_step_obj.operation
case_step_dict = {
'operation': "".join([operation if operation is not None else ""]),
'expect': demand_step_obj.expect,
'result': '', # 暂时为空
'case': case_model, # 指定父级Case模型
}
CaseStep.objects.create(**case_step_dict) # type:ignore
index += 1
# 这里返回一个demand的key用于前端刷新树状图
return ChenResponse(data={'key': demand_node.key}, status=200, code=200, message='测试项自动生成用例成功')
# 测试用例复制/移动到测试项上
@route.get("/case/copy_or_move_to_demand", url_name='case-copy-move-demand')
@transaction.atomic
def copy_move_case_to_demand(self, project_id: int, case_key: str, demand_key: str, move: bool):
if move: # 移动
old_key, new_key = case_move_to_test(project_id, case_key, demand_key)
else: # 复制
old_key, new_key = case_copy_to_test(project_id, case_key, demand_key)
# 返回刷新树状信息-需要刷新2个原来的case_key和现在的case_key
return ChenResponse(data={'oldCaseKey': {'key': old_key}, 'newCaseKey': {'key': new_key}})
# 测试用例复制/移动到用例
@route.get("/case/copy_or_move_by_case", url_name='case-copy-move-case')
@transaction.atomic
def copy_move_case_by_case(self, project_id: int, drag_key: str, drop_key: str, move: bool, position: int):
case_to_case_copy_or_move(project_id, drag_key, drop_key, move, position)
return ChenResponse(data={'old': {'key': drag_key}, 'new': {'key': drop_key}})

View File

@@ -0,0 +1,158 @@
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from ninja.errors import HttpError
from utils.chen_pagination import MyPagination
from django.db import transaction
from django.shortcuts import get_object_or_404
from typing import List
from utils.chen_response import ChenResponse
from utils.chen_crud import multi_delete_design
from utils.codes import HTTP_INDEX_ERROR
from apps.project.models import Design, Dut, Round, Project
from apps.project.schemas.design import DeleteSchema, DesignFilterSchema, DesignModelOutSchema, DesignTreeReturnSchema, \
DesignTreeInputSchema, DesignCreateOutSchema, DesignCreateInputSchema, MultiDesignCreateInputSchema
from apps.project.tools.delete_change_key import design_delete_sub_node_key
from utils.smallTools.interfaceTools import conditionNoneToBlank
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['设计需求数据'])
class DesignController(ControllerBase):
@route.get("/getDesignDemandList", response=List[DesignModelOutSchema], exclude_none=True, url_name="design-list")
@transaction.atomic
@paginate(MyPagination)
def get_design_list(self, datafilter: DesignFilterSchema = Query(...)):
conditionNoneToBlank(datafilter)
dut_key = "".join([datafilter.round_id, '-', datafilter.dut_id])
qs = Design.objects.filter(project__id=datafilter.project_id, dut__key=dut_key,
ident__icontains=datafilter.ident,
name__icontains=datafilter.name,
demandType__contains=datafilter.demandType,
chapter__icontains=datafilter.chapter).order_by('id')
return qs
@route.get("/getDesignOne", response=DesignModelOutSchema, url_name='design-one')
def get_dut(self, project_id: int, key: str):
design_qs = Design.objects.filter(project_id=project_id, key=key).first()
if design_qs:
return design_qs
raise HttpError(500, "未找到相应的数据")
# 处理树状数据
@route.get("/getDesignDemandInfo", response=List[DesignTreeReturnSchema], url_name="design-info")
def get_design_tree(self, payload: DesignTreeInputSchema = Query(...)):
qs = Design.objects.filter(project__id=payload.project_id, dut__key=payload.key).order_by('id')
return qs
# 添加设计需求
@route.post("/designDemand/save", response=DesignCreateOutSchema, url_name="design-create")
@transaction.atomic
def create_design(self, payload: DesignCreateInputSchema):
asert_dict = payload.dict(exclude_none=True)
# 如果识别description为None变为空字符串
description = asert_dict.get('description')
# 构造dut_key
dut_key = "".join([payload.round_key, "-", payload.dut_key])
# 判重标识-不需要再查询round以后的
if Design.objects.filter(project__id=payload.project_id, round__key=payload.round_key, dut__key=dut_key,
ident=payload.ident).exists() and asert_dict['ident'] != "":
return ChenResponse(code=400, status=400, message='研制需求的标识重复,请检查')
# 查询当前key应该为多少
design_count = Design.objects.filter(project__id=payload.project_id, dut__key=dut_key).count()
key_string = ''.join([dut_key, "-", str(design_count)])
# 查询当前的round_id
round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key)
dut_instance = Dut.objects.get(project__id=payload.project_id, key=dut_key)
asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'title': payload.name})
asert_dict.pop("round_key")
asert_dict.pop("dut_key")
qs = Design.objects.create(**asert_dict)
return qs
# 批量增加设计需求对应前端批量增加页面modal
@route.post('/designDemand/multi_save', url_name='design-multi-create')
@transaction.atomic
def multi_create_design(self, payload: MultiDesignCreateInputSchema):
project_obj = get_object_or_404(Project, id=payload.project_id)
dut_obj = project_obj.pdField.filter(key=payload.dut_key).first()
round_obj = dut_obj.round
# 当前dut下的design个数
design_count = Design.objects.filter(project=project_obj, dut=dut_obj).count()
key_index = design_count
# 这里根据payload.data批量增加
bulk_list = []
for desgin_obj in payload.data:
design_one = Design(**desgin_obj.model_dump())
design_one.title = design_one.name
# 计算出当前key应该为多少
design_one.key = ''.join([dut_obj.key, "-", str(key_index)])
key_index += 1
design_one.level = '2'
design_one.project = project_obj
design_one.round = round_obj
design_one.dut = dut_obj
bulk_list.append(design_one)
Design.objects.bulk_create(bulk_list)
# 为了前端更新需要返回一个dut_key
return ChenResponse(status=200, code=200, data={'key': dut_obj.key + '-1'})
# 更新设计需求
@route.put("/editDesignDemand/{id}", response=DesignCreateOutSchema, url_name="design-update")
@transaction.atomic
def update_design(self, id: int, payload: DesignCreateInputSchema):
design_search = Design.objects.filter(project__id=payload.project_id, ident=payload.ident,
round__key=payload.round_key)
# 判断是否和同项目同轮次的标识重复
if len(design_search) > 1 and payload.ident != '':
return ChenResponse(code=400, status=400, message='研制需求的标识重复,请检查')
# 查到当前
design_qs = Design.objects.get(id=id)
for attr, value in payload.dict().items():
if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key':
continue
if attr == 'name':
setattr(design_qs, "title", value)
setattr(design_qs, attr, value)
design_qs.save()
return design_qs
# 删除设计需求
@route.delete("/designDemand/delete", url_name="design-delete")
@transaction.atomic
def delete_design(self, data: DeleteSchema):
# 根据其中一个id查询出dut_id
try:
design_single = Design.objects.filter(id=data.ids[0])[0]
except IndexError:
return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容')
dut_id = design_single.dut.id
dut_key = design_single.dut.key
multi_delete_design(data.ids, Design)
index = 0
design_all_qs = Design.objects.filter(dut__id=dut_id).order_by('id')
for single_qs in design_all_qs:
design_key = "".join([dut_key, '-', str(index)])
single_qs.key = design_key
index = index + 1
single_qs.save()
design_delete_sub_node_key(single_qs)
return ChenResponse(message="设计需求删除成功!")
# 给复制功能级联选择器查询所有的设计需求
@route.get("/designDemand/getRelatedDesign", url_name='dut-relatedDesign')
def getRelatedDesign(self, id: int):
project_qs = get_object_or_404(Project, id=id)
# 依次找出round -> dut -> design
round_qs = project_qs.pField.all()
data_list = []
for round in round_qs:
round_dict = {'label': round.name, 'value': round.id, 'children': []}
for dut in round.rdField.all():
dut_dict = {'label': dut.name, 'value': dut.id, 'children': []}
for design in dut.rsField.all():
design_dict = {'label': design.name, 'value': design.id, 'key': design.key}
dut_dict['children'].append(design_dict)
round_dict['children'].append(dut_dict)
data_list.append(round_dict)
return ChenResponse(message='获取成功', data=data_list)

View File

@@ -0,0 +1,246 @@
import os
import tempfile
from copy import deepcopy
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query, File, UploadedFile
from ninja.errors import HttpError
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from utils.chen_pagination import MyPagination
from django.db import transaction
from typing import List
from utils.chen_response import ChenResponse
from utils.chen_crud import multi_delete_dut
from utils.codes import HTTP_INDEX_ERROR
from apps.project.models import Dut, Round, Project, DutMetrics
from django.shortcuts import get_object_or_404
from apps.project.schemas.dut import DutModelOutSchema, DutFilterSchema, DutTreeReturnSchema, DutTreeInputSchema, \
DutCreateInputSchema, DutCreateOutSchema, DeleteSchema, DutCreateR1SoDutSchema
# 导入自动生成design、demand、case的辅助函数
from apps.project.tools.auto_create_data import auto_create_jt_and_dm, auto_create_wd
from apps.project.tools.delete_change_key import dut_delete_sub_node_key
from utils.smallTools.interfaceTools import model_retrieve
# 导入代码统计函数
from apps.project.tool.source_counter import analyze_code_directory, extract_and_get_paths
# 导入需求解析类
from apps.project.tool.xq_parse import DocxChapterExtractor
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['被测件数据'])
class DutController(ControllerBase):
@route.get("/getDutList", response=List[DutModelOutSchema], exclude_none=True, url_name="dut-list")
@transaction.atomic
@paginate(MyPagination)
def get_dut_list(self, filters: DutFilterSchema = Query(...)):
qs = model_retrieve(filters, Dut.objects, ['project_id', 'round_id']).order_by("-create_datetime")
qs = qs.filter(project__id=filters.project_id, round__key=filters.round_id)
return qs
# 处理树状数据
@route.get("/getDutInfo", response=List[DutTreeReturnSchema], url_name="dut-info")
def get_round_tree(self, payload: DutTreeInputSchema = Query(...)):
qs = Dut.objects.filter(project__id=payload.project_id, round__key=payload.key)
return qs
# 获取单个dut
@route.get("/getDutOne", response=DutModelOutSchema, url_name="dut-one")
@transaction.atomic
def get_dut(self, project_id: int, key: str):
dut_qs = Dut.objects.filter(project_id=project_id, key=key).first()
if dut_qs:
return dut_qs
raise HttpError(500, "未找到相应的数据")
# 添加被测件
@route.post("/dut/save", url_name="dut-create", response=DutCreateOutSchema)
@transaction.atomic
def create_dut(self, payload: DutCreateInputSchema):
asert_dict = payload.dict(exclude_none=True)
# 当被测件为SO时一个轮次只运行有一个
if payload.type == 'SO':
if Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key, type='SO').exists():
return ChenResponse(code=400, status=400, message='源代码被测件一个轮次只能添加一个')
# 判重标识
if Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key,
ident=payload.ident).exists():
return ChenResponse(code=400, status=400, message='被测件的标识重复,请检查')
# 查询当前key应该为多少
dut_count = Dut.objects.filter(project__id=payload.project_id, round__key=payload.round_key).count()
key_string = ''.join([payload.round_key, "-", str(dut_count)])
# 然后在标识后面加上UT+KEY -> 注意删除时也改了key要对应修改blink1->>>>>>
asert_dict['ident'] = ''.join([asert_dict['ident'], str(dut_count + 1)])
# 查询当前的round_id
round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key)
asert_dict.update({'key': key_string, 'round': round_instance, 'title': payload.name})
asert_dict.pop("round_key")
qs = Dut.objects.create(**asert_dict)
return qs
# 更新被测件
@route.put("/dut/update/{id}", url_name="dut-update", response=DutCreateOutSchema)
@transaction.atomic
def update_dut(self, id: int, payload: DutCreateInputSchema):
dut_search = Dut.objects.filter(project__id=payload.project_id, ident=payload.ident)
# 判断是否和同项目同轮次的标识重复
if len(dut_search) > 1:
return ChenResponse(code=400, status=400, message='被测件的标识重复,请检查')
# 查到当前
if payload.type == 'SO':
dut_qs = Dut.objects.get(id=id)
for attr, value in payload.dict().items():
if attr == 'project_id' or attr == 'round_key':
continue
if attr == 'name':
setattr(dut_qs, "title", value)
setattr(dut_qs, attr, value)
dut_qs.save()
return dut_qs
else:
dut_qs = Dut.objects.get(id=id)
for attr, value in payload.dict().items():
if attr == 'project_id' or attr == 'round_key':
continue
if attr == 'total_lines' or attr == 'effective_lines' or attr == 'comment_lines':
setattr(dut_qs, attr, "")
continue
if attr == 'name':
setattr(dut_qs, "title", value)
setattr(dut_qs, attr, value)
dut_qs.save()
return dut_qs
# 删除被测件 - 1.重新对key排序 2.重新对表示尾号排序
@route.delete("/dut/delete", url_name="dut-delete")
@transaction.atomic
def delete_dut(self, data: DeleteSchema):
# 查询某一个dut对象
try:
dut_single = Dut.objects.filter(id=data.ids[0])[0]
except IndexError:
return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容')
# 查询出dut所属的轮次id、key
round_id = dut_single.round.id
round_key = dut_single.round.key
# blink1->>>>>> 这里不仅重排key还要重排ident中编号,先取出前面的RXXXX-RX等信息,这里必须要在删除之前
# 查询出当前轮次所有dut
ids = deepcopy(data.ids)
message = '被测件删除成功'
for id in data.ids:
dut_obj = Dut.objects.filter(type='SO', id=id).first()
if dut_obj:
ids.remove(id)
message = '源代码被测件不能删除'
multi_delete_dut(ids, Dut)
dut_all_qs = Dut.objects.filter(round__id=round_id).order_by('id')
ident_before_string = dut_all_qs[0].ident.split("UT")[0] # 输出类似于“R2233-R1-”
index = 0
for single_qs in dut_all_qs:
dut_key = "".join([round_key, '-', str(index)]) # 重排现有的dut的key
single_qs.key = dut_key
single_qs.ident = ident_before_string + "UT" + str(index + 1)
index = index + 1
single_qs.save()
# 不仅重排自己的还要改所有子类的key因为还是之前的key
dut_delete_sub_node_key(single_qs)
return ChenResponse(message=message)
# 查询项目中第一轮次是否存在源代码的被测件 -> 5月16日更改查每一轮是否有源代码被测件
@route.get("/dut/soExist", url_name="dut-soExist")
@transaction.atomic
def delete_soExist(self, id: int):
project_obj = get_object_or_404(Project, id=id)
# 先查询项目的所有轮次
round_qs = project_obj.pField.all()
data = {
'round_count': round_qs.count(),
'round_list': []
}
for round_obj in round_qs:
so_dut_exists = round_obj.rdField.filter(type='SO').exists()
round_dict = {
'key': round_obj.key,
'isExists': so_dut_exists
}
data['round_list'].append(round_dict)
return ChenResponse(code=200, status=200, message='在data展示轮次是否有源代码信息', data=data)
# 弹窗添加第一轮被测件源代码信息,另外创建测试项(静态分析、代码审查),测试用例(静态分析、代码审查)
@route.post("/dut/createR1Sodut", response=DutCreateOutSchema, url_name='dut-r1SoDut')
@transaction.atomic
def create_r1_so_dut(self, data: DutCreateR1SoDutSchema):
asert_dict = data.dict(exclude_none=True) # asert_dict['round_key']可以获取是第几轮次
round_key = asert_dict.pop('round_key')
project_obj = get_object_or_404(Project, id=data.project_id)
if Dut.objects.filter(project__id=data.project_id, round__key=round_key, type='SO').exists():
return ChenResponse(code=400, status=400, message='源代码被测件一个轮次只能添加一个')
# 查询当前key应该为多少
dut_count = Dut.objects.filter(project__id=data.project_id, round__key=round_key).count()
key_string = ''.join([round_key, "-", str(dut_count)])
asert_dict['ident'] = "-".join(
[project_obj.ident, ''.join(['R', str(int(round_key) + 1)]), 'UT', str(dut_count + 1)]).replace("UT-", "UT")
# 查询round_id
round_id = project_obj.pField.filter(key=round_key).first().id
asert_dict['round_id'] = round_id
asert_dict.update({'key': key_string, 'title': '软件源代码', 'type': 'SO', 'name': '软件源代码', 'level': '1'})
dut_qs: Dut = Dut.objects.create(**asert_dict)
# 到这里就自动生成了第一轮的源代码dut下面使用辅助函数自动生成静态分析、代码审查
user_name = self.context.request.user.name
# 注意判断如果非第一轮次
# 1.自动生成静态分析、代码审查
auto_create_jt_and_dm(user_name, dut_qs, project_obj)
# 2.自动生成文档审查在源代码被测件中
auto_create_wd(user_name, dut_qs, project_obj)
return dut_qs
# 进入dut页面返回dut的类型例如XQ/XY/SO
@route.get('/dut/dut_type', url_name='testDemand-type')
@transaction.atomic
def get_dut_type(self, project_id: int, key: str):
project_qs = get_object_or_404(Project, id=project_id)
dut = project_qs.pdField.filter(key=key).first()
return ChenResponse(code=200, status=200, data={'dut_type': dut.type})
@api_controller("/dut_upload", tags=['上传源代码/上传需求规格说明解析'])
class UploadController(ControllerBase):
# 上传zip、7z、rar压缩文件然后计算圈复杂度等信息
@route.post("/upload_file", url_name='dut-upload-file')
def upload_code_lines(self, dut_id: int, file: File[UploadedFile]):
# 获取dut对象
dut_qs: Dut = get_object_or_404(Dut, id=dut_id)
# 创建临时目录
with tempfile.TemporaryDirectory() as tmp_dir:
# 保存上传的ZIP文件
zip_path = os.path.join(tmp_dir, file.name)
with open(zip_path, 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
# 解压并获取文件路径
source_root = extract_and_get_paths(zip_path, tmp_dir)
results = analyze_code_directory(source_root)
# 判断是否录入了metrics并且去掉ORM不需要字段
key_to_remove = {'comment_rate', 'total_lines', 'effective_lines', 'comment_lines', 'code_ratio'}
create_results = {k: v for k, v in results.items() if k not in key_to_remove}
# 这是判断反向外键是否存在的关键
if not hasattr(dut_qs, 'metrics'):
DutMetrics.objects.create(**create_results, dut=dut_qs)
DutMetrics.objects.filter(dut=dut_qs).update(**create_results)
# 进行储存
dut_qs.total_lines = results['total_lines']
dut_qs.effective_lines = results['effective_lines']
dut_qs.comment_lines = results['comment_lines']
dut_qs.save()
return results
# 上传需求规格说明.docx进行解析
@route.post("/upload_xq_docx/", url_name='dut-xq-docx')
def upload_xq_docx(self, dut_key: str, project_id: int, file: File[UploadedFile]):
# 构建临时目录
with tempfile.TemporaryDirectory() as tmp_dir:
# 保存到临时目录
docx_path = os.path.join(tmp_dir, file.name)
with open(docx_path, 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
extractor = DocxChapterExtractor(docx_path)
extractor.main('需求')

View File

@@ -0,0 +1,332 @@
import datetime
import numpy as np
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from apps.dict.models import DictItem
from utils.chen_pagination import MyPagination
from django.db import transaction
from typing import List, Optional
from utils.chen_response import ChenResponse
from utils.codes import HTTP_INDEX_ERROR
from django.shortcuts import get_object_or_404
from apps.project.models import Case, Problem, Project, TestDemand
from apps.project.schemas.problem import (
DeleteSchema,
ProblemModelOutSchema,
ProblemFilterSchema,
ProblemCreateOutSchema,
ProblemCreateInputSchema,
ProblemSingleInputSchema,
ProblemUpdateInputSchema,
ProblemFilterWithHangSchema
)
from utils.util import get_str_abbr
from utils.smallTools.interfaceTools import conditionNoneToBlank
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['问题单系列'])
class ProblemController(ControllerBase):
@route.get("/getProblemList", response=List[ProblemModelOutSchema], exclude_none=True,
url_name="problem-list")
@transaction.atomic
@paginate(MyPagination)
def get_problem_list(self, data: ProblemFilterSchema = Query(...)):
project_id = data.project_id
conditionNoneToBlank(data)
case_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id])
# 先查询出对应的case
case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
# 然后进行过滤
qs = case_obj.caseField.filter(project__id=data.project_id,
ident__icontains=data.ident,
name__icontains=data.name,
status__icontains=data.status,
type__icontains=data.type,
grade__icontains=data.grade,
operation__icontains=data.operation,
postPerson__icontains=data.postPerson,
).order_by("id")
# 遍历通过代码不通过ORM查询闭环方式-巧妙使用numpy中array对象的in方法来判断
closeMethod1 = self.context.request.GET.get("closeMethod[0]")
closeMethod2 = self.context.request.GET.get("closeMethod[1]")
query_add_closeMethod = []
for query in qs:
arr = np.array(query.closeMethod)
if closeMethod1 is None and closeMethod2 is None:
query_add_closeMethod.append(query)
continue
if closeMethod1 in arr:
query_add_closeMethod.append(query)
continue
if closeMethod2 in arr:
query_add_closeMethod.append(query)
continue
return query_add_closeMethod
# 搜索全部问题单/或查询轮次下的问题单
@route.get('/problem/searchAllProblem', response=List[ProblemModelOutSchema], exclude_none=True,
url_name="problem-allList")
@transaction.atomic
@paginate(MyPagination)
def get_all_problems(self, round_key: Optional[str] = False, data: ProblemFilterWithHangSchema = Query(...)):
project_id = data.project_id
for attr, value in data.__dict__.items():
if getattr(data, attr) is None:
setattr(data, attr, '')
# 先查询当前项目
qs = Problem.objects.filter(project__id=data.project_id,
ident__icontains=data.ident,
name__icontains=data.name,
status__icontains=data.status,
type__icontains=data.type,
grade__icontains=data.grade,
operation__icontains=data.operation,
postPerson__icontains=data.postPerson,
).order_by("id")
closeMethod1 = self.context.request.GET.get("closeMethod[0]")
closeMethod2 = self.context.request.GET.get("closeMethod[1]")
query_final = []
for query in qs:
arr = np.array(query.closeMethod)
if closeMethod1 is None and closeMethod2 is None:
query_final.append(query)
continue
if closeMethod1 in arr:
query_final.append(query)
continue
if closeMethod2 in arr:
query_final.append(query)
continue
# 遍历所有problem查询是有否有关联case如果有则设置hang为True否则False
hang = True
# 过滤不是该轮次的问题单对象列表
deleted_problem_list = []
for pro_obj in query_final:
case_exists = pro_obj.case.exists()
if not case_exists:
setattr(pro_obj, "hang", hang)
# 如果有关联用例还要看是否是查询轮次的问题单,过滤出去
elif case_exists:
hang = False
setattr(pro_obj, "hang", hang)
hang = True
if round_key:
if not pro_obj.case.filter(round__key=round_key).exists():
deleted_problem_list.append(pro_obj)
for dq in deleted_problem_list:
query_final.remove(dq)
# !!!如果是轮次查询则返回轮次如果是关联查询则查询关联当前的case
if round_key:
pass
else:
case_obj = Case.objects.filter(project_id=project_id, key=data.key).first()
if case_obj:
for pro_obj in query_final:
# 查询关联的case
related = False
for re_case in pro_obj.case.all():
if case_obj.id == re_case.id:
related = True
setattr(pro_obj, "related", related)
# 过滤查询悬挂逻辑
query_last = []
if data.hang == '3' or data.hang == '': # 疑问:为什么会是空字符串
query_last = query_final
if data.hang == '2':
for pp in query_final:
if not pp.hang:
query_last.append(pp)
if data.hang == '1':
for pp in query_final:
if pp.hang is True:
query_last.append(pp)
return query_last
@staticmethod
def __date_solve(payload: ProblemCreateInputSchema):
"""辅助函数:
1.设置问题单时间而不是默认进入时间传入schema对象返回schema对象只对里面时间进行处理
"""
project_obj = get_object_or_404(Project, id=payload.project_id)
round_obj = project_obj.pField.filter(key=payload.round_key).first()
if round_obj:
if payload.postDate is None:
payload.postDate = round_obj.beginTime + datetime.timedelta(days=1)
if payload.designDate is None:
payload.designDate = round_obj.beginTime + datetime.timedelta(days=2)
return payload
# 添加问题单
@route.post("/problem/save", response=ProblemCreateOutSchema, url_name="problem-create")
@transaction.atomic
def create_case_demand(self, payload: ProblemCreateInputSchema):
payload = self.__date_solve(payload)
asert_dict = payload.dict()
project_id = payload.project_id
# 查询problem的总数
problem_count = Problem.objects.filter(project_id=project_id).count()
# 查询当前各个前面节点的instance
pop_keys: List[str] = ["round_key", "dut_key", "design_key", "test_key", "case_key"]
for pkey in pop_keys:
asert_dict.pop(pkey)
# 处理问题单标识PT_项目ident_数目依次增加
asert_dict["ident"] = str(problem_count + 1)
qs = Problem.objects.create(**asert_dict)
# 处理时间
qs.postDate = payload.postDate
qs.designDate = payload.designDate
qs.save()
# 分两个逻辑处理,无关联创建问题单/case下面创建问题单
if payload.case_key:
# 构造case_key
case_key = "".join(
[payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key, '-',
payload.case_key])
# 查询出所属的case
case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
qs.case.add(case_obj)
qs.save()
# 对problem的ident排序
self.reset_problem_ident(project_id)
return qs
# 更新问题单
@route.put("/problem/update/{id}", response=ProblemCreateOutSchema, url_name="problem-update")
@transaction.atomic
def update_problem(self, id: int, payload: ProblemCreateInputSchema):
# 查到当前
problem_qs = Problem.objects.get(id=id)
for attr, value in payload.dict().items():
setattr(problem_qs, attr, value)
problem_qs.save()
return ChenResponse(message="问题单更新成功")
# 弹窗的-更新问题单
@route.put("/problem/modalupdate/{id}", response=ProblemCreateOutSchema, url_name="problem-update")
@transaction.atomic
def update_modal_problem(self, id: int, payload: ProblemUpdateInputSchema):
# 查到当前
problem_qs = Problem.objects.get(id=id)
for attr, value in payload.dict().items():
setattr(problem_qs, attr, value)
problem_qs.save()
return ChenResponse(message="问题单更新成功")
# 删除问题单
@route.delete("/problem/delete", url_name="problem-delete")
@transaction.atomic
def delete_problem(self, data: DeleteSchema):
# 1.查询出所有被删除id
problems = Problem.objects.filter(id__in=data.ids)
if not problems.exists():
return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选取删除内容')
# 4.查询出当前项目id
project_id = None
# 2.循环该取出problem
for problem in problems:
project_id = problem.project_id
# 3. 直接删除case关联然后删除自己
problem.case.clear()
problem.delete()
# 4.找到对应项目的所有problems进行排序
if project_id is not None:
self.reset_problem_ident(project_id)
return ChenResponse(message="问题单删除成功!")
# 根据问题单id返回关联的用例s
@route.get('/getRelativeCases', url_name='problem-relative-case')
@transaction.atomic
def get_relative_cases(self, id: int):
problem_qs = get_object_or_404(Problem, id=id)
cases = problem_qs.case.all()
case_list = []
for case in cases:
case_dict = {
'id': case.id,
'case': case.title,
'round': case.round.title,
'dut': case.dut.title,
'design': case.design.title,
}
demand = case.test
case_dict['demand'] = demand.title
demand_testType_showtitle = get_str_abbr(demand.testType, 'testType')
case_dict['demand_ident'] = "-".join(['XQ', demand_testType_showtitle, demand.ident])
case_list.append(case_dict)
return case_list
# 单独显示问题单页面需要数据
@route.get("/getSingleProblem", url_name="problem-single", response=ProblemCreateOutSchema)
@transaction.atomic
def search_single_problem(self, data: ProblemSingleInputSchema = Query(...)):
key_string = "".join(
[data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id, '-',
data.problem_id])
qs = Problem.objects.get(project__id=data.project_id, key=key_string)
return qs
# 让测试用例关联/取消问题单
@route.get('/problem/relateProblem', exclude_none=True, url_name="problem-allList")
@transaction.atomic
def relate_problem(self, case_key: str, problem_id: int, val: bool): # val是将要变成的值
# 先判断将要变成的值是否为True
problem_obj: Problem = Problem.objects.filter(id=problem_id).first()
project_id = problem_obj.project_id # 根据问题单反推项目id
case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
flag = False # 是否操作成功的标志
if val:
# 这分支是进行关联操作
# 5月15日新需求一个用例只能关联一个问题单
if case_obj.caseField.count() >= 1:
return ChenResponse(code=400, status=400, message='请注意:一个用例只允许关联一个问题单',
data={'isOK': False})
case_obj.caseField.add(problem_obj)
flag = True
else:
case_obj.caseField.remove(problem_obj)
flag = True
# 排序ident
if project_id:
self.reset_problem_ident(project_id)
return ChenResponse(code=200, status=200, message='关联或取消关联成功...',
data={'isOK': flag, 'key': case_obj.key})
# 类方法操作后对problem的ident排序先基于轮次排序然后基于测试项类型排序
@classmethod
def reset_problem_ident(cls, project_id: int):
project_obj: Project = get_object_or_404(Project, id=project_id)
# 获取所有问题单
problem_qs = project_obj.projField.prefetch_related('case').prefetch_related('case__test')
# 待排序列表
not_sorted_problems = []
# 处理为List[Dict]以便后续排序修改ident
for problem in problem_qs:
cases = problem.case.all()
if len(cases):
# 如果关联了case
belong_demand: TestDemand = cases[0].test
# 找到对应测试类型
test_type = DictItem.objects.get(dict__code='testType', key=belong_demand.testType)
# 找到测试类型的sort/找到轮次key
not_sorted_problems.append({
'problem': problem,
'sort': test_type.sort,
'round_key': belong_demand.round.key,
})
else:
# 如果没有关联case
not_sorted_problems.append({
'problem': problem,
'sort': 1024,
'round_key': 1024,
})
# 排序后修改ident
round_sorted_problems = sorted(not_sorted_problems, key=lambda x: int(x['round_key']))
last_sorted_problems = sorted(round_sorted_problems, key=lambda x: int(x['sort']))
# 根据排序修改problem的ident
for index, problem_dict in enumerate(last_sorted_problems):
problem_dict['problem'].ident = str(index + 1)
problem_dict['problem'].save()

View File

@@ -0,0 +1,266 @@
from pathlib import Path
from datetime import date
from typing import List
from shutil import copytree, rmtree
from django.shortcuts import get_object_or_404
from django.db import transaction
from ninja_extra import api_controller, ControllerBase, route
from ninja_extra.permissions import IsAuthenticated
from ninja_jwt.authentication import JWTAuth
from apps.user.models import Users
from utils.chen_pagination import MyPagination
from ninja.pagination import paginate
from ninja import Query
from utils.chen_response import ChenResponse
from utils.chen_crud import create, multi_delete_project
from apps.project.models import Project, Round
from apps.project.schemas.project import ProjectRetrieveSchema, ProjectFilterSchema, ProjectCreateInput, DeleteSchema
from utils.util import get_str_dict
# 时间处理模块
from apps.project.tool.timeList import time_return_to
# 反射工具
from utils.smallTools.interfaceTools import conditionNoneToBlank
media_path = Path.cwd() / 'media'
base_document_path = Path.cwd() / 'conf/base_document'
@api_controller("/testmanage/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['项目表相关'])
class ProjectController(ControllerBase):
@route.get("/index", response=List[ProjectRetrieveSchema])
@paginate(MyPagination)
def list_project(self, filters: ProjectFilterSchema = Query(...)):
conditionNoneToBlank(filters)
# 处理时间范围
start_time = self.context.request.GET.get('searchOnlyTimeRange[0]')
if start_time is None:
start_time = "2000-01-01"
end_time = self.context.request.GET.get('searchOnlyTimeRange[1]')
if end_time is None:
end_time = '9999-01-01'
date_list = [start_time, end_time]
# 前端返回的member
member_list = []
for key, value in self.context.request.GET.items():
if key.find('member') != -1:
member_list.append(self.context.request.GET[key])
qs = Project.objects.filter(
ident__icontains=filters.ident, name__icontains=filters.name,
beginTime__range=date_list, duty_person__icontains=filters.duty_person,
security_level__icontains=filters.security_level,
report_type__icontains=filters.report_type, step__icontains=filters.step,
member__contains=member_list, secret__icontains=filters.secret).order_by(
"-create_datetime")
# 对软件类型进行处理
if filters.soft_type != '':
qs = qs.filter(soft_type=filters.soft_type)
# ~~role:查询项目的负责人和成员:普通用户只能看到自己参加的项目~~
final_qs = []
auth_info: Users = self.context.request.auth
if auth_info:
if auth_info.role != 'admin':
for proj in qs:
if proj.duty_person == auth_info.name or auth_info.name in proj.member:
final_qs.append(proj)
return final_qs
return qs
@route.get("/findOneById/{int:project_id}", response=ProjectRetrieveSchema)
@transaction.atomic
def get_project_by_id(self, project_id: int):
project_obj = get_object_or_404(Project, id=project_id)
return project_obj
@route.post("/save")
@transaction.atomic
def create_project(self, data: ProjectCreateInput):
data_dict = data.dict()
ident_qucover = Project.objects.filter(ident=data.dict()['ident'])
if ident_qucover:
return ChenResponse(code=400, status=400, message="项目标识重复,请重新设置")
qs = create(self.context.request, data_dict, Project)
# 创建项目时候自动添加第一轮测试
if qs:
Round.objects.create(project_id=qs.id, key='0', level='0', title='第1轮测试', name='第1轮测试',
remark='第一轮测试', ident=''.join([qs.ident, '-R1']))
# 在新增项目时,将/conf/base_document 移动到 /media/{项目ident}/下面
src_dir = base_document_path
dist_dir = media_path / qs.ident
try:
copytree(src_dir, dist_dir) # shutil模块直接是复制并命名如果命名文件存在则抛出FileExists异常
except PermissionError:
return ChenResponse(code=500, status=500, message="错误检查是否打开了服务器的conf中的文档关闭后重试")
except FileExistsError:
return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改')
except FileNotFoundError:
return ChenResponse(code=500, status=500, message='文件不存在,请检查')
return ChenResponse(code=200, status=200, message="添加项目成功,并添加第一轮测试")
@route.put("/update/{project_id}")
@transaction.atomic
def update_project(self, project_id: int, payload: ProjectCreateInput):
# 判断标识是否是被允许的字符串
project = self.get_object_or_exception(Project, id=project_id)
old_ident = project.ident
# 更新操作
for attr, value in payload.dict().items():
setattr(project, attr, value)
project.save()
new_ident = project.ident
# 如果新ident不等于老ident则做 1.更新文件夹名称 2.更新所有轮次中的ident
if new_ident != old_ident:
try:
Path(media_path / old_ident).rename(media_path / project.ident)
# 同时要更改round和dut的标识
for r in project.pField.all():
r.ident = r.ident.replace(old_ident, new_ident)
r.save()
for d in project.pdField.all():
d.ident = d.ident.replace(old_ident, new_ident)
d.save()
except PermissionError:
return ChenResponse(code=500, status=500, message="错误,请关闭文件资源管理器再试")
except FileExistsError:
return ChenResponse(code=500, status=500, message='文件标识已存在或输入为空格,请修改')
except FileNotFoundError:
return ChenResponse(code=500, status=500, message='文件不存在,请检查')
return ChenResponse(code=200, status=200, message="项目更新成功")
@route.delete("/delete")
@transaction.atomic
def delete(self, data: DeleteSchema):
idents = multi_delete_project(data.ids, Project)
# 查询media所属项目文件夹并删除
for ident in idents:
project_media_path = media_path / ident
try:
rmtree(project_media_path)
except FileNotFoundError as e:
return ChenResponse(status=400, code=400, message='项目模版目录可能不存在,可能之前已删除')
return ChenResponse(message="删除成功!")
# 看板页面接口
@route.get('/board')
@transaction.atomic
def board(self, id: int):
project_obj = get_object_or_404(Project, id=id)
# 1.项目阶段直接转字符串
step_str = get_str_dict(project_obj.step, 'step')
# 2.返回时间信息
# 3.返回人员信息
# 4.返回研制方信息
# 5.返回用例信息
case_qs = project_obj.pcField.all()
exe_count = 0 # 已执行数量
noexe_count = 0 # 未执行数量
partexe_count = 0 # 部分执行数量
## 5.1 计算已执行的用例数 -> 所以的都通过/未通过才算执行,否则部分执行
for case in case_qs:
steps = case.step.all()
steps_count = steps.count() # 步骤总数
passed_steps_count = steps.filter(passed='1').count()
notPassed_steps_count = steps.filter(passed='2').count()
notExe_steps_count = steps_count - passed_steps_count - notPassed_steps_count
if notExe_steps_count > 0:
# 步骤全是未执行,则用例未执行
if notExe_steps_count == steps_count:
noexe_count += 1
else:
partexe_count += 1
else:
exe_count += 1
# 6.计算问题单数
problems = project_obj.projField.all()
close_count = 0
open_count = 0
for problem in problems:
if problem.status != '1':
open_count += 1
else:
close_count += 1
# 7.将时间提取 todo:后续将计算的事件放入该页面
timers = {'round_time': []}
rounds = project_obj.pField.all()
timers['start_time'] = project_obj.beginTime
timers['end_time'] = project_obj.endTime
for round in rounds:
round_number = int(round.key) + 1
timers['round_time'].append({
'name': f'{round_number}轮次',
'start': round.beginTime,
'end': round.endTime
})
# 8.提取所有需求下面测试项、用例数量
# 9.提取测试类型下面测试项数量、用例数量
data_list = []
for round in rounds:
round_dict = {'name': f'{int(round.key) + 1}轮次', 'desings': [], 'method_demand': {}, 'method_case': {}}
designs = round.dsField.all()
for design in designs:
design_dict = {
'name': design.name,
'demand_count': design.dtField.count(),
'case_count': design.dcField.count()
}
round_dict['desings'].append(design_dict)
demands = round.rtField.all()
for demand in demands:
test_type = get_str_dict(demand.testType, 'testType')
if test_type not in round_dict['method_demand']:
round_dict['method_demand'][test_type] = 1
else:
round_dict['method_demand'][test_type] += 1
cases = round.rcField.all()
for case in cases:
testDemand = case.test
case_type = get_str_dict(testDemand.testType, 'testType')
if case_type not in round_dict['method_case']:
round_dict['method_case'][case_type] = 1
else:
round_dict['method_case'][case_type] += 1
data_list.append(round_dict)
return {
'ident': project_obj.ident,
'name': project_obj.name,
'step': step_str,
'title_info': {
'时间': {
'开始时间': project_obj.beginTime,
'结束时间': project_obj.endTime,
'到现在时间': f"{(date.today() - project_obj.beginTime).days}",
},
'人员': {
'负责人': project_obj.duty_person,
'成员数': len(project_obj.member),
},
'开发方信息': {
'联系人': project_obj.dev_contact,
'电话': project_obj.dev_contact_phone,
'邮箱': project_obj.dev_email
},
'用例数': {
'总数': case_qs.count(),
'已执行': exe_count,
'未执行': noexe_count,
'部分执行': partexe_count,
},
'问题数': {
'总数': problems.count(),
'已闭环': close_count,
'未闭环': open_count,
}
},
'time_line': timers,
'statistics': data_list,
}
# 看板页面的生成文档时间接口
@route.get('/document_time_show')
@transaction.atomic
def document_time_show(self, id: int):
time = time_return_to(id)
return time

View File

@@ -0,0 +1,83 @@
from ninja_extra import api_controller, ControllerBase, route
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from django.db import transaction
from apps.project.models import Round
from apps.project.schemas.round import TreeReturnRound, RoundInfoOutSchema, EditSchemaIn, DeleteSchema, \
CreateRoundOutSchema, CreateRoundInputSchema
from typing import List
from utils.chen_response import ChenResponse
from apps.project.tools.delete_change_key import round_delete_sub_node_key
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['轮次数据'])
class RoundController(ControllerBase):
@route.get("/getRoundInfo/{project_id}", response=List[TreeReturnRound], url_name="round-info")
def get_round_tree(self, project_id):
qs = Round.objects.filter(project__id=project_id).order_by('key')
return qs
@route.get("/getOneRoundInfo", response=RoundInfoOutSchema, url_name="round-one-info")
def get_round_info(self, projectId: str, round: str):
qs = Round.objects.filter(project__id=projectId).order_by('id')
# 这里问题是如果删除中间轮次会出现问题
qs = qs.get(key=round)
return qs
# 更新轮次信息
@route.put("/round/update/{id}", response=RoundInfoOutSchema, url_name="round-update")
def update_round(self, id, payload: EditSchemaIn):
round = self.get_object_or_exception(Round, project__id=payload.project, id=id)
# 去重功能
exist_round = Round.objects.filter(project__id=payload.project)
for exist_r in exist_round:
if exist_r.id != int(id):
if exist_r.ident == payload.ident:
return ChenResponse(code=400, status=400, message='标识和其他重复')
for attr, value in payload.dict().items():
# 不知道为什么多个project
if attr != "project":
setattr(round, attr, value)
round.save()
return ChenResponse(message="轮次信息更新成功")
@route.delete("/round/delete", url_name="round-delete")
@transaction.atomic
def delete_round(self, project_id: str, data: DeleteSchema):
# 先查询该project下面的值
instance = self.get_object_or_exception(Round, project__id=project_id, key=data.key)
if instance.key == '0':
return ChenResponse(code=400, status=400, message="无法删除第一轮次数据")
# 多对多删除下面case关联的problem关系
cases = instance.rcField.all()
for case in cases:
case.caseField.clear()
instance.delete()
# 注意删除中间key必须发生变化重写key
## 先查询出当前有多少轮次
round_all_qs = Round.objects.filter(project__id=project_id).order_by('id')
## 1.按顺序将轮次的key从1~N排序 2.并且将ident改为key值一样 3.将名称改为对应
index = 0
for single_qs in round_all_qs:
old_key = single_qs.key
single_qs.key = str(index)
single_qs.ident = single_qs.ident.replace(f'R{int(old_key) + 1}', f'R{index + 1}')
single_qs.name = single_qs.name.replace(str(int(old_key) + 1), str(index + 1))
single_qs.title = single_qs.name
index = index + 1
single_qs.save()
round_delete_sub_node_key(single_qs)
return ChenResponse(message="删除成功")
@route.post("/round/save", response=CreateRoundOutSchema, url_name="round-create")
def create_round(self, project_id: str, data: CreateRoundInputSchema):
asert_dict = data.dict()
asert_dict['project_id'] = int(project_id)
asert_dict['title'] = asert_dict['name']
# 标识去重
exist_round = Round.objects.filter(project__id=project_id)
for exist_r in exist_round:
if exist_r.id != int(project_id):
if exist_r.ident == asert_dict['ident']:
return ChenResponse(code=400, status=400, message='标识和其他重复')
Round.objects.create(**asert_dict)
return ChenResponse(message="新增轮次成功")

View File

@@ -0,0 +1,253 @@
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from ninja.errors import HttpError
from utils.chen_pagination import MyPagination
from django.db import transaction
from django.shortcuts import get_object_or_404
from typing import List
from utils.chen_response import ChenResponse
from utils.chen_crud import multi_delete_testDemand
from utils.codes import HTTP_INDEX_ERROR
from apps.project.models import Design, Dut, Round, TestDemand, TestDemandContent, TestDemandContentStep
from apps.project.schemas.testDemand import DeleteSchema, TestDemandModelOutSchema, TestDemandFilterSchema, \
TestDemandTreeReturnSchema, TestDemandTreeInputSchema, TestDemandCreateOutSchema, TestDemandCreateInputSchema, \
TestDemandRelatedSchema, TestDemandExistRelatedSchema, DemandCopyToDesignSchema
# 导入ORM
from apps.project.models import Project
# 导入工具
from apps.project.tools.copyDemand import demand_copy_to_design
from apps.project.tools.delete_change_key import demand_delete_sub_node_key
from utils.smallTools.interfaceTools import conditionNoneToBlank
@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['测试项接口'])
class TestDemandController(ControllerBase):
@route.get("/getTestDemandList", response=List[TestDemandModelOutSchema], exclude_none=True,
url_name="testDemand-list")
@transaction.atomic
@paginate(MyPagination)
def get_test_demand_list(self, datafilter: TestDemandFilterSchema = Query(...)):
conditionNoneToBlank(datafilter)
design_key = "".join([datafilter.round_id, '-', datafilter.dut_id, '-', datafilter.design_id])
qs = TestDemand.objects.filter(project__id=datafilter.project_id, design__key=design_key,
ident__icontains=datafilter.ident,
name__icontains=datafilter.name,
testType__contains=datafilter.testType,
priority__icontains=datafilter.priority).order_by("key")
# 由于有嵌套query_set存在把每个测试需求的schema加上一个字段
query_list = []
for query_single in qs:
# 遍历每一个测试子项
sub_list = []
for step_obj in query_single.testQField.all():
setattr(step_obj, "subStep", step_obj.testStepField.all().values())
sub_list.append(step_obj)
setattr(query_single, "testContent", sub_list)
query_list.append(query_single)
return query_list
@route.get("/getTestDemandOne", response=TestDemandModelOutSchema, url_name='testDemand-one')
@transaction.atomic
def get_test_demand_one(self, project_id: int, key: str):
demand_qs = TestDemand.objects.filter(project_id=project_id, key=key).first()
if demand_qs:
sub_list = []
for step_obj in demand_qs.testQField.all():
setattr(step_obj, "subStep", step_obj.testStepField.all().values())
sub_list.append(step_obj)
setattr(demand_qs, "testContent", sub_list)
return demand_qs
raise HttpError(500, "未找到相应的数据")
# 处理树状数据
@route.get("/getTestdemandInfo", response=List[TestDemandTreeReturnSchema], url_name="testDemand-info")
@transaction.atomic
def get_testDemand_tree(self, payload: TestDemandTreeInputSchema = Query(...)):
qs = TestDemand.objects.filter(project__id=payload.project_id, design__key=payload.key)
return qs
# 添加测试项
@route.post("/testDemand/save", response=TestDemandCreateOutSchema, url_name="testDemand-create")
@transaction.atomic
def create_test_demand(self, payload: TestDemandCreateInputSchema):
asert_dict = payload.dict(exclude_none=True)
# ident判重
project_qs = Project.objects.filter(id=payload.project_id).first()
if payload.ident and project_qs:
exists = project_qs.ptField.filter(ident=payload.ident).exists()
if exists:
return ChenResponse(code=500, status=500, message='测试项标识和其他测试项重复,请更换测试项标识!!!')
# 构造design_key
design_key = "".join([payload.round_key, "-", payload.dut_key, '-', payload.design_key])
# 查询当前key应该为多少
test_demand_count = TestDemand.objects.filter(project__id=payload.project_id, design__key=design_key).count()
key_string = ''.join([design_key, "-", str(test_demand_count)])
# 查询当前各个前面节点的instance
round_instance = Round.objects.get(project__id=payload.project_id, key=payload.round_key)
dut_instance = Dut.objects.get(project__id=payload.project_id,
key="".join([payload.round_key, "-", payload.dut_key]))
design_instance = Design.objects.get(project__id=payload.project_id, key="".join(
[payload.round_key, "-", payload.dut_key, '-', payload.design_key]))
asert_dict.update({'key': key_string, 'round': round_instance, 'dut': dut_instance, 'design': design_instance,
'title': payload.name})
asert_dict.pop("round_key")
asert_dict.pop("dut_key")
asert_dict.pop("design_key")
asert_dict.pop("testContent")
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 创建测试项 - 以及子项/子项步骤
qs = TestDemand.objects.create(**asert_dict)
for item in payload.dict()['testContent']:
content_obj = TestDemandContent.objects.create(
testDemand=qs,
subName=item['subName']
)
TestDemandContentStep.objects.bulk_create([
TestDemandContentStep(
testDemandContent=content_obj,
**step.dict() if not isinstance(step, dict) else step
)
for step in item['subStep']
])
return qs
# 更新测试项
@route.put("/testDemand/update/{id}", response=TestDemandCreateOutSchema, url_name="testDemand-update")
@transaction.atomic
def update_testDemand(self, id: int, payload: TestDemandCreateInputSchema):
project_qs = get_object_or_404(Project, id=payload.project_id)
# 查到当前
testDemand_qs = TestDemand.objects.get(id=id)
old_ident = testDemand_qs.ident # 用于判断是否要集体修改case的ident
for attr, value in payload.dict().items():
# 判重复
if attr == 'ident':
if testDemand_qs.ident != value: # 如果ident不和原来相等则要判重复
exists = project_qs.ptField.filter(ident=payload.ident).exists()
if exists:
return ChenResponse(code=500, status=500, message='更换的标识和其他测试项重复')
if attr == 'project_id' or attr == 'round_key' or attr == 'dut_key' or attr == 'design_key':
continue # 如果发现是key则不处理
if attr == 'name':
setattr(testDemand_qs, "title", value)
# 找到attr为testContent的
if attr == 'testContent':
content_list = testDemand_qs.testQField.all()
for content_single in content_list:
# 删除TestDemandContent会把CASCADE的TestDemandContentStep也删除
content_single.delete()
# 添加测试项步骤
for item in value: # 遍历的是testContent字段,所以每个item是TestDemandContent的数据
# 存在subName就添加一个测试子项
if item['subName']:
content_obj = TestDemandContent.objects.create(
testDemand=testDemand_qs,
subName=item["subName"]
)
TestDemandContentStep.objects.bulk_create([
TestDemandContentStep(
testDemandContent=content_obj,
**step.dict() if not isinstance(step, dict) else step
)
for step in item["subStep"]
])
# ~~~2024年5月9日测试项更新标识后还要更新下面用例的标识~~~
if testDemand_qs.ident != old_ident:
for case in testDemand_qs.tcField.all():
case.ident = testDemand_qs.ident
case.save()
return testDemand_qs
# 删除测试项
@route.delete("/testDemand/delete", url_name="testDemand-delete")
@transaction.atomic
def delete_testDemand(self, data: DeleteSchema):
# 根据其中一个id查询出dut_id
try:
test_demand_single = TestDemand.objects.filter(id=data.ids[0])[0]
except IndexError:
return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选择需要删除的内容')
design_id = test_demand_single.design.id
design_key = test_demand_single.design.key
multi_delete_testDemand(data.ids, TestDemand)
index = 0
test_demand_all_qs = TestDemand.objects.filter(design__id=design_id).order_by('id')
for single_qs in test_demand_all_qs:
test_demand_key = "".join([design_key, '-', str(index)])
single_qs.key = test_demand_key
index = index + 1
single_qs.save()
demand_delete_sub_node_key(single_qs) # 删除后需重排子节点
return ChenResponse(message="测试需求删除成功!")
# 查询一个项目的所有测试项
@route.get("/testDemand/getRelatedTestDemand", url_name="testDemand-getRelatedTestDemand")
@transaction.atomic
def getRelatedTestDemand(self, id: int, round: str):
project_qs = get_object_or_404(Project, id=id)
# 找出属于该轮次的所有测试项
round_qs = project_qs.pField.filter(key=round).first()
designs = round_qs.dsField.all()
data_list = []
for design in designs:
design_dict = {'label': design.name, 'value': design.id, 'children': []}
for test_item in design.dtField.all():
test_item_dict = {'label': test_item.name, 'value': test_item.id}
design_dict['children'].append(test_item_dict)
data_list.append(design_dict)
return ChenResponse(message='获取成功', data=data_list)
# 处理desgin关联testDemand接口
@route.post('/testDemand/solveRelatedTestDemand', url_name="testDemand-solveRelatedTestDemand")
@transaction.atomic
def solveRelatedTestDemand(self, data: TestDemandRelatedSchema):
test_item_ids = data.data
non_exist_ids = [x for x in test_item_ids]
project_qs = get_object_or_404(Project, id=data.project_id)
key_str = "-".join([data.round_key, data.dut_key, data.design_key])
design_item = project_qs.psField.filter(key=key_str).first()
if design_item:
# 将test_item_ids中本身具有的测试项从id数组中移除
for test_id in test_item_ids:
for ti in design_item.dtField.all():
if ti.pk == test_id:
non_exist_ids.remove(test_id)
if len(non_exist_ids) <= 0 < len(test_item_ids):
return ChenResponse(status=400, code=200, message='选择的测试项全部存在于当前设计需求中,请重新选择...')
# 先查询现在有的关联测试项
for item in design_item.odField.values('id'):
item_id = item.get('id', None)
if not item_id in test_item_ids:
test_item_obj = TestDemand.objects.filter(id=item_id).first()
design_item.odField.remove(test_item_obj)
for test_item_id in non_exist_ids:
test_items = design_item.odField.filter(id=test_item_id)
if len(test_items) <= 0:
# 查询testDemand
design_item.odField.add(TestDemand.objects.filter(id=test_item_id).first())
else:
return ChenResponse(status=400, code=400, message='设计需求不存在,请检查...')
return ChenResponse(status=200, code=200, message='添加关联测试项成功...')
# 找出已关联的测试项给前端的cascader
@route.post('/testDemand/getExistRelatedTestDemand', url_name="testDemand-getExistRelatedTestDemand")
@transaction.atomic
def getExistRelatedTestDemand(self, data: TestDemandExistRelatedSchema):
project_qs = get_object_or_404(Project, id=data.project_id)
key_str = "-".join([data.round_key, data.dut_key, data.design_key])
design_item = project_qs.psField.filter(key=key_str).first()
ids = []
if design_item:
for item in design_item.odField.all():
ids.append(item.id)
return ids
# 前端测试项右键复制到某个设计需求下面
@route.post('/testDemand/copy_to_design', url_name='testDemand-copy')
@transaction.atomic
def copy_to_design(self, data: DemandCopyToDesignSchema):
"""前端测试项右键复制到某个设计需求下面"""
new_demand_key = demand_copy_to_design(data.project_id, data.demand_key, data.design_id, data.depth)
return ChenResponse(data={'key': new_demand_key})

View File

@@ -0,0 +1,27 @@
from ninja_extra import api_controller, ControllerBase, route
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from django.db import transaction
from django.shortcuts import get_object_or_404
# 导入schema
from apps.project.schemas.treeOperation import CopySchema
# 导入模型
from apps.project.models import Project
# 导入本app工具
from apps.project.tools.keyTools import TreeKey
# 导入项目工具
from utils.chen_response import ChenResponse
@api_controller("/treeOperation", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['树的操作'])
class TreeController(ControllerBase):
@route.post("/copy", url_name="tree-copy")
@transaction.atomic
def tree_copy(self, data: CopySchema):
"""新建下一个轮次,并复制选中的节点"""
project_obj = get_object_or_404(Project, id=data.pid)
round_count = project_obj.pField.count()
tree_keys = data.data
# 逻辑是:如果大节点有值,则复制整个大节点而不关心其子节点
key_tree = TreeKey(tree_keys)
key_tree.copy_tree(round_count, project_obj)
return ChenResponse(code=200, status=200, message='生成轮次成功')