Beispiel #1
0
def update_plan(plan_id):
    try:
        request_data = request.get_json()
        request_data['lastUpdateTime'] = datetime.utcnow()
        if 'enableWXWorkNotify' in request_data and request_data[
                'enableWXWorkNotify']:
            if 'WXWorkAPIKey' not in request_data or not request_data[
                    'WXWorkAPIKey']:
                return jsonify({'status': 'failed', 'data': '请设置企业微信APIKey!'})
        if 'enableDingTalkNotify' in request_data and request_data[
                'enableDingTalkNotify']:
            if 'DingTalkAccessToken' not in request_data or not request_data[
                    'DingTalkAccessToken']:
                return jsonify({
                    'status': 'failed',
                    'data': '请设置钉钉AccessToken!'
                })
        filtered_data = Plan.filter_field(request_data)
        update_response = Plan.update({'_id': ObjectId(plan_id)},
                                      {'$set': filtered_data})
        if update_response["n"] == 0:
            return jsonify({'status': 'failed', 'data': '未找到相应更新数据!'})
        current_app.logger.info(
            "update plan successfully. Plan ID: {}, User:{}".format(
                str(plan_id), current_user.email))
        return jsonify({'status': 'ok', 'data': '更新成功'})
    except BaseException as e:
        current_app.logger.error(
            "update plan failed. User:{}, error:{}".format(
                current_user.email, str(e)))
        return jsonify({'status': 'failed', 'data': '更新失败: %s' % e})
Beispiel #2
0
def add_card():
    log('调用路由')
    form = request.form
    # todo 用户判断
    # u = current_user()
    log(f'Card: {Card}')
    card_id = Card.new(form)
    plan = Plan()
    plan.insert_id(card_id)
    return redirect(url_for('.review', card_id=card_id))
Beispiel #3
0
def add_plan():
    try:
        params = request.get_json()
        params['createAt'] = datetime.utcnow()
        params['status'] = False
        filtered_data = Plan.filter_field(params, use_set_default=True)
        Plan.insert(filtered_data)
        current_app.logger.info(
            "add plan successfully. Plan:{}, User:{}".format(
                str(filtered_data['name']), current_user.email))
        return jsonify({'status': 'ok', 'data': '新建成功'})
    except BaseException as e:
        current_app.logger.error("add plan failed. User:{}, error:{}".format(
            current_user.email, str(e)))
        return jsonify({'status': 'failed', 'data': '新建失败 %s' % e})
Beispiel #4
0
def get_is_parallel_and_execution_range(plan_id):
    res = common.format_response_in_dic(
        Plan.find_one({'_id': ObjectId(plan_id)}))
    is_parallel = res.get("isParallel")
    execution_range = list(
        map(get_project_execution_range, res.get("executionRange")))
    return is_parallel, execution_range
