Exemple #1
0
 def post(self, request, *args, **kwargs):
     # 选项只能手动关联
     rst_code = self.ops_manual()
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     rst_code, rst_data = self.ops_view_all()
     return general_json_response(status.HTTP_200_OK, rst_code, rst_data)
Exemple #2
0
 def delete(self, request, *args, **kwargs):
     if "," in self.passage_ids:
         self.passage_ids = [
             int(obj_id) for obj_id in self.passage_ids.split(",")
         ]
     else:
         if self.passage_ids:
             self.passage_ids = [int(self.passage_ids)]
     for passage_id in self.passage_ids:
         if Question.objects.filter_active(question_passage_id=passage_id,
                                           use_count__gt=0).exists():
             return general_json_response(
                 status.HTTP_200_OK, ErrorCode.QUESTION_USED_DELETE_FORBID)
     passage_qs = QuestionPassage.objects.filter_active(
         id__in=self.passage_ids, question_facet_id=self.question_facet_id)
     # 批量删除材料禁止
     for passage_id in self.passage_ids:
         if Question.objects.filter_active(question_passage_id=passage_id,
                                           use_count__gt=0).exists():
             return general_json_response(
                 status.HTTP_200_OK, ErrorCode.QUESTION_USED_DELETE_FORBID)
     passage_qs.update(is_active=False)
     logger.info("user_id %s want delete passage_ids %s" %
                 (self.request.user.id, self.passage_ids))
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #3
0
 def post(self, request, *args, **kwargs):
     u"""模型维度指标与构面的关联,以及选择题目
     支持新增题目,或者全部替换
     """
     relation_option_qs = SurveyModelFacetRelation.objects.filter_active(
         survey_id=self.survey_id,
         model_id=self.model_id,
         related_facet_type=SurveyModelFacetRelation.RELATED_FACET_TYPE_OPTION
     )
     if relation_option_qs.exists():
         # 问卷关联了选项就不能关联题目
         return general_json_response(status.HTTP_200_OK, ErrorCode.SURVEY_QUESTION_OPTION_DOUBLE_ERROR, [])
     rst_code = ErrorCode.SUCCESS
     if self.related_type == SurveyModelFacetRelation.RELATED_TYPE_MANUAL:
         # 手动组卷
         rst_code = self.ops_manual()
     elif self.related_type == SurveyModelFacetRelation.RELATED_TYPE_AUTO:
         # 自动组卷
         rst_code = self.ops_auto()
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     question_category = self.request.data.get("question_category", None)
     if not question_category or int(question_category) == Question.CATEGORY_NORMAL:
         rst_code, rst_data = self.ops_view_all()
     elif int(question_category) == Question.CATEGORY_UNIFORMITY:
         rst_code, rst_data = self.ops_view_uniformity()
     elif int(question_category) == Question.CATEGORY_PRAISE:
         rst_code, rst_data = self.ops_view_praise()
     else:
         rst_code, rst_data = self.ops_view_all()
     return general_json_response(status.HTTP_200_OK, rst_code, rst_data)
Exemple #4
0
 def delete(self, request, *args, **kwargs):
     if "," in self.folder_ids:
         self.folder_ids = [
             int(obj_id) for obj_id in self.folder_ids.split(",")
         ]
     else:
         self.folder_ids = [int(self.folder_ids)]
     if Question.objects.filter_active(
             question_folder_id__in=self.folder_ids,
             use_count__gt=0).exists():
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.QUESTION_USED_DELETE_FORBID)
     # question_qs = Question.objects.filter_active(question_folder_id__in=self.folder_ids, use_count=0)
     # delete_ids = question_qs.values_list("question_folder_id", flat=True)
     delete_ids = self.folder_ids
     folder_ids = QuestionFolder.objects.filter_active(id__in=delete_ids)
     folder_id_list = list(folder_ids.values_list("id", flat=True))
     folder_ids.update(is_active=False)
     folder_ids = QuestionFolder.objects.filter_active(
         parent_id__in=delete_ids)
     folder_id_list += list(folder_ids.values_list("id", flat=True))
     folder_ids.update(is_active=False)
     QuestionFacet.objects.filter_active(
         question_folder_id__in=folder_id_list).update(is_active=False)
     Question.objects.filter_active(
         question_folder_id__in=folder_id_list).update(is_active=False)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #5
