initial commit
This commit is contained in:
13
apps/project/controllers/__init__.py
Normal file
13
apps/project/controllers/__init__.py
Normal 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']
|
||||
BIN
apps/project/controllers/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/case.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/case.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/case.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/case.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/design.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/design.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/design.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/design.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/dut.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/dut.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/dut.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/dut.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/problem.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/problem.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/problem.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/problem.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/project.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/project.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/project.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/project.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/round.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/round.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/round.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/round.cpython-38.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/testDemand.cpython-313.pyc
Normal file
BIN
apps/project/controllers/__pycache__/testDemand.cpython-313.pyc
Normal file
Binary file not shown.
BIN
apps/project/controllers/__pycache__/testDemand.cpython-38.pyc
Normal file
BIN
apps/project/controllers/__pycache__/testDemand.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
245
apps/project/controllers/case.py
Normal file
245
apps/project/controllers/case.py
Normal 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}})
|
||||
158
apps/project/controllers/design.py
Normal file
158
apps/project/controllers/design.py
Normal 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)
|
||||
246
apps/project/controllers/dut.py
Normal file
246
apps/project/controllers/dut.py
Normal 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('需求')
|
||||
332
apps/project/controllers/problem.py
Normal file
332
apps/project/controllers/problem.py
Normal 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()
|
||||
266
apps/project/controllers/project.py
Normal file
266
apps/project/controllers/project.py
Normal 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
|
||||
83
apps/project/controllers/round.py
Normal file
83
apps/project/controllers/round.py
Normal 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="新增轮次成功")
|
||||
253
apps/project/controllers/testDemand.py
Normal file
253
apps/project/controllers/testDemand.py
Normal 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})
|
||||
27
apps/project/controllers/treeOperation.py
Normal file
27
apps/project/controllers/treeOperation.py
Normal 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='生成轮次成功')
|
||||
Reference in New Issue
Block a user