Beispiel #5
0
    def parser(self, soup, title, url):
        tags = []
        writers = []
        subjects = []
        formats = set()
        for tagEle in soup.select(
                "div.container.mt-3 div.d-flex.flex-column.mb-3 div"):
            tagStr = tagEle.text.split(":")

            if tagStr[0] == "科目分類":
                subjects.append(tagStr[1])
            elif tagStr[0] == "作者":
                writers.append(tagStr[1])
            elif tagStr[0] in ["教學指引", "教學媒體", "學習單"]:
                formats.add(
                    self._get_format_from_extension(
                        "." +
                        tagEle.select_one("a").text.replace("檔案", "").lower()))
            elif tagStr[0] == "上架日期":
                pass
            else:
                tags.append(tagStr[0] + ":" + tagStr[1])

        for tag in soup.select(
                "div.container.mt-3 a.badge.badge-pill.badge-info.mb-3"):
            tags.append("網路" + ":" + tag.text)

        grades = []
        for tag in soup.select(
                "div.container.mt-3 a.badge.badge-pill.badge-success.mb-3"):
            try:
                grades += self.audience_parser(tag.text)
            except KeyError as e:
                pass

        content = ""
        img = ""
        for section in soup.select_one(
                "div.d-flex.justify-content-end.border-bottom.mb-3"
        ).next_siblings:
            if type(section) is not NavigableString:
                imgEle = section.select_one("img")
                if imgEle is not None and len(img) == 0:
                    img = imgEle["src"]
            content += str(section)

        return Plan(
            id=self._hash_id(url),
            origin_id=self.origin_id,
            title=title,
            writers=writers,
            tags=list(tags),
            page=url,
            grades=grades,
            subjects=subjects,
            formats=list(formats),
            img=img,
        )
    def registeruser(self):
        # _firstname = request.form.get('firstname', '')
        # _lastname = request.form.get('lastname', '')
        # _email = request.form.get('email', '')
        # _password = request.form.get('password', '')
        # return sendResponse(authmodel.registerUser(_firstname,_lastname,_email,_password))

        errores = []

        email = request.form["email"]
        if Usuario.existe_usuario_con_email(email):
            errores.append(
                'El email ya esta en uso, por favor seleccione otro.')

        contraseña = request.form["password"]
        # https://kite.com/python/answers/how-to-check-if-a-string-contains-letters-in-python
        tiene_letras = contraseña.lower().islower()
        # https://stackoverflow.com/questions/5188792/how-to-check-a-string-for-specific-characters
        tiene_numeros = '0' in contraseña or '1' in contraseña or '2' in contraseña or '3' in contraseña or '4' in contraseña or '5' in contraseña or '6' in contraseña or '7' in contraseña or '8' in contraseña or '9' in contraseña
        tiene_simbolos = "'" in contraseña or '¿' in contraseña or '?' in contraseña or '¡' in contraseña or '!' in contraseña or '#' in contraseña or '@' in contraseña or '.' in contraseña or '-' in contraseña or '_' in contraseña
        longitud_de_caracteres_valido = (contraseña.__len__() >= 8)
        contraseña_valida = tiene_letras and tiene_numeros and tiene_simbolos and longitud_de_caracteres_valido

        if not contraseña_valida:
            errores.append('Contraseña inválida.')

        # El número de la tarjeta tiene que ser de 16 digitos
        # El pin de la tiene que ser de 3 digitos
        tarjetaNumero_valido = (request.form["tarjetaNumero"].__len__() != 16)
        tarjetaPin_valido = (request.form["tarjetaPin"].__len__() != 3)

        # https://learnandlearn.com/python-programming/python-reference/python-get-current-date
        # Pregunto si la fecha de expiración ingresada para la tarjeta es mayor al día de hoy ...
        # ... con esto compruebo si es válida o no.
        feha_de_hoy = datetime.today()
        tarjetaFechaDeExpiracion = datetime.strptime(
            request.form["tarjetaFechaDeExpiracion"], "%Y-%m-%d")
        if (feha_de_hoy >= tarjetaFechaDeExpiracion
            ) or tarjetaNumero_valido or tarjetaPin_valido:
            errores.append(
                'Los datos proporcionados acerca la tarjeta de crédito no son válidos.'
            )

        if errores:
            # Solo entra aca si el arreglo tiene elementos, osea que hay errores.
            planes = Plan.all()
            usuario = request.form
            return render_template('registrar.html',
                                   planes=planes,
                                   errores=errores,
                                   usuario=usuario)
        else:
            # Solo entra aca si el arreglo esta vacio, esto significa que no hay ...
            # ... errores y el registro se realiza de forma exitosa.
            usuario = Usuario.crear(request.form)
            mensaje_de_exito = "Enhorabuena, ¡Su usuario fue creado con exito!"
            return render_template("login.html",
                                   mensaje_de_exito=mensaje_de_exito)
Beispiel #7
0
    async def scrape(self) -> [Plan]:
        plan1 = Plan(
            id=self._hash_id(self._get_unique_path(MAIN_URL, 1)),
            origin_id=self.origin_id,
            title="「泰」Men「菲」常女?─外籍勞工之性別分工",
            page=MAIN_URL,
            formats=[PlanFormat.PDF],
        )

        plan2 = Plan(
            id=self._hash_id(self._get_unique_path(MAIN_URL, 2)),
            origin_id=self.origin_id,
            title="她與他的校園空間",
            page=MAIN_URL,
            formats=[PlanFormat.PDF],
        )

        return [plan1, plan2]