0
 def get(self, request, *args, **kwargs):
     account = request.GET.get('account', None)
     verify_code = request.GET.get("verify_code", None)
     rst = VerifyCodeExpireCache(account).check_verify_code(verify_code, False)
     if not rst:
         return general_json_response(status.HTTP_200_OK, ErrorCode.USER_VERIFY_CODE_INVALID)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #6
0
    def get(self, request, *args, **kwargs):
        u"""查看当前所有题目 测谎题 社会称许题
        迫选组卷,仅能查看指标/维度下选的全部题目,最终迫选结果在预览里面
        """
        # survey = Survey.objects.get(id=self.survey_id)
        # rst_code = ErrorCode.SUCCESS
        # if survey.form_type == Survey.FORM_TYPE_NORMAL:
        #     rst_code, data = self.ops_view_normal()
        # else:
        #     pass
        rst_code = ErrorCode.SUCCESS
        data = {}

        if self.ops_type == self.OPS_TYPE_VIEW_ALL:
            # 查看全部题目
            rst_code, data = self.ops_view_all()
        elif self.ops_type == self.OPS_TYPE_VIEW_PRAISE:
            # 查看社会称许题
            rst_code, data = self.ops_view_praise()
        elif self.ops_type == self.OPS_TYPE_VIEW_UNIFORMITY:
            # 查看测谎题(一致性题目)
            rst_code, data = self.ops_view_uniformity()
        if rst_code == ErrorCode.SURVEY_MODEL_SUBSTANDARD_NOT_RELATED_QUESTION:
            return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS, {})
        elif rst_code != ErrorCode.SUCCESS:
            return general_json_response(status.HTTP_200_OK, rst_code)
        else:
            return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS, data)
Exemple #7
0
    def delete(self, request, *args, **kwargs):
        if "," in self.bank_ids:
            self.bank_ids = [
                int(bank_id) for bank_id in self.bank_ids.split(",")
            ]
        else:
            self.bank_ids = [int(self.bank_ids)]

        if Question.objects.filter_active(question_bank_id__in=self.bank_ids,
                                          use_count__gt=0).exists():
            return general_json_response(status.HTTP_200_OK,
                                         ErrorCode.QUESTION_USED_DELETE_FORBID)
        # question_qs = Question.objects.filter_active(question_bank_id__in=self.bank_ids, use_count__gt=0)
        # reserved_ids = question_qs.values_list("question_bank_id", flat=True)
        # delete_ids = list(set(self.bank_ids) - set(reserved_ids))
        delete_ids = self.bank_ids
        QuestionBank.objects.filter_active(id__in=delete_ids).update(
            is_active=False)
        QuestionFolder.objects.filter_active(
            question_bank_id__in=delete_ids).update(is_active=False)
        QuestionFacet.objects.filter_active(
            question_bank_id__in=delete_ids).update(is_active=False)
        Question.objects.filter_active(question_bank_id__in=delete_ids).update(
            is_active=False)
        logger.info('user_id %s want delete question_bank %s' %
                    (self.request.user.id, delete_ids))
        return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #8
0
 def get(self, request, *args, **kwargs):
     u"""迫选排序题,仅支持普通题
     """
     rst_code, data = self.ops_view_all()
     if rst_code == ErrorCode.SURVEY_MODEL_SUBSTANDARD_NOT_RELATED_QUESTION:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS, {})
     elif rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     else:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS, data)
Exemple #9
0
 def post(self, request, *args, **kwargs):
     if self.dest_parent_id == self.src_substandard_id:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.RESEARCH_COPY_SELF_FORBID)
     substandard = ResearchSubstandardUtils.deep_copy(
         int(self.src_substandard_id), int(self.dest_model_id),
         int(self.dest_demension_id), int(self.dest_parent_id))
     return general_json_response(
         status.HTTP_200_OK, ErrorCode.SUCCESS,
         ResearchSubstandardDetailSerializer(instance=substandard).data)
