def delete(self, data):
        """
        删除指定学期、教学分组下的所有课表
        :param data:
        {
            "term_uuid": "062d6d14-97c1-448d-a9fb-67367fdf843b",
            "group_uuid": "41b212d6-3ef4-49f1-851d-424cb4559261"
        }
        :return:
        """
        try:
            logger.info("data: %s" % data)
            term_uuid = data.get("term_uuid", None)
            group_uuid = data.get("group_uuid", None)
            if not term_uuid or not group_uuid:
                return get_error_result("ParamError")

            if not db_api.get_term_with_first({"uuid": term_uuid}):
                return get_error_result("TermNotExist")

            if not db_api.get_group_with_first({"uuid": group_uuid}):
                return get_error_result("EduGroupNotExist")

            template_uuids = db_api.get_distinct_course_template_uuids_by_course_schedule(
                {
                    "term_uuid": term_uuid,
                    "group_uuid": group_uuid
                })
            template_uuids = [tuple_[0] for tuple_ in template_uuids]

            ds_ret = db_api.delete_course_schedule_many({
                "term_uuid":
                term_uuid,
                "group_uuid":
                group_uuid
            })
            logger.info(
                "delete many[%s] in yzy_course_schedule success where term_uuid[%s] group_uuid[%s] "
                % (ds_ret, term_uuid, group_uuid))

            dt_ret = db_api.delete_course_template_many_by_uuids(
                template_uuids)
            logger.info(
                "delete many[%s] in yzy_course_template success where uuid in [%s]"
                % (dt_ret, template_uuids))

            dc_ret = db_api.delete_course_many_by_course_template_uuids(
                template_uuids)
            logger.info(
                "delete many[%s] in yzy_course success where course_template_uuid in [%s]"
                % (dc_ret, template_uuids))

            return get_error_result()
        except Exception as e:
            logger.exception("delete course_schedule failed: %s" % str(e),
                             exc_info=True)
            return get_error_result("OtherError")
    def disable(self, data):
        """
        禁用指定学期、教学分组下的所有课表
        :param data:
        {
            "term_uuid": "062d6d14-97c1-448d-a9fb-67367fdf843b",
            "group_uuid": "41b212d6-3ef4-49f1-851d-424cb4559261"
        }
        :return:
        """
        try:
            logger.info("data: %s" % data)
            term_uuid = data.get("term_uuid", None)
            group_uuid = data.get("group_uuid", None)
            if not term_uuid or not group_uuid:
                return get_error_result(
                    "ParamError", data={"keys": ["term_uuid", "group_uuid"]})

            term_obj = db_api.get_term_with_first({"uuid": term_uuid})
            if not term_obj:
                return get_error_result("TermNotExist")

            if not db_api.get_group_with_first({"uuid": group_uuid}):
                return get_error_result("EduGroupNotExist")

            # 更新该学期的group_status_map字段(保存了教学桌面组的课表启用状态)
            group_status_map = json.loads(term_obj.group_status_map)
            if group_status_map.get(
                    group_uuid, None) != constants.COURSE_SCHEDULE_DISABLED:
                group_status_map[
                    group_uuid] = constants.COURSE_SCHEDULE_DISABLED
                term_obj.update(
                    {"group_status_map": json.dumps(group_status_map)})
                logger.info(
                    "update uuid[%s] in yzy_term success : {'group_status_map': {'%s': %s}}"
                    % (term_uuid, group_uuid,
                       constants.COURSE_SCHEDULE_DISABLED))

            # 找出该学期、该教学分组下的所有课表,标记为禁用
            ret = db_api.update_course_schedule_many(
                value_dict={"status": constants.COURSE_SCHEDULE_DISABLED},
                item={
                    "term_uuid": term_uuid,
                    "group_uuid": group_uuid
                })

            logger.info(
                "update many[%s] in yzy_course_schedule success where term_uuid[%s], group_uuid[%s]: {'status': %s}"
                % (ret, term_uuid, group_uuid,
                   constants.COURSE_SCHEDULE_DISABLED))

            return get_error_result()
        except Exception as e:
            logger.exception("disable course_schedule failed: %s" % str(e),
                             exc_info=True)
            return get_error_result("OtherError")
    def delete(self, data):
        """
        删除学期及其关联的所有课表
        :param data: {"uuid": "c9d3cda3-9977-44be-a758-b92c622be97e"}
        :return:
        """
        try:
            logger.info("data: %s" % data)
            term_uuid = data.get("uuid", None)
            if not term_uuid:
                return get_error_result("ParamError")

            term_obj = db_api.get_term_with_first({"uuid": term_uuid})
            if not term_obj:
                return get_error_result("TermNotExist")

            # 删除学期的定时任务
            if not CrontabController().remove_course_crontab_job(
                    term_obj.crontab_task_uuid):
                return get_error_result("OtherError")

            # 找出该学期下所有课表的引用模板的uuid
            template_uuids = db_api.get_distinct_course_template_uuids_by_course_schedule(
                {"term_uuid": term_uuid})
            template_uuids = [tuple_[0] for tuple_ in template_uuids]

            # 删除该学期下所有课表
            ds_ret = db_api.delete_course_schedule_many(
                {"term_uuid": term_uuid})
            logger.info(
                "delete many[%s] in yzy_course_schedule success where term_uuid[%s]"
                % (ds_ret, term_uuid))

            # 删除该学期下所有课表的引用模板
            dt_ret = db_api.delete_course_template_many_by_uuids(
                template_uuids)
            logger.info(
                "delete many[%s] in yzy_course_template success where uuid in [%s]"
                % (dt_ret, template_uuids))

            # 删除该学期下所有课表的引用模板包含的课程
            dc_ret = db_api.delete_course_many_by_course_template_uuids(
                template_uuids)
            logger.info(
                "delete many[%s] in yzy_course success where course_template_uuid in [%s]"
                % (dc_ret, template_uuids))

            # 删除学期
            term_obj.soft_delete()
            logger.info("delete uuid[%s] in yzy_term success" % term_uuid)
            return get_error_result()
        except Exception as e:
            logger.exception("delete term failed: %s" % str(e), exc_info=True)
            return get_error_result("OtherError")
    def update(self, data):
        """
        创建、编辑、删除一个周的课表
        :param data:
        {
            "term_uuid": "5e5244f0-b269-4f2d-a8f8-38e946b07942",
            "group_uuid": "41b212d6-3ef4-49f1-851d-424cb4559261",
            "week_num": 1,
            "course": [
                {
                    "course_num": 1,
                    "mon": {
                              "name": "math_desktop",
                              "uuid": "f56036ca-e91d-440c-8e33-26a18c1f7220"
                    },
                    "tue": {
                              "name": "",
                              "uuid": ""
                    },
                    "wed": {
                              "name": "",
                              "uuid": ""
                    },
                    "thu": {
                              "name": "",
                              "uuid": ""
                    },
                    "fri": {
                              "name": "math_desktop",
                              "uuid": "f56036ca-e91d-440c-8e33-26a18c1f7220"
                    },
                    "sat": {
                              "name": "",
                              "uuid": ""
                    },
                    "sun": {
                              "name": "",
                              "uuid": ""
                    }
                },
                ...
            ]
        }
        :return:
        """
        try:
            logger.info("data: %s" % data)

            # name = data.get("name", None)
            # term = data.get("term", None)
            # term_time_detail = data.get("term_time_detail", None)
            # course_num_map = data.get("course_num_map", None)

            term_uuid = data.get("term_uuid", None)
            group_uuid = data.get("group_uuid", None)
            week_num = data.get("week_num", None)
            course = data.get("course", None)
            if not all([term_uuid, week_num, group_uuid, course]):
                return get_error_result(
                    "ParamError",
                    data={
                        "keys":
                        ["term_uuid", "week_num", "group_uuid", "course"]
                    })

            # if not term in [0, 1]:
            #     logger.error('ParamError: term')
            #     return get_error_result("ParamError")
            #
            # if not self._check_term_time_detail(term_time_detail):
            #     return get_error_result("ParamError")
            #
            # weeks_num_map = self._generate_weeks_num_map(term_time_detail["start"], term_time_detail["end"])
            # if week_num not in weeks_num_map.keys():
            #     logger.error('ParamError: week_num')
            #     return get_error_result("ParamError")
            #
            # course_num_map = self._check_course_num_map(course_num_map)
            # if not course_num_map:
            #     logger.error('ParamError: course_num_map')
            #     return get_error_result("ParamError")
            #
            # if db_api.get_course_schedule_with_first({"name": name}):
            #     return get_error_result("CourseScheduleNameExist")

            term_obj = db_api.get_term_with_first({"uuid": term_uuid})
            if not term_obj:
                return get_error_result("TermNotExist")

            # 校验课表内容,并提取出有效值(课表中非空的格子)
            course_ret_list = list()
            desktops = dict()
            course_num_map = json.loads(term_obj.course_num_map)
            check_ret = self._check_course(course, group_uuid, course_num_map,
                                           course_ret_list, desktops)
            if check_ret:
                return check_ret

            # 用term_uuid、group_uuid、week_num三个字段代替uuid的作用,唯一确定一条yzy_course_schedule数据
            course_schedule_obj = db_api.get_course_schedule_with_first({
                "term_uuid":
                term_uuid,
                "group_uuid":
                group_uuid,
                "week_num":
                week_num
            })

            # 数据库中找不到,并且课表内容不为空,说明是要创建新课表
            if not course_schedule_obj and course_ret_list:
                self._create(term_uuid, group_uuid, week_num, course_ret_list,
                             desktops)
            # 数据库中能找到,并且课表内容不为空,说明是要编辑此课表
            elif course_schedule_obj and course_ret_list:
                self._update(course_schedule_obj, course_ret_list, desktops)
            # 数据库中能找到,并且课表内容为空,说明是要删除此课表
            elif course_schedule_obj and not course_ret_list:
                self._delete(course_schedule_obj)
            # 数据库中找不到,并且课表内容为空,说明是传参错误
            else:
                return get_error_result()
                # return get_error_result("ParamError", data={"course": data["course"]})

            return get_error_result()

            # # 创建课表后默认启用课表
            # course_schedule_obj = db_api.get_course_schedule_with_first(course_schedule["uuid"])
            # ret = self.enable({"uuids": [course_schedule["uuid"]]})
            # if ret.get("code", -1) == 0:
            #     course_schedule_obj["status"] = 0
            #     course_schedule_obj.softupdate()
            #     return get_error_result()
            # else:
            #     # # 启用失败则删除课表
            #     # self._delete_course_template([course_schedule_obj.course_template_uuid])
            #     # course_schedule_obj.soft_delete()
            #     return get_error_result("AddCourseScheduleCrontabError")
        except Exception as e:
            logger.exception("update course_schedule failed: %s" % str(e),
                             exc_info=True)
            return get_error_result("OtherError")
    def update(self, data):
        """
        编辑指定学期
        :param data:
        {
           "uuid": "062d6d14-97c1-448d-a9fb-67367fdf843b",
           "name": "2020年上学期",
           "start": "2020/09/01",
           "end": "2021/02/01",
           "duration": 45,
           "break_time": 10,
           "morning": "08:00",
           "afternoon": "14:00",
           "evening": "19:00",
           "morning_count": 4,
           "afternoon_count": 4,
           "evening_count": 2,
           "course_num_map": {
               "1": "08:00-08:45",
               "2": "09:00-09:45",
               "3": "10:00-10:45",
               "4": "11:00-11:45",
               "5": "14:00-14:45",
               "6": "15:00-15:45",
               "7": "16:00-16:45",
               "8": "17:00-17:45",
               "9": "19:00-19:45",
               "10": "20:00-20:45"
           }
        :return:
        """
        try:
            logger.info("data: %s" % data)
            uuid = data.pop("uuid", None)
            if not uuid:
                return get_error_result("ParamError")

            term_obj = db_api.get_term_with_first({"uuid": uuid})
            if not term_obj:
                return get_error_result("TermNotExist")

            name_exist_obj = db_api.get_term_with_first({"name": data["name"]})
            # 如果学期下已有课表,只允许编辑学期名称
            if db_api.get_course_schedule_with_all({"term_uuid": uuid}):
                if list(data.keys()) != ["name"]:
                    return get_error_result("TermOccupiedError")

                if name_exist_obj and name_exist_obj.uuid != uuid:
                    return get_error_result("TermNameExist")

                term_obj.update({"name": data["name"]})
                crontab_task_obj = db_api.get_crontab_first(
                    {"uuid": term_obj.crontab_task_uuid})
                if crontab_task_obj:
                    crontab_task_obj.update(
                        {"desc": "%s_课表定时任务" % term_obj.name})
                logger.info("update in yzy_term success: {'name': %s}" %
                            term_obj.name)
                return get_error_result()

            # 如果学期下没有课表,可以编辑所有项
            check_ret = self._check_course_num_map(data["course_num_map"])
            if check_ret:
                return check_ret

            check_ret = self._check_term_time_detail(data)
            if check_ret:
                return check_ret

            update_dict = dict()
            for k, v in data.items():
                if isinstance(v, dict):
                    data[k] = json.dumps(data[k])
                if data[k] != getattr(term_obj, k):
                    if k == "name" and name_exist_obj and name_exist_obj.uuid != uuid:
                        return get_error_result("TermNameExist")
                    update_dict[k] = data[k]

            # 如果修改了学期开始,则校验新学期期间是否与已有学期重叠
            if "start" in update_dict.keys() or "end" in update_dict.keys():
                check_ret = self._check_term_duplicate(data["start"],
                                                       data["end"],
                                                       exclued_uuid=uuid)
                if check_ret:
                    return check_ret
                update_dict["weeks_num_map"] = json.dumps(
                    self._generate_weeks_num_map(data["start"], data["end"]))

            if update_dict:
                # 编辑学期时,删除原有定时任务,重新设置
                if not CrontabController().remove_course_crontab_job(
                        term_obj.crontab_task_uuid):
                    return get_error_result("OtherError")

                task_uuid = self._add_crontab_task(
                    term_uuid=term_obj.uuid,
                    name=update_dict.get("name", term_obj.name),
                    start_date=update_dict.get("start", term_obj.start),
                    end_date=update_dict.get("end", term_obj.end),
                    course_num_map=json.loads(
                        update_dict.get("course_num_map",
                                        term_obj.course_num_map)),
                    weeks_num_map=json.loads(
                        update_dict.get("weeks_num_map",
                                        term_obj.weeks_num_map)))

                if task_uuid:
                    update_dict["crontab_task_uuid"] = task_uuid
                    term_obj.update(update_dict)
                    logger.info("update in yzy_term success: %s" % update_dict)
                    return get_error_result()
                else:
                    logger.info("add course crontab task failed")
                    return get_error_result("OtherError")

            return get_error_result()
        except Exception as e:
            logger.exception("update term failed: %s" % str(e), exc_info=True)
            return get_error_result("OtherError")
    def create(self, data):
        """
        创建新学期
        :param data:
        {
           "name": "2020年上学期",
           "start": "2020/09/01",
           "end": "2021/02/01",
           "duration": 45,
           "break_time": 10,
           "morning": "08:00",
           "afternoon": "14:00",
           "evening": "19:00",
           "morning_count": 4,
           "afternoon_count": 4,
           "evening_count": 2,
           "course_num_map": {
               "1": "08:00-08:45",
               "2": "09:00-09:45",
               "3": "10:00-10:45",
               "4": "11:00-11:45",
               "5": "14:00-14:45",
               "6": "15:00-15:45",
               "7": "16:00-16:45",
               "8": "17:00-17:45",
               "9": "19:00-19:45",
               "10": "20:00-20:45"
           }
        }
        :return:
        """
        try:
            logger.info("data: %s" % data)

            check_ret = self._check_course_num_map(data["course_num_map"])
            if check_ret:
                return check_ret

            check_ret = self._check_term_time_detail(data)
            if check_ret:
                return check_ret

            if db_api.get_term_with_first({"name": data["name"]}):
                return get_error_result("TermNameExist")

            # 校验新学期期间是否与已有学期重叠
            check_ret = self._check_term_duplicate(data["start"], data["end"])
            if check_ret:
                return check_ret

            data["uuid"] = create_uuid()
            data["weeks_num_map"] = self._generate_weeks_num_map(
                data["start"], data["end"])

            # 添加学期的定时任务
            task_uuid = self._add_crontab_task(
                term_uuid=data["uuid"],
                name=data["name"],
                start_date=data["start"],
                end_date=data["end"],
                course_num_map=data["course_num_map"],
                weeks_num_map=data["weeks_num_map"])

            if task_uuid:
                data["crontab_task_uuid"] = task_uuid
                data["course_num_map"] = json.dumps(data["course_num_map"])
                data["weeks_num_map"] = json.dumps(data["weeks_num_map"])

                # 新创建学期时,所有教学分组的状态默认为已启用
                group_status_map = dict()
                group_obj_list = db_api.get_group_with_all(
                    {"group_type": constants.EDUCATION_GROUP})
                for group_obj in group_obj_list:
                    group_status_map[
                        group_obj.uuid] = constants.COURSE_SCHEDULE_ENABLED
                data["group_status_map"] = json.dumps(group_status_map)

                db_api.create_term(data)
                logger.info("insert in yzy_term success: %s" % data)
                return get_error_result()
            else:
                logger.info("add course crontab task failed")
                return get_error_result("OtherError")

        except Exception as e:
            logger.exception("create term failed: %s" % str(e), exc_info=True)
            return get_error_result("OtherError")
    def apply(self, data):
        """
        将指定课表批量应用到多个周
        :param data:
        {
            "uuid": "886cc37d-121c-4f81-a933-f002b5d86094",
            "week_nums": [1, 3, 5, 7]
        }
        :return:
        """
        try:
            logger.info("data: %s" % data)
            uuid = data.get("uuid", None)
            week_nums = data.get("week_nums", None)
            if not uuid or not week_nums or not isinstance(
                    week_nums, list) or not all(
                        [isinstance(i, int) for i in week_nums]):
                return get_error_result("ParamError",
                                        data={"keys": ["uuid", "week_nums"]})

            target_cs_obj = db_api.get_course_schedule_with_first(
                {"uuid": uuid})
            if not target_cs_obj:
                return get_error_result("CourseScheduleNotExist")

            # 校验批量应用的周是否合法
            term_obj = db_api.get_term_with_first(
                {"uuid": target_cs_obj.term_uuid})
            weeks_num_map = json.loads(term_obj.weeks_num_map)
            week_nums = set(
                [num for num in week_nums if num != target_cs_obj.week_num])
            for week_num in week_nums:
                if str(week_num) not in weeks_num_map.keys():
                    # logger.error("ParamError: week_nums %d" % week_num)
                    return get_error_result("ParamError",
                                            data={"week_nums": week_num})

            # 找出该学期、该教学分组下所有已有课表的周
            cs_list = db_api.get_course_schedule_with_all({
                "term_uuid":
                target_cs_obj.term_uuid,
                "group_uuid":
                target_cs_obj.group_uuid
            })
            occupied_weeks = dict()
            for cs_obj in cs_list:
                occupied_weeks[cs_obj.week_num] = cs_obj

            cs_values_list = list()
            for week_num in week_nums:
                # 如果批量应用的周已有课表,则覆盖,将其引用模板更新为target模板
                if week_num in occupied_weeks.keys():
                    occupied_weeks[week_num].update({
                        "course_template_uuid":
                        target_cs_obj.course_template_uuid
                    })
                    logger.info(
                        "update uuid[%s] week_num[%s] in yzy_course_schedule success: {'course_template_uuid': %s}"
                        % (str(week_num), occupied_weeks[week_num].uuid,
                           occupied_weeks[week_num].course_template_uuid))
                # 如果批量应用的周没有课表,则创建
                else:
                    cs_values_list.append({
                        "uuid":
                        create_uuid(),
                        "term_uuid":
                        target_cs_obj.term_uuid,
                        "group_uuid":
                        target_cs_obj.group_uuid,
                        "course_template_uuid":
                        target_cs_obj.course_template_uuid,
                        "week_num":
                        week_num,
                        "course_md5":
                        target_cs_obj.course_md5,
                        "status":
                        1
                    })

            db_api.create_course_schedule_many(cs_values_list)
            logger.info(
                "insert many[%d] in yzy_course_schedule success: course_template_uuid[%s]"
                % (len(cs_values_list), target_cs_obj.course_template_uuid))

            return get_error_result()
        except Exception as e:
            logger.exception("apply course_schedule failed: %s" % str(e),
                             exc_info=True)
            return get_error_result("OtherError")