Beispiel #8
0
    async def read_original_plans(cls, file_name: str) -> dict:
        path = cls.get_path(file_name)
        if not os.path.isfile(path):
            return {}

        with cls.open_file(file_name, "r") as file:
            data = json.load(file)
            plans = [Plan.from_dict(dict) for dict in data]
            return {plan.id: plan for plan in plans}
 def usuario_detalles(self):
     usuario = Usuario.encontrar_por_id(session['id'])
     perfiles_creados = Usuario.cantidad_de_perfiles_creados_por_el_usuario_con_id(
         session['id'])
     plan = Plan.encontrar_por_id(usuario['plan_id'])
     return render_template('usuarios/detalles.html',
                            usuario=usuario,
                            plan=plan,
                            perfiles_creados=perfiles_creados)
Beispiel #10
0
def get_plan_report_info(plan_report_id):
    res = TestPlanReport.find_one({'_id': ObjectId(plan_report_id)})
    res = common.format_response_in_dic(res)
    if res.get("planId"):
        res['planName'] = Plan.find_one({
            '_id': ObjectId(res.get("planId"))
        }).get('name')
    return jsonify({'status': 'ok', 'data': common.format_response_in_dic(res)}) if res else \
        jsonify({'status': 'failed', 'data': '未找到该report信息'})
Beispiel #11
0
    def ver_perfiles(self, id):
        user = Usuario.encontrar_por_id(id)
        perfiles = Perfiles.user_id(id)
        plan = Plan.encontrar_por_id(user["plan_id"])

        return render_template("/usuarios/perfiles.html",
                               perfiles=perfiles,
                               usuario=user,
                               plan=plan)
Beispiel #12
0
def check_secret_for_plan(plan_id, secret_key):
    try:
        res = Plan.find_one({"_id": ObjectId(plan_id)})
        secret_token = common.format_response_in_dic(res).get("secretToken")
        return True if secret_key == secret_token else False
    except BaseException as e:
        with app.app_context():
            current_app.logger.error("check_secret_for_plan failed. - %s" %
                                     str(e))
        return False
Beispiel #13
0
    async def scrape(self) -> [Plan]:
        page = "http://www.hnjh.tyc.edu.tw/xoops/uploads/tadnews/file/nsn_7054_4.pdf"

        plan = Plan(
            id=self._hash_id(page),
            origin_id=self.origin_id,
            title="各領域教案彙編",
            page=page,
            formats=[PlanFormat.PDF],
        )
        return [plan]
Beispiel #14
0
    async def _parse(self, url: str, document: BeautifulSoup) -> [Plan]:
        tags = document.select(".C-tableA2, .C-tableA3")
        plans = []
        for index, tag in enumerate(tags):
            plan = Plan(
                id=self._hash_id(self._get_unique_path(url, index)),
                origin_id=self.origin_id,
                title=tag.findAll("td")[2].text,
                page=url,
                formats=[PlanFormat.PDF],
            )

            plans.append(plan)

        return plans
Beispiel #15
0
    def parser(self, soup, title, url):
        tags = []
        grades = []
        writers = []
        subjects = []
        for tag in soup.select(
                "div.author-date div.column.one-second.column_column span.f_c3.title_icon_chevron.m_left_10"
        ):
            tag = tag.text.split(":")
            for content in tag[1].split("."):
                if len(content) == 0:
                    continue
                content = content.strip()
                if tag[0] == "教學設計者":
                    writers.append(content)
                elif tag[0] == "適用對象":
                    grades.append(self.audience_parser(content))
                elif tag[0] == "學習領域":
                    subjects.append(content)
                else:
                    tags.append(tag[0].strip() + ":" + content)

        content = ""
        for section in soup.select(
                "div.entry-content div.the_content_wrapper"):
            for section2 in section.contents:
                # content += html2text(str(section2))
                content += str(section2)

        formats = set()
        for a in soup.select(
                "div.column.one.author-box div.desc-wrapper div.desc a"):
            formats.add(
                self._get_format_from_extension(
                    os.path.splitext(a["href"])[1]))

        return Plan(
            id=self._hash_id(url),
            origin_id=self.origin_id,
            title=title,
            writers=writers,
            tags=list(tags),
            page=url,
            grades=grades,
            subjects=subjects,
            formats=list(formats),
            # description=content
        )
Beispiel #16
0
    async def _parse(self, course: Course, page) -> [Plan]:
        meta = course.meta
        page = HOME_URL + meta.course_page_link

        plan = Plan(
            id=self._hash_id(page),
            origin_id=self.origin_id,
            title=meta.name,
            page=page,
            formats=self._get_formats(course.materials),
            writers=[meta.author.name],
            description=meta.intro,
            grades=course.grades,
            subjects=self._get_subject(course.units),
        )
        return [plan]