Exemple #10
0
 def dispatch(self, request, *args, **kwargs):
     """
         `.dispatch()` is pretty much the same as Django's regular dispatch,
         but with extra hooks for startup, finalize, and exception handling.
     """
     # self.err_code = ErrorCode.SUCCESS
     self.args = args
     self.kwargs = kwargs
     request = self.initialize_request(request, *args, **kwargs)
     self.request = request
     self.headers = self.default_response_headers  # deprecate?
     self.custom_view_cache = {}
     try:
         if not request.has_permission:
             return general_json_response(status.HTTP_200_OK,
                                          ErrorCode.PERMISSION_FAIL)
         self.initial(request, *args, **kwargs)
         req_kwargs = self.request.data if self.request.method not in [
             "GET", "DELETE"
         ] else self.request.GET
         # if self.request.method != "DELETE":
         err_code = self.check_parameter(req_kwargs)
         if err_code != ErrorCode.SUCCESS:
             return general_json_response(status.HTTP_200_OK, err_code)
         # Get the appropriate handler method
         if request.method.lower() in self.http_method_names:
             handler = getattr(self, request.method.lower(),
                               self.http_method_not_allowed)
         else:
             handler = self.http_method_not_allowed
         response = handler(request, *args, **kwargs)
     except Exception as exc:
         traceback.print_exc()
         logger.warning("dispatch error, msg: %s" % exc.message)
         if self.CUSTOM_TEMPLATE_VIEW:
             self.template_name = self.default_template_name
             return HttpResponseRedirect(self.template_name)
         try:
             response = self.handle_exception(exc)
         except Exception as e:
             traceback.print_exc()
             logger.error("dispatch handler exception error, msg: %s" %
                          e.message)
             return general_json_response(status.HTTP_200_OK,
                                          ErrorCode.INTERNAL_ERROR)
     self.response = self.finalize_response(request, response, *args,
                                            **kwargs)
     if self.response.status_code in {
             status.HTTP_200_OK, status.HTTP_302_FOUND
     }:
         return self.response
     else:
         return general_json_response(
             status.HTTP_200_OK,
             ErrorCode.FAILURE + self.response.status_code)
Exemple #11
0
 def inherit_model(self):
     new_model_name = self.request.data.get('new_model_name', None)
     new_model_en_name = self.request.data.get('new_model_en_name', None)
     if new_model_name is None or new_model_en_name is None:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.INVALID_INPUT)
     inherit_obj = ResearchModelUtils.deep_copy(int(self.model_id),
                                                new_model_name,
                                                new_model_en_name)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                  {"id": inherit_obj.id})
Exemple #12
0
 def get(self, request, *args, **kwargs):
     rst_code, survey_data, question_data = SurveyUtils(
         self.survey_id, int(self.request.GET.get("force_option_num", 0)), int(self.request.GET.get("force_base_level", Survey.BASE_LEVEL_SUBSTANDARD)),
         is_test=False, context=self.get_serializer_context(),
     ).preview()
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS, {
         "survey_data": survey_data,
         "question_data": question_data
     })
Exemple #13
0
 def post(self, request, *args, **kwargs):
     account = request.data.get('account', None)
     verify_code = request.data.get("verify_code", None)
     user, rst_code = UserAccountUtils.account_check(account)
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     rst = VerifyCodeExpireCache(account).check_verify_code(
         verify_code, False)
     if not rst:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.USER_VERIFY_CODE_INVALID)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #14
0
 def post(self, request, *args, **kwargs):
     rsp = super(WdCreateAPIView, self).post(request, *args, **kwargs)
     if self.POST_DATA_RESPONSE:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                      rsp.data)
     elif self.POST_DATA_ID_RESPONSE:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                      {"id": rsp.data["id"]})
     elif self.POST_DATA_STRID_RESPONSE:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                      {"id": str(rsp.data["id"])})
     else:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #15
0
 def delete_ops(self):
     enterprise_ids = self.request.data.get("enterprise_ids", [])
     if enterprise_ids:
         if AssessProject.objects.filter(
                 enterprise_id__in=enterprise_ids).exists():
             return general_json_response(
                 status.HTTP_200_OK,
                 ErrorCode.ENTERPRISE_DELETE_FAILED_WITH_ASSESS_PROJECT)
     EnterpriseInfo.objects.filter(id__in=enterprise_ids).update(
         is_active=False)
     logger.info('user_id %s want delete enterprise_id %s' %
                 (self.request.user.id, enterprise_ids))
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #16
0
 def put(self, request, *args, **kwargs):
     if self.p_serializer_class is not None:
         self.serializer_class = self.p_serializer_class
     if self.partial:
         rsp = self.partial_update(request, *args, **kwargs)
     else:
         rsp = self.update(request, *args, **kwargs)
     if self.UPDATE_TAG:
         self.perform_tag()
     if self.POST_DATA_RESPONSE:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                      rsp.data)
     else:
         return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #17
