From 76dcf99aa6809d9601e4f7ed75c46f0879f638a0 Mon Sep 17 00:00:00 2001 From: youhuo Date: Thu, 23 Jun 2022 20:31:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0testlib=E4=B8=AD?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E5=AE=9E=E4=BD=93=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/__init__.py | 211 ++++++++++++++++++++++++++++++++++++ models/case_model.py | 168 ++++++++++++++++++++++++++++ models/device_model.py | 39 +++++++ models/job_model.py | 42 +++++++ models/outline_model.py | 13 +++ models/plan_model.py | 28 +++++ models/requirement_model.py | 16 +++ models/task_model.py | 28 +++++ models/tone_model.py | 42 +++++++ models/user_models.py | 109 +++++++++++++++++++ 10 files changed, 696 insertions(+) create mode 100644 models/__init__.py create mode 100644 models/case_model.py create mode 100644 models/device_model.py create mode 100644 models/job_model.py create mode 100644 models/outline_model.py create mode 100644 models/plan_model.py create mode 100644 models/requirement_model.py create mode 100644 models/task_model.py create mode 100644 models/tone_model.py create mode 100644 models/user_models.py diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..29d5caa --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,211 @@ +import datetime + +from sqlalchemy import Column, Integer, DateTime, func, select, delete, insert + +from app.db import Base, db +from common.tools import datetime_toString, string_toDatetime + + +class BaseModel(Base): + __abstract__ = True + + def autoset(self, data=None, filter=[]): + if data: + for i in self.__table__.columns: + if i.name in filter: + continue + v = data.get(i.name) + if v is not None: + self.__setattr__(i.name, v) + return self + + def to_dict(self): + res = dict() + for i in self.__table__.columns: + v = getattr(self, i.name) + res[i.name] = v + if isinstance(v, datetime.datetime): + res[i.name] = datetime_toString(v) + return res + + def to_obj(self, data): + return self.autoset(data) + + async def save(self, data=None): + async with db.conn() as session: + session.add(self.autoset(data)) + await session.commit() + return self + + async def remove(self): + async with db.conn() as session: + await session.delete(self) + await session.commit() + + async def update(self, data=None): + async with db.conn() as session: + res = await session.merge(self.autoset(data)) + await session.commit() + return res + + @staticmethod + async def exec_orm(orm): + async with db.conn() as session: + res = await session.execute(orm) + await session.commit() + return res + + @staticmethod + async def query_obj_base(orm): + async with db.conn() as session: + result = (await session.execute(orm)).scalars() + return [i for i in result] + + @staticmethod + async def query_dict_base(orm): + async with db.conn() as session: + result = (await session.execute(orm)).scalars() + return [i.to_dict() for i in result] + + @classmethod + async def query_obj_all(cls, *condition): + async with db.conn() as session: + result = (await session.execute(select(cls).where(*condition))).scalars() + return [i for i in result] + + @classmethod + async def query_dict_all(cls, *condition): + async with db.conn() as session: + result = (await session.execute(select(cls).where(*condition))).scalars() + return [i.to_dict() for i in result] + + @classmethod + async def query_obj_one(cls, *condition): + async with db.conn() as session: + result = (await session.execute(select(cls).where(*condition))).scalars() + res = [i for i in result] + return res[-1] if len(res) > 0 else None + + @classmethod + async def query_dict_one(cls, *condition): + async with db.conn() as session: + result = (await session.execute(select(cls).where(*condition))).scalars() + res = [i.to_dict() for i in result] + return res[-1] if len(res) > 0 else None + + @classmethod + async def batch_delete(cls, *conditions): + data_orm = delete(cls).where(*conditions) + await cls.exec_orm(data_orm) + return + + +class CommonModel(BaseModel): + __abstract__ = True + + id = Column(Integer, primary_key=True, autoincrement=True, comment='主键自增id') + gmt_created = Column(DateTime, default=datetime.datetime.now, comment='创建时间') + gmt_modified = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='修改时间') + + def to_obj(self, data): + return self.autoset(data, ['id', 'gmt_created', 'gmt_modified']) + + async def save(self, data=None): + async with db.conn() as session: + session.add(self.autoset(data, ['id', ])) + await session.commit() + return self + + async def update(self, data=None): + async with db.conn() as session: + res = await session.merge(self.autoset(data, ['id', ])) + await session.commit() + return res + + @classmethod + async def query_page(cls, page_num, page_size, search=None, match=None, desc=False): + if match is None: + match = dict() + if search is None: + search = dict() + condition_list = [] + total_orm = select(func.count(cls.id)) + data_orm = select(cls) + search_set = set(search) + match_set = set(match) + table_set = set([i.name for i in cls.__table__.columns]) + + query_list = search_set & match_set + for i in query_list: + search.pop(i) + + if search: + start_time = search.pop('start_time', None) + end_time = search.pop('end_time', None) + if start_time: + start_time = string_toDatetime(start_time) + condition_list.append(cls.gmt_created >= start_time) + if end_time: + end_time = string_toDatetime(end_time) + condition_list.append(cls.gmt_modified <= end_time) + + query_list = search_set & table_set + for i in query_list: + v = search.get(i) + if isinstance(v, str) and ',' in v: + condition_list.append(getattr(cls, i).in_(v.split(','))) + else: + condition_list.append(getattr(cls, i) == v) + + if match: + query_list = match_set & table_set + for i in query_list: + v = match.get(i) + if v: + condition_list.append(getattr(cls, i).contains(v)) + + if len(condition_list) > 0: + total_orm = total_orm.where(*condition_list) + data_orm = data_orm.where(*condition_list) + if desc: + data_orm = data_orm.order_by(desc.desc()) + data_orm = data_orm.limit(page_size).offset((page_num - 1) * page_size) + + async with db.conn() as session: + total = (await session.execute(total_orm)).scalar() + res = (await session.execute(data_orm)).scalars() + data = [i.to_dict() for i in res] + + if total % page_size == 0: + total_page = total // page_size + else: + total_page = total // page_size + 1 + + return dict( + data=data, + page_num=page_num, + page_size=page_size, + total=total, + total_page=total_page + ) + + @classmethod + async def batch_add(cls, datas, on_duplicate=False): + insert_orm = insert(cls).values(datas) + if not on_duplicate: + await cls.exec_orm(insert_orm) + return + on_duplicate_key_stmt = insert_orm.on_duplicate_key_update( + insert_orm.inserted + ) + await cls.exec_orm(on_duplicate_key_stmt) + return + + @classmethod + async def count(cls, conditions): + total_orm = select(func.count(cls.id)) + if len(conditions) > 0: + total_orm = total_orm.where(*conditions) + async with db.conn() as session: + total = (await session.execute(total_orm)).scalar() + return total diff --git a/models/case_model.py b/models/case_model.py new file mode 100644 index 0000000..b5ec003 --- /dev/null +++ b/models/case_model.py @@ -0,0 +1,168 @@ +from sqlalchemy import Column, String, Boolean, Enum, Integer, JSON, update, delete, select, func +from sqlalchemy.dialects.mysql import insert + +from app import db +from common.enums import Case_Run_Method, Case_Run_Model, Device_Type_EN, Case_Level, Device_Arch, Case_Type +from models import CommonModel + + +class Case(CommonModel): + __tablename__ = 'case' + + name = Column(String(64), nullable=False, unique=True, comment='测试用例名称') + creator = Column(String(64), nullable=False, comment='负责人') + type = Column(Enum(Case_Type), nullable=False, default=Case_Type.OTHERS.value, comment='测试用例类型') + priority = Column(Enum(Case_Level), default=Case_Level.PRIORITY_3.value, nullable=False, comment='测试用例的优先级') + suite_name = Column(String(128), default='default', index=True, comment='测试套名称') + run_method = Column(Enum(Case_Run_Method), default=Case_Run_Method.MANUAL.value, nullable=False, + comment='测试用例执行方式,手动、自动') + run_model = Column(Enum(Case_Run_Model), default=Case_Run_Model.SINGLE.value, nullable=False, + comment='测试用例运行模式,单机、集群') + is_available = Column(Boolean(), nullable=False, default=True, comment='测试用例是否可用') + tone_case = Column(String(128), nullable=True, comment='T-One测试用例,当run_method为自动的时候生效') + device_type = Column(Enum(Device_Type_EN), default=Device_Type_EN.UNLIMIT.value, nullable=False, comment='设备类型') + device_arch = Column(String(256), default=Device_Arch.NOARCH.value, nullable=False, comment='设备架构类型,可多选,默认支撑所有类型') + labels = Column(String(256), nullable=True, default='', comment='设备标签,多个用,隔开') + desc = Column(String(512), default='', nullable=True, comment='用例描述') + pre_condition = Column(String(512), default='', nullable=True, comment='前置条件') + steps = Column(JSON(), nullable=True, comment='操作步骤') + custom_fields = Column(JSON(), nullable=True, comment='测试用例扩展属性') + parent = Column(Integer, nullable=False, default=0, comment='测试用例分类节点') + + +# 这种结构更新容易、删除比较耗时 +class CaseTree(CommonModel): + __tablename__ = 'case_tree' + + name = Column(String(16), nullable=False, comment='用例结构树节点名称') + parent = Column(Integer(), nullable=False, default=0, comment='父节点id') + level = Column(Integer(), nullable=False, default=0, comment='树结构的深度') + path = Column(String(128), index=True, nullable=False, default='/', comment='模块完整路径') + children_nums = Column(Integer(), nullable=False, default=0, comment='子节点数量') + + +class TestSuite(CommonModel): + __tablename__ = 'test_suite' + + name = Column(String(128), nullable=False, unique=True, comment='测试套名称') + creator = Column(String(64), nullable=False, comment='测试套的创建人员') + + +class CaseLabel(CommonModel): + __tablename__ = 'case_label' + + name = Column(String(64), nullable=False, unique=True, comment='用例标签') + creator = Column(String(64), nullable=False, comment='标签创建人') + + +class CaseLabelMap(CommonModel): + __tablename__ = 'case_label_map' + + label_name = Column(String(64), nullable=False, index=True, comment='用例名称') + case_id = Column(Integer, nullable=False, comment='用例ID') + + +async def remove_case_tree_nodes(node_id, nodes): + async with db.conn() as session: + try: + count_orm = select(func.count(Case.id)).where(Case.parent.in_(nodes)) + case_nums = (await session.execute(count_orm)).scalar() + batch_update_orm = update(Case).where(Case.parent.in_(nodes)).values(parent=node_id) + batch_delete_orm = delete(CaseTree).where(CaseTree.id.in_(nodes)) + await session.execute(batch_update_orm) + await session.execute(batch_delete_orm) + await session.commit() + return case_nums, True + except Exception as err: + await session.rollback() + return err, False + + +async def batch_update_cases(ids, values): + batch_update_orm = update(Case).where(Case.id.in_(ids)).values(values) + await Case.exec_orm(batch_update_orm) + return + + +async def update_cases_list(node_id, nodes): + batch_update_orm = update(Case).where(Case.id.in_(nodes)).values(parent=node_id) + await Case.exec_orm(batch_update_orm) + return + + +async def get_distinct_path(start=None): + distinct_orm = select(CaseTree.path).distinct(CaseTree.path) + if start: + condition = f'{start}%' + distinct_orm = distinct_orm.where(CaseTree.path.like(condition)) + result = (await CaseTree.exec_orm(distinct_orm)).scalars() + return [i for i in result] + + +async def insert_case_list(data): + insert_orm = insert(Case).values(data) + on_duplicate_key_stmt = insert_orm.on_duplicate_key_update( + insert_orm.inserted + ) + await Case.exec_orm(on_duplicate_key_stmt) + return + + +async def class_cases_in_nodes(case_ids): + count_orm = select(Case.parent, func.count(Case.parent)).where(Case.id.in_(case_ids)).group_by(Case.parent) + async with db.conn() as session: + results = await session.execute(count_orm) + return [{'node_id': result[0], 'nums': result[1]} for result in results] + + +async def get_path_by_group(): + group_orm = select(CaseTree.path).group_by(CaseTree.path) + async with db.conn() as session: + results = await session.execute(group_orm) + return [result[0] for result in results] + + +async def search_label_map_group_by_name(label_names): + count_orm = select(CaseLabelMap.name, func.count(CaseLabelMap.name)).where(CaseLabelMap.name.in_(label_names)) \ + .group_by(CaseLabelMap.name) + async with db.conn() as session: + results = await session.execute(count_orm) + return results + + +async def get_cases_by_label_names(label_names): + count_orm = select(CaseLabelMap.case_id).where(CaseLabelMap.label_name.in_(label_names)) \ + .group_by(CaseLabelMap.case_id) + async with db.conn() as session: + results = await session.execute(count_orm) + return [f'{result[0]}' for result in results] + + +async def insert_label_map(label_list): + insert_orm = insert(CaseLabelMap).values(label_list) + await CaseLabelMap.exec_orm(insert_orm) + return + + +async def remove_label_map(conditions): + delete_orm = delete(CaseLabelMap).where(*conditions) + await CaseLabelMap.exec_orm(delete_orm) + return + + +async def get_total_label_name(name=None): + conditions = list() + if name: + conditions.append(CaseLabel.name.startswith(name)) + select_orm = select(CaseLabel.name).where(*conditions) + result = (await CaseLabel.exec_orm(select_orm)).scalars() + return [i for i in result] + + +async def get_total_suite_name(name=None): + conditions = list() + if name: + conditions.append(TestSuite.name.startswith(name)) + select_orm = select(TestSuite.name).where(*conditions) + result = (await TestSuite.exec_orm(select_orm)).scalars() + return [i for i in result] diff --git a/models/device_model.py b/models/device_model.py new file mode 100644 index 0000000..aa78173 --- /dev/null +++ b/models/device_model.py @@ -0,0 +1,39 @@ +from sqlalchemy import Column, String, Boolean, Enum, JSON, Integer, update + +from common.enums import Device_Arch, Device_Type_EN +from models import CommonModel + + +class Device(CommonModel): + __tablename__ = 'device' + + name = Column(String(64), nullable=False, comment='设备名称') + arch = Column(Enum(Device_Arch), nullable=False, default=Device_Arch.OTHERS.value, comment='设备架构') + ip = Column(String(16), nullable=False, default='', comment='设备ip') + type = Column(Enum(Device_Type_EN), nullable=False, default=Device_Type_EN.UNLIMIT.value, comment='设备类型') + sn = Column(String(16), nullable=True, comment='设备序号') + status = Column(Boolean(), nullable=False, default=True, comment='设备是否可用') + label = Column(JSON(), nullable=True, default=[], comment='设备标签列表数组') + owner = Column(String(256), nullable=False, comment='设备负责人') + + +class DeviceLabel(CommonModel): + __tablename__ = 'device_label' + + name = Column(String(64), nullable=False, unique=True, index=True, comment='设备标签名称') + color = Column(String(32), nullable=False, default='blue', comment='设备标签颜色') + + +class DeviceLabelRelationship(CommonModel): + __tablename__ = 'device_label_relationship' + + label_id = Column(Integer, nullable=False, index=True, comment='机器Label id') + device_id = Column(Integer, nullable=False, index=True, comment='机器Label id') + is_delete = Column(Boolean, nullable=True, default=False, comment='对应关系是否删除') + + +async def update_relationships(device_id): + batch_update_orm = update(DeviceLabelRelationship).where(DeviceLabelRelationship.device_id == device_id).values( + is_delete=True) + await DeviceLabelRelationship.exec_orm(batch_update_orm) + return diff --git a/models/job_model.py b/models/job_model.py new file mode 100644 index 0000000..2c8af79 --- /dev/null +++ b/models/job_model.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, String, Integer, Enum + +from common.enums import Tone_Job_State, Test_Type, Case_Result, Track_Result +from models import CommonModel + + +class ToneJob(CommonModel): + __tablename__ = 'tone_job' + + name = Column(String(100), nullable=False, comment='tone job名称') + state = Column(Enum(Tone_Job_State), nullable=False, default=Tone_Job_State.PENDING.value, comment='tone job状态') + test_type = Column(Enum(Test_Type), nullable=False, default=Test_Type.FUNCTIONAL.value, comment='测试类型') + tone_job_id = Column(Integer(), nullable=False, default=-1, index=True, comment='tone job id') + tone_job_link = Column(String(100), nullable=False, comment='tone job链接') + task_id = Column(Integer(), nullable=False, default=-1, index=True, comment='测试方案id') + + +class FuncResult(CommonModel): + __tablename__ = 'func_result' + + sub_case_name = Column(String(128), nullable=False, comment='tone job名称') + sub_case_result = Column(Enum(Case_Result), nullable=False, default=Case_Result.SUCCESS.value, comment='功能结果') + tone_suite_id = Column(Integer(), nullable=False, default=-1, index=True, comment='suite id') + tone_case_id = Column(Integer(), nullable=False, default=-1, index=True, comment='tone case id') + tone_job_id = Column(Integer(), nullable=False, default=-1, index=True, comment='tone job id') + task_id = Column(Integer(), nullable=False, default=-1, index=True, comment='测试方案id') + + +class PerfResult(CommonModel): + __tablename__ = 'perf_result' + + metric = Column(String(128), nullable=False, comment='指标名称') + test_value = Column(String(64), nullable=True, comment='测试值') + cv_value = Column(String(64), nullable=True, comment='cv值') + max_value = Column(String(64), nullable=True, comment='最大值') + min_value = Column(String(64), nullable=True, comment='最小值') + unit = Column(String(64), nullable=True, comment='测试单位') + track_result = Column(Enum(Track_Result), nullable=False, default=Track_Result.NA.value, comment='跟踪结果') + tone_suite_id = Column(Integer(), nullable=False, default=-1, index=True, comment='suite id') + tone_case_id = Column(Integer(), nullable=False, default=-1, index=True, comment='tone case id') + tone_job_id = Column(Integer(), nullable=False, default=-1, index=True, comment='tone job id') + task_id = Column(Integer(), nullable=False, default=-1, index=True, comment='测试方案id') diff --git a/models/outline_model.py b/models/outline_model.py new file mode 100644 index 0000000..021ed89 --- /dev/null +++ b/models/outline_model.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, String, select + +from models import CommonModel + + +class Outline(CommonModel): + __tablename__ = 'outline' + + name = Column(String(64), nullable=False, comment='测试大纲文件名') + title = Column(String(128), nullable=False, comment='测试大纲的标题') + owner = Column(String(16), nullable=False, comment='测试大纲负责人') + tid = Column(String(256), nullable=False, comment='oss文件地址') + remark = Column(String(64), nullable=True, comment='测试大纲备注描述信息') diff --git a/models/plan_model.py b/models/plan_model.py new file mode 100644 index 0000000..398978a --- /dev/null +++ b/models/plan_model.py @@ -0,0 +1,28 @@ +from sqlalchemy import Column, String, Integer, JSON, Boolean + +from common.enums import Status_EN +from models import CommonModel + + +class Plan(CommonModel): + __tablename__ = 'plan' + + title = Column(String(64), unique=True, nullable=False, comment='测试方案名称') + req_id = Column(Integer(), nullable=True, default=-1, comment='测试需求id') + req_title = Column(String(128), nullable=True, default='', comment='测试需求标题') + content = Column(JSON(), nullable=True, default=[], comment='测试方案内容') + status = Column(String(16), nullable=False, default=Status_EN.INIT.value, comment='测试方案阶段状态') + cases = Column(String(256), nullable=True, comment='测试用例,多个使用","隔开') + tasks = Column(String(256), nullable=True, comment='测试任务,多个使用","隔开') + reviewers = Column(String(256), nullable=False, comment='方案评审人列表') + owner = Column(String(256), nullable=False, comment='测试需求的负责人,多个以,隔开') + report = Column(Boolean, nullable=False, default=False, comment='测试报告是否已生成') + + +class PlanReview(CommonModel): + __tablename__ = 'plan_review' + + plan_id = Column(Integer(), nullable=False, comment='测试方案id') + reviewer = Column(String(32), nullable=False, comment='测试需方案的评审人') + status = Column(String(16), nullable=False, default=Status_EN.REVIEWING.value, comment='评审状态') + desc = Column(String(512), nullable=True, comment='评审内容') diff --git a/models/requirement_model.py b/models/requirement_model.py new file mode 100644 index 0000000..c748ed6 --- /dev/null +++ b/models/requirement_model.py @@ -0,0 +1,16 @@ +from sqlalchemy import Column, String, Integer, JSON + +from common.enums import Status_EN +from models import CommonModel + + +class Requirement(CommonModel): + __tablename__ = 'requirement' + + title = Column(String(128), nullable=False, comment='测试需求标题') + outline_id = Column(Integer(), nullable=True, comment='测试大纲id') + outline_title = Column(String(64), nullable=True, comment='测试大纲标题') + content = Column(JSON(), nullable=False, comment='测试需求的描述内容') + status = Column(String(16), nullable=False, default=Status_EN.INIT.value, comment='测试需求阶段') + owner = Column(String(16), nullable=False, comment='测试需求的创建人') + assignee = Column(String(256), nullable=False, comment='测试需求的指派人,多个以,隔开') diff --git a/models/task_model.py b/models/task_model.py new file mode 100644 index 0000000..b7ace12 --- /dev/null +++ b/models/task_model.py @@ -0,0 +1,28 @@ +from sqlalchemy import Column, String, Integer, Enum, JSON, update, Boolean + +from common.enums import Status_EN, Task_Run_Method +from models import CommonModel + + +class Task(CommonModel): + __tablename__ = 'task' + + name = Column(String(64), nullable=False, comment='任务名称') + status = Column(String(16), nullable=False, default=Status_EN.INIT.value, comment='任务执行状态') + run_method = Column(Enum(Task_Run_Method), nullable=False, default=Task_Run_Method.AUTO.value, comment='测试任务执行方式') + owner = Column(String(256), nullable=False, comment='任务执行人') + plan_id = Column(Integer(), nullable=False, default=-1, index=True, comment='测试方案id') + plan_title = Column(String(256), nullable=False, default='', comment='测试方案标题') + cases = Column(String(256), nullable=False, comment='测试用例,多个使用,隔开') + desc = Column(String(256), nullable=True, comment='备注') + run_result = Column(JSON(), nullable=True, comment='手动用例执行结果') + config = Column(JSON(), nullable=True, comment='测试任务配置') + device_id = Column(Integer, nullable=True, comment='测试设备ID') + device_ip = Column(String(256), nullable=True, default='-', comment='测试机器IP') + + +async def update_manual_task_status(task_ids): + batch_update_orm = update(Task).where(Task.id.in_(task_ids), Task.run_method == Task_Run_Method.MANUAL).values( + status=Status_EN.RUNNING.value) + await Task.exec_orm(batch_update_orm) + return diff --git a/models/tone_model.py b/models/tone_model.py new file mode 100644 index 0000000..036e01d --- /dev/null +++ b/models/tone_model.py @@ -0,0 +1,42 @@ +from sqlalchemy import Integer, Column, String, select, func + +from app import db +from models import CommonModel + + +# 记录主动从tone拉数据的日志 +class ToneSyncPull(CommonModel): + __tablename__ = 'tone_sync_pull' + + +# Tone主动推送给TestLib +class ToneSyncPush(CommonModel): + __tablename__ = 'tone_sync_push' + + +class ToneCase(CommonModel): + __tablename__ = 'tone_case' + + tone_case_id = Column(Integer, nullable=False, comment='tone用例ID') + tone_case_name = Column(String(256), nullable=False, index=True, comment='tone用例名称') + suite_id = Column(Integer, nullable=False, index=True, comment='suite ID') + suite_name = Column(String(256), nullable=False, index=True, comment='suite用例名称') + test_type = Column(String(64), nullable=True, comment='测试类型') + + +async def query_tone_case_type(): + select_orm = select(ToneCase.test_type).distinct() + async with db.conn() as session: + result = (await session.execute(select_orm)).scalars() + return result + + +async def query_tone_suite(page_num, page_size, condition): + select_orm = select(ToneCase.suite_id, ToneCase.suite_name, func.count(ToneCase.suite_name)) + if len(condition) > 0: + select_orm = select_orm.where(*condition) + select_orm = select_orm.group_by(ToneCase.suite_name) + select_orm = select_orm.limit(page_size).offset((page_num - 1) * page_size) + async with db.conn() as session: + results = await session.execute(select_orm) + return [{'suite_id': result[0], 'suite_name': result[1], 'nums': result[2]} for result in results] diff --git a/models/user_models.py b/models/user_models.py new file mode 100644 index 0000000..7f01cdf --- /dev/null +++ b/models/user_models.py @@ -0,0 +1,109 @@ +from datetime import datetime + +from sqlalchemy import String, Enum, select, or_, Column, Integer, Boolean, update, delete, insert, func + +from app import db +from common.enums import User_Role, User_Role_Review_Status, User_Role_Op_Method +from models import CommonModel + + +# 开源版本用户体系 +class User(CommonModel): + __tablename__ = 'user_open_source' + + user_name = Column(String(256), nullable=True, comment='用户名称') + nick_name = Column(String(64), unique=True, nullable=False, comment='用户昵称,具有唯一性') + email = Column(String(64), unique=True, nullable=False, comment='用户邮箱') + password = Column(String(64), nullable=False, comment='用户密码,前端对密码md5加密后的值') + avatar_url = Column(String(512), nullable=True, comment='用户头像地址') + role = Column(Enum(User_Role), nullable=False, default=User_Role.COMMON.value, comment='用户角色,默认为普通用户') + token = Column(String(256), nullable=False, index=True, comment='api访问凭证') + + +# 社区版本用户体系 +class User_Community(CommonModel): + __tablename__ = 'user_community' + + user_name = Column(String(256), index=True, nullable=True, comment='用户名称') + nick_name = Column(String(64), unique=True, nullable=True, comment='用户昵称,具有唯一性') + email = Column(String(64), unique=True, nullable=False, comment='用户邮箱') + avatar_url = Column(String(512), nullable=True, comment='用户头像地址') + role = Column(Enum(User_Role), nullable=False, default=User_Role.COMMON.value, comment='用户角色,默认为普通用户') + + +class UserRoleOpRecord(CommonModel): + __tablename__ = 'user_role_op_record' + + applicant = Column(String(64), nullable=False, comment='申请人name') + applicant_id = Column(Integer, nullable=False, comment='申请人ID') + apply_reason = Column(String(512), nullable=True, comment='申请理由') + signer = Column(String(64), nullable=True, comment='评审人name') + has_review = Column(Boolean, nullable=False, default=False, comment='是否已经审核') + review_result = Column(Enum(User_Role_Review_Status), nullable=False, default=User_Role_Review_Status.INIT, + comment='审核结果') + review_reason = Column(String(512), nullable=True, comment='评审意见') + method = Column(Enum(User_Role_Op_Method), nullable=False, default=User_Role_Op_Method.UPGRADE, + comment='操作方式') + + +async def find_user_exists(name): + select_orm = select(User).where(or_(User.nick_name == name, User.email == name)) + result = (await User.exec_orm(select_orm)).scalars() + res = [i for i in result] + return res[-1] if len(res) > 0 else None + + +async def upgrade_user_roles(operator, apply_ids, user_ids, review_result, review_reason): + async with db.conn() as session: + try: + apply_update_orm = update(UserRoleOpRecord).where(UserRoleOpRecord.id.in_(apply_ids)) \ + .values(signer=operator, has_review=True, gmt_modified=datetime.now(), + review_result=review_result, review_reason=review_reason) + if review_result == User_Role_Review_Status.PASS: + user_update_orm = update(User).where(User.id.in_(user_ids)) \ + .values(role=User_Role.SENIOR, gmt_modified=datetime.now()) + await session.execute(apply_update_orm) + await session.execute(user_update_orm) + await session.commit() + return None, True + except Exception as err: + await session.rollback() + return err, False + + +async def batch_delete(users, signer): + id_list, op_list = list(), list() + for user in users: + op = { + 'applicant': user.nick_name, + 'applicant_id': user.id, + 'signer': signer, + 'has_review': True, + 'review_result': User_Role_Review_Status.PASS.value, + 'review_reason': '删除普通用户', + 'method': User_Role_Op_Method.DELETE.value + } + op_list.append(op) + id_list.append(user.id) + async with db.conn() as session: + try: + user_del_orm = delete(User).where(User.id.in_(id_list)) + op_insert_orm = insert(UserRoleOpRecord).values(op_list) + await session.execute(user_del_orm) + await session.execute(op_insert_orm) + await session.commit() + return None, True + except Exception as err: + await session.rollback() + return err, False + + +async def get_review_count_infos(): + has_review_orm = select(func.count(UserRoleOpRecord.id)).where(UserRoleOpRecord.has_review == True) + has_review = (await UserRoleOpRecord.exec_orm(has_review_orm)).scalar() + wait_review_orm = select(func.count(UserRoleOpRecord.id)).where(UserRoleOpRecord.has_review == False) + wait_review = (await UserRoleOpRecord.exec_orm(wait_review_orm)).scalar() + return { + 'has_review': has_review, + 'wait_review': wait_review + } -- Gitee