Beispiel #17
0
def validate_plan_id(plan_id):
    try:
        res = common.format_response_in_dic(
            Plan.find_one({'_id': ObjectId(plan_id)}))
        execution_range = list(
            map(get_project_execution_range, res.get("executionRange")))
        if len(execution_range) < 1:
            return False
        else:
            return True
    except BaseException as e:
        with app.app_context():
            current_app.logger.error(
                "validate plan_id error, plan_id: {}, error:{}".format(
                    plan_id, str(e)))
        return False
Beispiel #18
0
    def _parse(self, url: str, document: BeautifulSoup) -> [Plan]:
        tags = document.select(".mptattach a")
        plans = []
        for index, tag in enumerate(tags):
            name, extension = os.path.splitext(tag.text)

            plan = Plan(
                id=self._get_unique_path(MAIN_URL, index),
                origin_id=self.origin_id,
                title=name,
                page=url,
                formats=[self._get_format_from_extension(extension)],
            )

            plans.append(plan)

        return plans
Beispiel #19
0
    def modificar_plan(self):
        if request.method == 'GET':
            planes = Plan.all()
            usuario = Usuario.encontrar_por_id(session['id'])
            plan_del_usuario = Plan.encontrar_por_id(usuario['plan_id'])
            return render_template('usuarios/modificar_plan.html',
                                   planes=planes,
                                   id_plan_del_usuario=plan_del_usuario['id'])

        elif request.method == 'POST':
            errores = []
            cantidad_perfiles_del_usuario = Plan.numero_de_perfiles_del_usuario_con_id(
                session['id'])['COUNT(*)']
            plan_nuevo = Plan.encontrar_por_id(request.form['plan_id'])

            if (plan_nuevo['perfiles_max'] >= cantidad_perfiles_del_usuario):
                # Solo entra aca cuando el usuario puede cambiar de plan
                Usuario.modificar_plan_id(session['id'], plan_nuevo['id'])

                usuario = Usuario.encontrar_por_id(session['id'])
                perfiles_creados = Usuario.cantidad_de_perfiles_creados_por_el_usuario_con_id(
                    session['id'])
                plan = Plan.encontrar_por_id(usuario['plan_id'])
                mensaje_de_exito = "Enhorabuena, ¡Su plan fue actualizado con exito!"
                return render_template('usuarios/detalles.html',
                                       usuario=usuario,
                                       plan=plan,
                                       perfiles_creados=perfiles_creados,
                                       mensaje_de_exito=mensaje_de_exito)
            else:
                # Solo entra aca cuando el usuario tiene más perfiles creados de los que permite el plan nuevo.
                # Por lo tanto no se puede cambiar el plan
                planes = Plan.all()
                usuario = Usuario.encontrar_por_id(session['id'])
                plan_del_usuario = Plan.encontrar_por_id(usuario['plan_id'])

                perfiles_a_borrar = abs(plan_nuevo['perfiles_max'] -
                                        cantidad_perfiles_del_usuario)
                errores.append(
                    "El usuario actual excede la cantidad maxima de perfiles permitidos, debera borrar "
                    + str(perfiles_a_borrar) +
                    " perfil/es para poder realizar el cambio.")
                return render_template(
                    'usuarios/modificar_plan.html',
                    planes=planes,
                    id_plan_del_usuario=plan_del_usuario['id'],
                    errores=errores)
Beispiel #20
0
    def _parse(self, url: str, document: BeautifulSoup) -> [Plan]:
        tags = document.select("td")
        plans = []

        for index, tag in enumerate(tags):
            name = tag.find("h3").text

            if "示例" in name:
                break

            plan = Plan(
                id=self._hash_id(self._get_unique_path(url, index)),
                origin_id=self.origin_id,
                title=name,
                page=url,
                formats=[PlanFormat.HTML],
                description=tag.find("p").text.replace(
                    "\r\n                  ", ";"),
            )

            plans.append(plan)

        return plans