0
 def post(self, request, *args, **kwargs):
     self.role_ids = [int(role_id) for role_id in self.role_ids]
     exist_role_ids = list(
         RoleUser.objects.filter_active(user_id=self.user_id).values_list(
             "role_id", flat=True))
     # 保留的角色
     reserve_role_ids = list(
         set(self.role_ids).intersection(set(exist_role_ids)))
     # 需要删除的角色
     remove_role_ids = list(
         set(exist_role_ids).difference(set(reserve_role_ids)))
     # 需要新建的角色
     new_role_ids = list(
         set(self.role_ids).difference(set(reserve_role_ids)))
     if remove_role_ids:
         RoleUser.objects.filter_active(
             role_id__in=remove_role_ids,
             user_id=self.user_id).update(is_active=False)
     if new_role_ids:
         bulk_list = []
         for new_role_id in new_role_ids:
             bulk_list.append(
                 RoleUser(role_id=new_role_id, user_id=self.user_id))
         RoleUser.objects.bulk_create(bulk_list)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #18
0
    def post(self, request, *args, **kwargs):
        report_assess_survey = []
        for survey_info in self.survey_infos:

            survey_name = Survey.objects.get(id=survey_info["survey_id"]).title
            try:
                asr_qs = AssessSurveyRelation.objects.filter_active(
                    assess_id=survey_info["project_id"],
                    survey_id=survey_info["survey_id"])
                if asr_qs.exists():
                    a = json.loads(asr_qs[0].custom_config)
                    survey_name_after = a.get("survey_name", None)
                    if survey_name_after:
                        survey_name = survey_name_after
            except:
                survey_name = Survey.objects.get(
                    id=survey_info["survey_id"]).title

            if not ReportSurveyAssessmentProjectRelation.objects.filter_active(
                    assessment_project_id=survey_info["project_id"],
                    survey_id=survey_info["survey_id"],
                    report_id=self.report_id).exists():
                report_assess_survey.append(
                    ReportSurveyAssessmentProjectRelation(
                        assessment_project_id=survey_info["project_id"],
                        survey_id=survey_info["survey_id"],
                        report_id=self.report_id,
                        survey_name=survey_name))
        ReportSurveyAssessmentProjectRelation.objects.bulk_create(
            report_assess_survey)
        return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #19
0
 def delete(self, request, *args, **kwargs):
     if "," in self.option_ids:
         self.option_ids = [int(obj_id) for obj_id in self.option_ids.split(",")]
     else:
         self.option_ids = [int(self.option_ids)]
     # 关联的选项
     SurveyQuestionRelation.objects.filter_active(
         survey_id=self.survey_id,
         question_option_id__in=self.option_ids
     ).update(is_active=False)
     # 选项的题目,这些选项对应的题目下的选项都没有关联时,这个题目删除
     question_ids = QuestionOption.objects.filter_active(id__in=self.option_ids).values_list("question_id", flat=True).distinct()
     # 找到这些选项的题目
     for q_id in question_ids:
         # 查看每个题目是不是有题目或选项级别的关联
         s_qs = SurveyQuestionRelation.objects.filter_active(
             survey_id=self.survey_id,
             question_id=q_id,
         )
         # 如果没有这个题目的题目和选项级别的关联,那么删除这个题目的结果关联
         if not s_qs.exists():
             SurveyQuestionResult.objects.filter_active(
                 survey_id=self.survey_id,
                 question_id=q_id
             ).update(is_active=False)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #20
0
 def post(self, request, *args, **kwargs):
     account = request.data.get('account', None)
     pwd = request.data.get("pwd", None)
     verify_code = request.data.get("verify_code", None)
     user, rst_code = UserAccountUtils.account_check(account)
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     rst = VerifyCodeExpireCache(account).check_verify_code(verify_code)
     if not rst:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.USER_VERIFY_CODE_INVALID)
     pwd = make_password(pwd)
     user.password = pwd
     user.active_code_valid = False
     user.save()
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #21
0
 def delete(self, request, *args, **kwargs):
     org = self.get_object()
     org_tree_ids = [org.id] + OrganizationUtils.get_child_orgs(
         org.assess_id, org.id)[1]
     codes = Organization.objects.filter_active(
         id__in=org_tree_ids).values_list("identification_code", flat=True)
     if PeopleOrganization.objects.filter_active(
             org_code__in=codes).exists():
         return general_json_response(
             status.HTTP_200_OK,
             ErrorCode.ORG_USED_IN_PROJECT_CAN_NOT_DELETE)
     Organization.objects.filter(id__in=org_tree_ids).update(
         is_active=False)
     logger.info('user_id %s want delete orgs %s' %
                 (self.request.user.id, org_tree_ids))
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #22
0
 def post(self, request, *args, **kwargs):
     logger.debug("状态:%s, 任务:%s, id:%s" % (self.status, self.task, self.task_id) )
     task = self.task
     if task in ['trail_task', 'clean']:
         ret_code = self.trial()
     elif task in ['official_clean']:
             ret_code = self.clean()
     return general_json_response(status.HTTP_200_OK, ret_code)