Beispiel #21
0
def main():
    f_name = None
    test_passed = False
    parser = create_parser()
    args = parser.parse_args()
    hook_enabled = False
    if args.url:
        f_name = download_file(args.url[0])
    elif args.filename:
        f_name = args.filename[0]
    else:
        print('No valid archive was provided, aborting...')
        exit(-1)

    cont = unpack_file(f_name=f_name)

    if args.vmname:
        if args.planname:
            if args.hook:
                hook_enabled = True
                print('<<<---Looking for hook--->>>')
            plan = Plan(args.planname[0], cont)
            plan.first_init()
            for current_vm in args.vmname:
                plan.vm_list.append(Vm(current_vm, cont, hook_enabled))
            assert len(plan.vm_list) == len(args.vmname)
            plan.validate()
            if not plan.error_state:
                test_passed = True
        else:
            print('No plan was provided, only VM and DV will be inspected')
            vm = Vm(args.vmname[0], cont)
            vm.validate()
            if not vm.error_state:
                test_passed = True
    else:
        print('No VM names were provided, aborting...')
        exit(-1)

    if test_passed:
        print('PASSED')
    else:
        print('FAILED')
Beispiel #22
0
 def register(self):
     planes = Plan.all()
     return render_template('registrar.html', planes=planes)
Beispiel #23
0
def get_plan_detail(plan_id):
    res = Plan.find_one({'_id': ObjectId(plan_id)})
    return common.format_response_in_dic(res)
Beispiel #24
0
parser = argparse.ArgumentParser()
parser.add_argument('--cities',
                    nargs='+',
                    type=str,
                    help='List of cities splitted by "," to add to plan')
parser.add_argument('--file',
                    type=str,
                    help='List of cities splitted by "," to add to plan')
parser.add_argument('out_file', type=str, help='CSV output')
args = parser.parse_args()

if __name__ == "__main__":
    plan = None
    if args.file != None:
        cities_file = open(args.file)
        cities = cities_file.readlines().replace("\n", "").replace(" ", "")
        plan = Plan(cities.split(","))
    elif len(args.cities) > 0:
        plan = Plan(args.cities)
    else:
        exit(0)

    # Print city informations
    print("Population : ", plan.total_population)
    for city in plan.cities:
        print(city.name, ":", city.importance, ",", city.population)

    game = Game(plan, args.out_file)
    print("===== Game", game._id, "=====")
    game.run()
Beispiel #25
0
def get_plan(plan_id):
    res = Plan.find_one({'_id': ObjectId(plan_id)})
    return jsonify({
        'status': 'ok',
        'data': common.format_response_in_dic(res)
    })
Beispiel #26
0
    def parser(self, soup, title, url) -> Plan:
        fileInContent = True
        grades = []
        tags = []
        formats = set()
        for infoRow in soup.select(
                "div.right_dataBox.box_shadow.b_radius div.row.infoRow div.col-12"
        ):
            # tags, grades
            for tagEle in infoRow.select("div.article_tag.rounded"):
                try:
                    grades += self.audience_parser(tagEle.text)
                except KeyError as e:
                    pass
                else:
                    tags.append("運動:" + tagEle.text)

            # formats
            # 舊版網頁會將formats放在article_box
            eles = infoRow.select(
                "div.row.no-gutters.article_box_file div.col.file_name a")
            for a in eles:
                if fileInContent:
                    fileInContent = False
                formats.add(
                    self._get_format_from_extension(
                        os.path.splitext(a.text)[1]))

        # tags, writers
        # 舊版網頁會有些tags放在editBox
        writers = set()
        # https://stackoverflow.com/questions/4188933/how-do-i-select-the-innermost-element
        for p in soup.select("div.editBox p"):
            if not fileInContent:
                key = None
                for strong in p.select("strong"):  # id 1~223
                    if ":" in strong.text:
                        arr = strong.text.split(":")
                        key = arr[0].replace("\u3000", "")
                        if key in ["作者", "姓名"]:
                            v = arr[1].strip()
                            if v.endswith("、"):
                                writers.add(v[:-1])
                            else:
                                writers = writers.union(set(v.split("、")))
                        elif key in ["獎項", "教案名稱"]:
                            tags.append(key + ":" + arr[1])
                    elif key in ["作者", "姓名"]:
                        writers.add(strong.text.replace("、", ""))

            # 223~229 作者藏在內文
            # 231~301 file In Content
            # TODO
            # else: # https://sportsbox.sa.gov.tw/material/detail/231
            # print("file In Content")
            # after=p.select_one("span strong a").next_siblings

        img = ""
        content = soup.select_one("div.article_contentBox div.editBox").text
        # for section in soup.select_one("div.d-flex.justify-content-end.border-bottom.mb-3").next_siblings:
        #     # print(str(section))
        #     if type(section) is not NavigableString:
        #         imgEle=section.select_one("img")
        #         if imgEle is not None and len(img)==0:
        #             img=imgEle['src']
        #     content += str(section)

        return Plan(
            id=self._hash_id(url),
            origin_id=self.origin_id,
            title=title,
            writers=list(writers),
            tags=list(tags),
            page=url,
            grades=grades,
            subjects=["體育"],
            formats=list(formats),
            description=content,
            img=img,
        )
Beispiel #27
0
    def crear_perfil(self, id):
        errores = []
        p = []
        user = Usuario.encontrar_por_id(id)
        perfiles = Perfiles.all()
        planes = Plan.all()
        contador = 0
        ok = False
        if user['plan_id'] == 2:
            for perfil in perfiles:  # cuento cuantos perfiles tengo y los guardo en otro arreglo
                if perfil['id_usuario'] == user['id']:
                    contador = contador + 1
                    p.append(perfil)
            print(p)
            if contador < 4:
                if request.method == 'POST':
                    for per in p:
                        if per['nombre'] == request.form["nombre"]:
                            ok = True
                    if ok == True:
                        errores.append(
                            "Ya existe un perfil con el nombre especificado.")

                    else:
                        foto = request.form['foto']
                        nombre = request.form['nombre']
                        Perfiles.crear(
                            dict([('nombre', nombre), ('foto', foto),
                                  ('id_usuario', id)]))
                        return redirect(
                            url_for('ver_perfiles', id=session['id']))
            else:

                flash('Ya no puede agregar mas contactos!!!!!')
            return render_template("/usuarios/crearPerfil.html",
                                   usuario=user,
                                   errores=errores)
            # return redirect(url_for('ver_perfiles', id=session['id'], errores=errores))
        else:
            if user['plan_id'] == 1:
                for perfil in perfiles:
                    if perfil['id_usuario'] == user['id']:
                        contador = contador + 1
                        p.append(perfil)

            if contador < 2:
                if request.method == 'POST':
                    for per in p:
                        if per['nombre'] == request.form["nombre"]:
                            ok = True
                    if ok == True:
                        errores.append(
                            "Ya existe un perfil con el nombre especificado.")

                    else:
                        foto = request.form['foto']
                        nombre = request.form['nombre']
                        Perfiles.crear(
                            dict([('nombre', nombre), ('foto', foto),
                                  ('id_usuario', id)]))
                        return redirect(
                            url_for('ver_perfiles', id=session['id']))
            else:

                flash('Ya no puede agregar mas contactos!!!!!')

            return render_template("/usuarios/crearPerfil.html",
                                   p=p,
                                   usuario=user,
                                   errores=errores)