Exemple #23
0
class LogoutView(WdCreateAPIView):
    u"""Web登出"""
    def post(self, request, *args, **kwargs):
        try:
            logout(request)
        except Exception, e:
            logger.error("web logout error, msg(%s)" % e)
        return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #24
0
 def post(self, request, *args, **kwargs):
     if self.ops_type == self.OPS_TYPE_RELEASE:
         return self.release_model()
     elif self.ops_type == self.OPS_TYPE_INHERIT:
         return self.inherit_model()
     else:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.INVALID_INPUT)
Exemple #25
0
 def get(self, request, *args, **kwargs):
     if self.request.GET.get(self.SEARCH_KEY, None) is not None:
         return super(OrganizationListCreateView,
                      self).get(request, *args, **kwargs)
     # tree_orgs = OrganizationUtils.get_tree_organization(self.enterprise_id)
     tree_orgs = OrganizationUtils.get_tree_organization(self.assess_id)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                  {"organization": tree_orgs})
Exemple #26
0
 def delete(self, request, *args, **kwargs):
     RoleUserBusiness.objects.filter_active(
         user_id=self.user_id,
         model_type=self.model_type,
         model_id=self.model_id).update(is_active=False)
     logger.info('user_id %s want delete role_user_business %s' %
                 (self.request.user.id, self.user_id))
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #27
0
 def get(self, request, *args, **kwargs):
     file_full_path = self.get_file()
     if file_full_path is None:
         return general_json_response(status.HTTP_200_OK,
                                      ErrorCode.INVALID_INPUT)
     r = FileResponse(open(file_full_path, "rb"))
     r['Content-Disposition'] = 'attachment; filename=%s' % self.default_export_file_name
     return r
Exemple #28
0
 def delete(self, request, *args, **kwargs):
     # 帐号删除
     AuthUser.objects.filter(id=self.user_id).update(is_active=False)
     # 角色成员删除
     RoleUser.objects.filter_active(user_id=self.user_id).update(
         is_active=False)
     RoleUserBusiness.objects.filter_active(user_id=self.user_id).update(
         is_active=False)
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
Exemple #29
0
 def post(self, request, *args, **kwargs):
     u"""模型维度指标与构面的关联,以及选择题目
     支持新增题目,或者全部替换
     """
     rst_code = self.ops_manual()
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     question_category = self.request.data.get("question_category", None)
     if not question_category or int(question_category) == Question.CATEGORY_NORMAL:
         rst_code, rst_data = self.ops_view_all()
     elif int(question_category) == Question.CATEGORY_UNIFORMITY:
         rst_code, rst_data = self.ops_view_uniformity()
     elif int(question_category) == Question.CATEGORY_PRAISE:
         rst_code, rst_data = self.ops_view_praise()
     else:
         rst_code, rst_data = self.ops_view_all()
     # print(rst_code, rst_data)
     return general_json_response(status.HTTP_200_OK, rst_code, rst_data)
Exemple #30
0
 def post(self, request, *args, **kwargs):
     account = request.data.get('account', None)
     active_code = request.data.get("active_code", None)
     pwd = request.data.get("pwd", None)
     user, rst_code = UserAccountUtils.account_check(
         account, True, active_code)
     if rst_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, rst_code)
     password = make_password(pwd)
     user.password = password
     user.active_code_valid = False
     user.save()
     user, err_code = UserAccountUtils.user_login_web(request, user, pwd)
     if err_code != ErrorCode.SUCCESS:
         return general_json_response(status.HTTP_200_OK, err_code)
     user_info = login_info(request, user, self.get_serializer_context())
     return general_json_response(status.HTTP_200_OK, ErrorCode.SUCCESS,
                                  user_info)