Beispiel #28
0
def execute_plan_async(plan_id, plan_report_id, test_plan_report, test_env_id, env_name, protocol, domain,
                       execution_mode="planManual"):
    # validate plan id
    res_plan = common.format_response_in_dic(Plan.find_one({'_id': ObjectId(plan_id)}))
    execution_range = list(map(get_project_execution_range, res_plan.get("executionRange")))
    is_parallel = res_plan.get('isParallel')
    plan_name = res_plan.get('name')
    always_send_mail = res_plan.get('alwaysSendMail')
    alarm_mail_group_list = res_plan.get('alarmMailGroupList')

    enable_wxwork_notify = res_plan.get('enableWXWorkNotify')
    wxwork_api_key = res_plan.get('WXWorkAPIKey')
    mentioned_mobile_list = res_plan.get('WXWorkMentionMobileList')
    always_wxwork_notify = res_plan.get('alwaysWXWorkNotify')

    enable_ding_talk_notify = res_plan.get('enableDingTalkNotify')
    ding_talk_access_token = res_plan.get('DingTalkAccessToken')
    ding_talk_at_mobiles = res_plan.get('DingTalkAtMobiles')
    ding_talk_secret = res_plan.get('DingTalkSecret')
    always_ding_talk_notify = res_plan.get('alwaysDingTalkNotify')

    # test plan report
    test_plan_report['testStartTime'] = datetime.utcnow()
    plan_total_count = 0
    plan_pass_count = 0
    plan_fail_count = 0
    plan_error_count = 0
    plan_start_time = time.time()
    try:
        if is_parallel:
            counts = []
            pool = Pool(processes=len(execution_range))
            for item in execution_range:
                count_dict = pool.apply_async(execute_single_project,
                                              (item, plan_report_id, test_env_id, env_name, protocol, domain,
                                               execution_mode))
                counts.append(count_dict)
            pool.close()
            pool.join()
            for count in counts:
                plan_total_count += int(count.get().get("total_count"))
                plan_pass_count += int(count.get().get("pass_count"))
                plan_fail_count += int(count.get().get("fail_count"))
                plan_error_count += int(count.get().get("error_count"))
        else:
            for item in execution_range:
                count_dict = execute_single_project(item, plan_report_id, test_env_id, env_name, protocol, domain,
                                                    execution_mode)
                plan_total_count += count_dict.get("total_count")
                plan_pass_count += count_dict.get("pass_count")
                plan_fail_count += count_dict.get("fail_count")
                plan_error_count += count_dict.get("error_count")
        test_plan_report['totalCount'] = plan_total_count
        test_plan_report['passCount'] = plan_pass_count
        test_plan_report['failCount'] = plan_fail_count
        test_plan_report['errorCount'] = plan_error_count
        plan_end_time = time.time()
        test_plan_report['spendTimeInSec'] = round(plan_end_time - plan_start_time, 3)
        test_plan_report['createAt'] = datetime.utcnow()
        save_plan_report(test_plan_report)
        if test_plan_report['totalCount'] > 0:
            notify_total_count = test_plan_report['totalCount']
            notify_pass_count = test_plan_report['passCount']
            notify_pass_rate = '{:.2%}'.format(notify_pass_count / notify_total_count)
            # 发送 邮件通知
            alarm_mail_list = []
            if alarm_mail_group_list:
                if isinstance(alarm_mail_group_list, list) and len(alarm_mail_group_list) > 0:
                    alarm_mail_list = get_mails_by_group(alarm_mail_group_list)
                else:
                    raise TypeError('alarm_mail_group_list must be list')
            is_send_mail = ((always_send_mail and isinstance(alarm_mail_list, list) and len(alarm_mail_list) > 0)
                            or (test_plan_report['totalCount'] > test_plan_report['passCount']
                                and isinstance(alarm_mail_list, list) and len(alarm_mail_list) > 0))
            if is_send_mail:
                subject = 'Leo API Auto Test Notify'
                content_plan_result = "<font color='green'>PASS</font>"
                if test_plan_report['totalCount'] > test_plan_report['passCount']:
                    content_plan_result = "<font color='red'>FAIL</font>"
                content = "<h2>Dears:</h2>" \
                          "<div style='font-size:20px'>&nbsp;&nbsp;API Test Plan executed successfully!<br/>" \
                          "&nbsp;&nbsp;Plan Name:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;Environment:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;Status:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;TotalAPICount:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;PassAPICount:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;PassRate:&nbsp;&nbsp; <b>{}</b><br/>" \
                          "&nbsp;&nbsp;<a href=\"http://{}:{}/plan/{}/reportDetail/{}\">Please login platform " \
                          "for details!</a><br/>" \
                          "&nbsp;&nbsp;Report ID: {}<br/>" \
                          "&nbsp;&nbsp;Generated At: {} CST</div>" \
                    .format(plan_name, env_name, content_plan_result, notify_total_count, notify_pass_count,
                            notify_pass_rate, host_ip, host_port, plan_id, plan_report_id, plan_report_id,
                            test_plan_report['createAt'].replace(tzinfo=pytz.utc).astimezone(
                                pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'))
                mail_result = send_cron_email(alarm_mail_list, subject, content)
                if mail_result.get('status') == 'failed':
                    with app.app_context():
                        current_app.logger.error('邮件发送异常: {}'.format(mail_result.get('data')))
                    raise BaseException('邮件发送异常: {}'.format(mail_result.get('data')))

            # 发送企业微信通知
            if enable_wxwork_notify:
                if always_wxwork_notify or test_plan_report['totalCount'] > test_plan_report['passCount']:
                    notify_title = 'Leo API Auto Test Notify'
                    content_plan_result = "<font color='green'>PASS</font>"
                    if test_plan_report['totalCount'] > test_plan_report['passCount']:
                        content_plan_result = "<font color='red'>FAIL</font>"
                    content_text = '''请注意'''
                    content_markdown = '''{} 
                    > Dears:
                        API Test Plan executed successfully!
                        Plan Name: **{}**
                        Environment: **{}**
                        Status: **{}**
                        TotalAPICount: **{}**
                        PassAPICount: **{}**
                        PassRate: **{}**
                        [Please login platform for details!](http://{}:{}/plan/{}/reportDetail/{})
                        Report ID: {}
                        Generated At: {} CST
                        '''.format(notify_title, plan_name, env_name, content_plan_result, notify_total_count,
                                   notify_pass_count,
                                   notify_pass_rate,
                                   host_ip, host_port, plan_id, plan_report_id, plan_report_id,
                                   test_plan_report['createAt'].replace(tzinfo=pytz.utc).astimezone(
                                       pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'))
                    if mentioned_mobile_list and len(mentioned_mobile_list) > 0:
                        notify_res_text = send_notify.send_wxwork_notify_text(content_text, mentioned_mobile_list,
                                                                              wxwork_api_key)
                        if notify_res_text.status_code != 200 or eval(
                                str(notify_res_text.content, encoding="utf-8")).get('errcode') != 0:
                            with app.app_context():
                                current_app.logger.error('企业微信通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                                    notify_res_text.status_code, notify_res_text.content))
                            raise BaseException('企业微信通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                                notify_res_text.status_code, notify_res_text.content))
                    notify_res_markdown = send_notify.send_wxwork_notify_markdown(content_markdown, wxwork_api_key)
                    if notify_res_markdown.status_code != 200 or eval(
                            str(notify_res_markdown.content, encoding="utf-8")).get('errcode') != 0:
                        with app.app_context():
                            current_app.logger.error('企业微信通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                                notify_res_markdown.status_code, notify_res_markdown.content))
                        raise BaseException('企业微信通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                            notify_res_markdown.status_code, notify_res_markdown.content))

            # 发送钉钉通知
            if enable_ding_talk_notify:
                if always_ding_talk_notify or test_plan_report['totalCount'] > test_plan_report['passCount']:
                    notify_title = 'LEO API Auto Test Notify'
                    content_plan_result = "<font color='#00FF00'>PASS</font>"
                    if test_plan_report['totalCount'] > test_plan_report['passCount']:
                        content_plan_result = "<font color='#FF0000'>FAIL</font>"
                    content = "# {}\n" \
                              "API Test Plan executed successfully!\n\n" \
                              " Plan Name: **{}** \n\n" \
                              " Environment: **{}** \n\n" \
                              " Status: **{}** \n\n" \
                              " TotalAPICount: **{}** \n\n" \
                              " PassAPICount: **{}** \n\n" \
                              " PassRate: **{}** \n\n" \
                              " [Please login platform for details!](http://{}:{}/plan/{}/reportDetail/{})\n\n" \
                              " Report ID: **{}** \n\n" \
                              " Generated At: **{}** CST\n\n".format(notify_title, plan_name, env_name,
                                                                     content_plan_result, notify_total_count,
                                                                     notify_pass_count,
                                                                     notify_pass_rate,
                                                                     host_ip, host_port, plan_id, plan_report_id,
                                                                     plan_report_id,
                                                                     test_plan_report['createAt'].replace(
                                                                         tzinfo=pytz.utc).astimezone(
                                                                         pytz.timezone('Asia/Shanghai')).strftime(
                                                                         '%Y-%m-%d %H:%M:%S'))
                    notify_res = send_notify.send_ding_talk_notify_markdown(notify_title, content,
                                                                            ding_talk_access_token,
                                                                            at_mobiles=ding_talk_at_mobiles,
                                                                            secret=ding_talk_secret)
                    if notify_res.status_code != 200 or eval(str(notify_res.content, encoding="utf-8")).get(
                            'errcode') != 0:
                        with app.app_context():
                            current_app.logger.error('钉钉通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                                notify_res.status_code, notify_res.content))
                        raise BaseException('钉钉通知发送异常: ResponseCode:{}, ResponseBody:{}'.format(
                            notify_res.status_code, notify_res.content))
        else:
            raise TypeError('无任何测试结果!')
    except BaseException as e:
        with app.app_context():
            current_app.logger.error("execute_plan_async exception - %s." % str(e))
        return False, "出错了 - %s" % e