From cc98f903ea63048222444d9eda80f0330b976e2f Mon Sep 17 00:00:00 2001
From: pycook <pycook@126.com>
Date: Tue, 20 Aug 2024 13:49:51 +0800
Subject: [PATCH] feat(api): supports bool and reference

---
 cmdb-api/api/lib/cmdb/attribute.py            |  18 +-
 cmdb-api/api/lib/cmdb/ci.py                   |  68 +++++++-
 cmdb-api/api/lib/cmdb/ci_type.py              |  35 +++-
 cmdb-api/api/lib/cmdb/const.py                |   2 +
 cmdb-api/api/lib/cmdb/resp_format.py          |   4 +
 cmdb-api/api/lib/cmdb/search/ci/db/search.py  |   3 +
 cmdb-api/api/lib/cmdb/utils.py                |   6 +
 cmdb-api/api/lib/cmdb/value.py                |  14 +-
 cmdb-api/api/lib/notify.py                    |   2 +-
 cmdb-api/api/models/cmdb.py                   |   4 +
 cmdb-api/api/tasks/cmdb.py                    |  10 +-
 .../translations/zh/LC_MESSAGES/messages.mo   | Bin 17334 -> 17640 bytes
 .../translations/zh/LC_MESSAGES/messages.po   | 156 ++++++++++--------
 cmdb-api/api/views/cmdb/ci.py                 |   4 +-
 cmdb-api/api/views/cmdb/ci_type.py            |  23 ++-
 15 files changed, 242 insertions(+), 107 deletions(-)

diff --git a/cmdb-api/api/lib/cmdb/attribute.py b/cmdb-api/api/lib/cmdb/attribute.py
index f2e3d69..a7579dc 100644
--- a/cmdb-api/api/lib/cmdb/attribute.py
+++ b/cmdb-api/api/lib/cmdb/attribute.py
@@ -167,24 +167,30 @@ class AttributeManager(object):
     def get_attribute_by_name(self, name):
         attr = Attribute.get_by(name=name, first=True)
         if attr.get("is_choice"):
-            attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
-                                                          attr["choice_web_hook"], attr.get("choice_other"))
+            attr["choice_value"] = self.get_choice_values(attr["id"],
+                                                          attr["value_type"],
+                                                          attr["choice_web_hook"],
+                                                          attr.get("choice_other"))
 
         return attr
 
     def get_attribute_by_alias(self, alias):
         attr = Attribute.get_by(alias=alias, first=True)
         if attr.get("is_choice"):
-            attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
-                                                          attr["choice_web_hook"], attr.get("choice_other"))
+            attr["choice_value"] = self.get_choice_values(attr["id"],
+                                                          attr["value_type"],
+                                                          attr["choice_web_hook"],
+                                                          attr.get("choice_other"))
 
         return attr
 
     def get_attribute_by_id(self, _id):
         attr = Attribute.get_by_id(_id).to_dict()
         if attr.get("is_choice"):
-            attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
-                                                          attr["choice_web_hook"], attr.get("choice_other"))
+            attr["choice_value"] = self.get_choice_values(attr["id"],
+                                                          attr["value_type"],
+                                                          attr["choice_web_hook"],
+                                                          attr.get("choice_other"))
 
         return attr
 
diff --git a/cmdb-api/api/lib/cmdb/ci.py b/cmdb-api/api/lib/cmdb/ci.py
index e2cc6c5..a6d5c76 100644
--- a/cmdb-api/api/lib/cmdb/ci.py
+++ b/cmdb-api/api/lib/cmdb/ci.py
@@ -266,7 +266,7 @@ class CIManager(object):
                 value_table = TableMap(attr_name=id2name[attr_id]).table
 
                 values = value_table.get_by(attr_id=attr_id,
-                                            value=ci_dict.get(id2name[attr_id]) or None,
+                                            value=ci_dict.get(id2name[attr_id]),
                                             only_query=True).join(
                     CI, CI.id == value_table.ci_id).filter(CI.type_id == type_id)
                 _ci_ids = set([i.ci_id for i in values])
@@ -292,6 +292,53 @@ class CIManager(object):
 
         return 1
 
+    @staticmethod
+    def _reference_to_ci_id(attr, payload):
+        def __unique_value2id(_type, _v):
+            value_table = TableMap(attr_name=_type.unique_id).table
+            ci = value_table.get_by(attr_id=attr.id, value=_v)
+            if ci is not None:
+                return ci.ci_id
+
+            return abort(400, ErrFormat.ci_reference_invalid.format(attr.alias, _v))
+
+        def __valid_reference_id_existed(_id, _type_id):
+            ci = CI.get_by_id(_id) or abort(404, ErrFormat.ci_reference_not_found.format(attr.alias, _id))
+
+            if ci.type_id != _type_id:
+                return abort(400, ErrFormat.ci_reference_invalid.format(attr.alias, _id))
+
+        if attr.name in payload:
+            k, reference_value = attr.name, payload[attr.name]
+        elif attr.alias in payload:
+            k, reference_value = attr.alias, payload[attr.alias]
+        else:
+            return
+        if not reference_value:
+            return
+
+        reference_type = None
+        if isinstance(reference_value, list):
+            for idx, v in enumerate(reference_value):
+                if isinstance(v, dict) and v.get('unique'):
+                    if reference_type is None:
+                        reference_type = CITypeCache.get(attr.reference_type_id)
+                    if reference_type is not None:
+                        reference_value[idx] = __unique_value2id(reference_type, v)
+                else:
+                    __valid_reference_id_existed(v, attr.reference_type_id)
+
+        elif isinstance(reference_value, dict) and reference_value.get('unique'):
+            if reference_type is None:
+                reference_type = CITypeCache.get(attr.reference_type_id)
+            if reference_type is not None:
+                reference_value = __unique_value2id(reference_type, reference_value)
+        elif str(reference_value).isdigit():
+            reference_value = int(reference_value)
+            __valid_reference_id_existed(reference_value, attr.reference_type_id)
+
+        payload[k] = reference_value
+
     @classmethod
     def add(cls, ci_type_name,
             exist_policy=ExistPolicy.REPLACE,
@@ -392,6 +439,8 @@ class CIManager(object):
 
                     if attr.re_check and password_dict.get(attr.id):
                         value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
+                elif attr.is_reference:
+                    cls._reference_to_ci_id(attr, ci_dict)
 
             cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
 
@@ -443,9 +492,10 @@ class CIManager(object):
 
         return ci.id
 
-    def update(self, ci_id, _is_admin=False, ticket_id=None, __sync=False, **ci_dict):
+    def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict):
         now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
         ci = self.confirm_ci_existed(ci_id)
+        ci_type = ci.ci_type
 
         attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
         ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
@@ -475,11 +525,13 @@ class CIManager(object):
 
                 if attr.re_check and password_dict.get(attr.id):
                     value_manager.check_re(attr.re_check, attr.alias, password_dict[attr.id][0])
+            elif attr.is_reference:
+                self._reference_to_ci_id(attr, ci_dict)
 
         limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
 
         record_id = None
-        with redis_lock.Lock(rd.r, ci.ci_type.name):
+        with redis_lock.Lock(rd.r, ci_type.name):
             db.session.commit()
 
             self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
@@ -510,14 +562,14 @@ class CIManager(object):
                 record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id)
 
         if record_id or has_dynamic:  # has changed
-            if not __sync:
+            if not _sync:
                 ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
             else:
                 ci_cache(ci_id, OperateType.UPDATE, record_id)
 
         ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
         if ref_ci_dict:
-            if not __sync:
+            if not _sync:
                 ci_relation_add.apply_async(args=(ref_ci_dict, ci.id), queue=CMDB_QUEUE)
             else:
                 ci_relation_add(ref_ci_dict, ci.id)
@@ -578,7 +630,7 @@ class CIManager(object):
         if ci_dict:
             AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
 
-        ci_delete.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
+        ci_delete.apply_async(args=(ci_id, ci.type_id), queue=CMDB_QUEUE)
         delete_id_filter.apply_async(args=(ci_id,), queue=CMDB_QUEUE)
 
         return ci_id
@@ -773,7 +825,7 @@ class CIManager(object):
         value_table = ValueTypeMap.table[ValueTypeEnum.PASSWORD]
         if current_app.config.get('SECRETS_ENGINE') == 'inner':
             if value:
-                encrypt_value, status = InnerCrypt().encrypt(value)
+                encrypt_value, status = InnerCrypt().encrypt(str(value))
                 if not status:
                     current_app.logger.error('save password failed: {}'.format(encrypt_value))
                     return abort(400, ErrFormat.password_save_failed.format(encrypt_value))
@@ -801,7 +853,7 @@ class CIManager(object):
             vault = VaultClient(current_app.config.get('VAULT_URL'), current_app.config.get('VAULT_TOKEN'))
             if value:
                 try:
-                    vault.update("/{}/{}".format(ci_id, attr_id), dict(v=value))
+                    vault.update("/{}/{}".format(ci_id, attr_id), dict(v=str(value)))
                 except Exception as e:
                     current_app.logger.error('save password to vault failed: {}'.format(e))
                     return abort(400, ErrFormat.password_save_failed.format('write vault failed'))
diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py
index 8bffd58..8b98316 100644
--- a/cmdb-api/api/lib/cmdb/ci_type.py
+++ b/cmdb-api/api/lib/cmdb/ci_type.py
@@ -145,7 +145,7 @@ class CITypeManager(object):
         kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
 
         cls._validate_unique(name=kwargs['name'])
-        cls._validate_unique(alias=kwargs['alias'])
+        # cls._validate_unique(alias=kwargs['alias'])
 
         kwargs["unique_id"] = unique_key.id
         kwargs['uid'] = current_user.uid
@@ -184,7 +184,7 @@ class CITypeManager(object):
         ci_type = cls.check_is_existed(type_id)
 
         cls._validate_unique(type_id=type_id, name=kwargs.get('name'))
-        cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
+        # cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name'))
 
         unique_key = kwargs.pop("unique_key", None)
         unique_key = AttributeCache.get(unique_key)
@@ -234,6 +234,10 @@ class CITypeManager(object):
         if CITypeInheritance.get_by(parent_id=type_id, first=True):
             return abort(400, ErrFormat.ci_type_inheritance_cannot_delete)
 
+        reference = Attribute.get_by(reference_type_id=type_id, first=True, to_dict=False)
+        if reference is not None:
+            return abort(400, ErrFormat.ci_type_referenced_cannot_delete.format(reference.alias))
+
         relation_views = PreferenceRelationView.get_by(to_dict=False)
         for rv in relation_views:
             for item in (rv.cr_ids or []):
@@ -1323,6 +1327,7 @@ class CITypeTemplateManager(object):
     def _import_attributes(self, type2attributes):
         attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
         attrs = []
+        references = []
         for i in copy.deepcopy(attributes):
             if i.pop('inherited', None):
                 continue
@@ -1337,6 +1342,10 @@ class CITypeTemplateManager(object):
             if not choice_value:
                 i['is_choice'] = False
 
+            if i.get('reference_type_id'):
+                references.append(copy.deepcopy(i))
+                i.pop('reference_type_id')
+
             attrs.append((i, choice_value))
 
         attr_id_map = self.__import(Attribute, [i[0] for i in copy.deepcopy(attrs)])
@@ -1345,7 +1354,7 @@ class CITypeTemplateManager(object):
             if choice_value and not i.get('choice_web_hook') and not i.get('choice_other'):
                 AttributeManager.add_choice_values(attr_id_map.get(i['id'], i['id']), i['value_type'], choice_value)
 
-        return attr_id_map
+        return attr_id_map, references
 
     def _import_ci_types(self, ci_types, attr_id_map):
         for i in ci_types:
@@ -1359,6 +1368,11 @@ class CITypeTemplateManager(object):
 
         return self.__import(CIType, ci_types)
 
+    def _import_reference_attributes(self, attrs, type_id_map):
+        for attr in attrs:
+            attr['reference_type_id'] = type_id_map.get(attr['reference_type_id'], attr['reference_type_id'])
+        self.__import(Attribute, attrs)
+
     def _import_ci_type_groups(self, ci_type_groups, type_id_map):
         _ci_type_groups = copy.deepcopy(ci_type_groups)
         for i in _ci_type_groups:
@@ -1584,13 +1598,15 @@ class CITypeTemplateManager(object):
 
         import time
         s = time.time()
-        attr_id_map = self._import_attributes(tpt.get('type2attributes') or {})
+        attr_id_map, references = self._import_attributes(tpt.get('type2attributes') or {})
         current_app.logger.info('import attributes cost: {}'.format(time.time() - s))
 
         s = time.time()
         ci_type_id_map = self._import_ci_types(tpt.get('ci_types') or [], attr_id_map)
         current_app.logger.info('import ci_types cost: {}'.format(time.time() - s))
 
+        self._import_reference_attributes(references, ci_type_id_map)
+
         s = time.time()
         self._import_ci_type_groups(tpt.get('ci_type_groups') or [], ci_type_id_map)
         current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s))
@@ -1675,6 +1691,16 @@ class CITypeTemplateManager(object):
             type_ids.extend(extend_type_ids)
             ci_types.extend(CITypeManager.get_ci_types(type_ids=extend_type_ids))
 
+        # handle reference type
+        references = Attribute.get_by(only_query=True).join(
+            CITypeAttribute, CITypeAttribute.attr_id == Attribute.id).filter(
+            CITypeAttribute.type_id.in_(type_ids)).filter(CITypeAttribute.deleted.is_(False)).filter(
+            Attribute.reference_type_id.isnot(None))
+        reference_type_ids = list(set([i.reference_type_id for i in references if i.reference_type_id]))
+        if reference_type_ids:
+            type_ids.extend(reference_type_ids)
+            ci_types.extend(CITypeManager.get_ci_types(type_ids=reference_type_ids))
+
         tpt = dict(
             ci_types=ci_types,
             relation_types=[i.to_dict() for i in RelationTypeManager.get_all()],
@@ -1687,6 +1713,7 @@ class CITypeTemplateManager(object):
             icons=dict()
         )
         tpt['ci_type_groups'] = CITypeGroupManager.get(ci_types=tpt['ci_types'], type_ids=type_ids)
+        tpt['ci_type_groups'] = [i for i in tpt['ci_type_groups'] if i.get('ci_types')]
 
         def get_icon_value(icon):
             try:
diff --git a/cmdb-api/api/lib/cmdb/const.py b/cmdb-api/api/lib/cmdb/const.py
index 972fa69..1cfd43f 100644
--- a/cmdb-api/api/lib/cmdb/const.py
+++ b/cmdb-api/api/lib/cmdb/const.py
@@ -14,6 +14,8 @@ class ValueTypeEnum(BaseEnum):
     JSON = "6"
     PASSWORD = TEXT
     LINK = TEXT
+    BOOL = "7"
+    REFERENCE = INT
 
 
 class ConstraintEnum(BaseEnum):
diff --git a/cmdb-api/api/lib/cmdb/resp_format.py b/cmdb-api/api/lib/cmdb/resp_format.py
index 57a5b65..0a39341 100644
--- a/cmdb-api/api/lib/cmdb/resp_format.py
+++ b/cmdb-api/api/lib/cmdb/resp_format.py
@@ -44,6 +44,8 @@ class ErrFormat(CommonErrFormat):
     unique_value_not_found = _l("The model's primary key {} does not exist!")  # 模型的主键 {} 不存在!
     unique_key_required = _l("Primary key {} is missing")  # 主键字段 {} 缺失
     ci_is_already_existed = _l("CI already exists!")  # CI 已经存在!
+    ci_reference_not_found = _l("{}: CI reference {} does not exist!")  # {}: CI引用 {} 不存在!
+    ci_reference_invalid = _l("{}: CI reference {} is illegal!")  # {}, CI引用 {} 不合法!
     relation_constraint = _l("Relationship constraint: {}, verification failed")  # 关系约束: {}, 校验失败
     # 多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!
     m2m_relation_constraint = _l(
@@ -63,6 +65,8 @@ class ErrFormat(CommonErrFormat):
     ci_exists_and_cannot_delete_inheritance = _l(
         "The inheritance cannot be deleted because the CI already exists")  # 因为CI已经存在,不能删除继承关系
     ci_type_inheritance_cannot_delete = _l("The model is inherited and cannot be deleted")  # 该模型被继承, 不能删除
+    ci_type_referenced_cannot_delete = _l(
+        "The model is referenced by attribute {} and cannot be deleted")  # 该模型被属性 {} 引用, 不能删除
 
     # 因为关系视图 {} 引用了该模型,不能删除模型
     ci_relation_view_exists_and_cannot_delete_type = _l(
diff --git a/cmdb-api/api/lib/cmdb/search/ci/db/search.py b/cmdb-api/api/lib/cmdb/search/ci/db/search.py
index 766413c..2aaa690 100644
--- a/cmdb-api/api/lib/cmdb/search/ci/db/search.py
+++ b/cmdb-api/api/lib/cmdb/search/ci/db/search.py
@@ -451,6 +451,9 @@ class Search(object):
             if field_type == ValueTypeEnum.DATE and len(v) == 10:
                 v = "{} 00:00:00".format(v)
 
+            if field_type == ValueTypeEnum.BOOL and "*" not in str(v):
+                v = str(int(v in current_app.config.get('BOOL_TRUE')))
+
             # in query
             if v.startswith("(") and v.endswith(")"):
                 _query_sql = self._in_query_handler(attr, v, is_not)
diff --git a/cmdb-api/api/lib/cmdb/utils.py b/cmdb-api/api/lib/cmdb/utils.py
index ab354ea..7ea7e2a 100644
--- a/cmdb-api/api/lib/cmdb/utils.py
+++ b/cmdb-api/api/lib/cmdb/utils.py
@@ -7,6 +7,7 @@ import json
 import re
 
 import six
+from flask import current_app
 
 import api.models.cmdb as model
 from api.lib.cmdb.cache import AttributeCache
@@ -64,6 +65,7 @@ class ValueTypeMap(object):
         ValueTypeEnum.DATETIME: str2datetime,
         ValueTypeEnum.DATE: str2date,
         ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
+        ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
     }
 
     serialize = {
@@ -74,6 +76,7 @@ class ValueTypeMap(object):
         ValueTypeEnum.DATE: lambda x: x.strftime("%Y-%m-%d") if not isinstance(x, six.string_types) else x,
         ValueTypeEnum.DATETIME: lambda x: x.strftime("%Y-%m-%d %H:%M:%S") if not isinstance(x, six.string_types) else x,
         ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
+        ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
     }
 
     serialize2 = {
@@ -84,6 +87,7 @@ class ValueTypeMap(object):
         ValueTypeEnum.DATE: lambda x: (x.decode() if not isinstance(x, six.string_types) else x).split()[0],
         ValueTypeEnum.DATETIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
         ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
+        ValueTypeEnum.BOOL: lambda x: x in current_app.config.get('BOOL_TRUE'),
     }
 
     choice = {
@@ -105,6 +109,7 @@ class ValueTypeMap(object):
         'index_{0}'.format(ValueTypeEnum.TIME): model.CIIndexValueText,
         'index_{0}'.format(ValueTypeEnum.FLOAT): model.CIIndexValueFloat,
         'index_{0}'.format(ValueTypeEnum.JSON): model.CIValueJson,
+        'index_{0}'.format(ValueTypeEnum.BOOL): model.CIIndexValueInteger,
     }
 
     table_name = {
@@ -117,6 +122,7 @@ class ValueTypeMap(object):
         'index_{0}'.format(ValueTypeEnum.TIME): 'c_value_index_texts',
         'index_{0}'.format(ValueTypeEnum.FLOAT): 'c_value_index_floats',
         'index_{0}'.format(ValueTypeEnum.JSON): 'c_value_json',
+        'index_{0}'.format(ValueTypeEnum.BOOL): 'c_value_index_integers',
     }
 
     es_type = {
diff --git a/cmdb-api/api/lib/cmdb/value.py b/cmdb-api/api/lib/cmdb/value.py
index 8fe4fcf..0edc2b3 100644
--- a/cmdb-api/api/lib/cmdb/value.py
+++ b/cmdb-api/api/lib/cmdb/value.py
@@ -128,14 +128,20 @@ class AttributeValueManager(object):
             return abort(400, ErrFormat.attribute_value_invalid2.format(alias, value))
 
     def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
-        ci = ci or {}
-        v = self._deserialize_value(attr.alias, attr.value_type, value)
+        if not attr.is_reference:
+            ci = ci or {}
+            v = self._deserialize_value(attr.alias, attr.value_type, value)
+
+            attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
+
+        else:
+            v = value or None
 
-        attr.is_choice and value and self._check_is_choice(attr, attr.value_type, v)
         attr.is_unique and self._check_is_unique(
             value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
-
         self._check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
+        if attr.is_reference:
+            return v
 
         if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
             v = None
diff --git a/cmdb-api/api/lib/notify.py b/cmdb-api/api/lib/notify.py
index 8d6e070..75921a0 100644
--- a/cmdb-api/api/lib/notify.py
+++ b/cmdb-api/api/lib/notify.py
@@ -58,7 +58,7 @@ def _request_messenger(subject, body, tos, sender, payload):
 
 def notify_send(subject, body, methods, tos, payload=None):
     payload = payload or {}
-    payload = {k: v or '' for k, v in payload.items()}
+    payload = {k: '' if v is None else v for k, v in payload.items()}
     subject = Template(subject).render(payload)
     body = Template(body).render(payload)
 
diff --git a/cmdb-api/api/models/cmdb.py b/cmdb-api/api/models/cmdb.py
index 5e4f73c..d310afa 100644
--- a/cmdb-api/api/models/cmdb.py
+++ b/cmdb-api/api/models/cmdb.py
@@ -105,6 +105,10 @@ class Attribute(Model):
     is_password = db.Column(db.Boolean, default=False)
     is_sortable = db.Column(db.Boolean, default=False)
     is_dynamic = db.Column(db.Boolean, default=False)
+    is_bool = db.Column(db.Boolean, default=False)
+
+    is_reference = db.Column(db.Boolean, default=False)
+    reference_type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'))
 
     default = db.Column(db.JSON)  # {"default": None}
 
diff --git a/cmdb-api/api/tasks/cmdb.py b/cmdb-api/api/tasks/cmdb.py
index c0bde06..fd00c87 100644
--- a/cmdb-api/api/tasks/cmdb.py
+++ b/cmdb-api/api/tasks/cmdb.py
@@ -20,10 +20,12 @@ from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
 from api.lib.cmdb.const import RelationSourceEnum
 from api.lib.cmdb.perms import CIFilterPermsCRUD
+from api.lib.cmdb.utils import TableMap
 from api.lib.decorator import flush_db
 from api.lib.decorator import reconnect_db
 from api.lib.perm.acl.cache import UserCache
 from api.lib.utils import handle_arg_list
+from api.models.cmdb import Attribute
 from api.models.cmdb import AutoDiscoveryCI
 from api.models.cmdb import AutoDiscoveryCIType
 from api.models.cmdb import AutoDiscoveryCITypeRelation
@@ -84,7 +86,7 @@ def batch_ci_cache(ci_ids, ):  # only for attribute change index
 
 @celery.task(name="cmdb.ci_delete", queue=CMDB_QUEUE)
 @reconnect_db
-def ci_delete(ci_id):
+def ci_delete(ci_id, type_id):
     current_app.logger.info(ci_id)
 
     if current_app.config.get("USE_ES"):
@@ -99,6 +101,12 @@ def ci_delete(ci_id):
             adt.update(updated_at=datetime.datetime.now())
         instance.delete()
 
+    for attr in Attribute.get_by(reference_type_id=type_id, to_dict=False):
+        table = TableMap(attr=attr).table
+        for i in getattr(table, 'get_by')(attr_id=attr.id, value=ci_id, to_dict=False):
+            i.delete()
+            ci_cache(i.ci_id, None, None)
+
     current_app.logger.info("{0} delete..........".format(ci_id))
 
 
diff --git a/cmdb-api/api/translations/zh/LC_MESSAGES/messages.mo b/cmdb-api/api/translations/zh/LC_MESSAGES/messages.mo
index a62ae1d41b231bc0574a78022436be480b17289c..8b6e43b969f07368c1fbeee39ebb836f0350e2b4 100644
GIT binary patch
delta 3640
zcmZA3eN5F=9LMo5Rx0KLfgp&=MKKA%hvY#qL=jeMW;vgzd?36kVrqn&%JACE#7eC$
zYL?QIO$^QSZt5Qdv$B~ZOIBnxGc%M*S5xz;O?!X1XX}B<eVude`JMAU-*e6n_pb?Q
zSsUW}A^i4@#?Ntnd+@uxt!n@LBzH3FMm5EmhjG-WV>Fhdp4)`oF@Onp3?uQ9YY**g
z)|Yw$_QbK+%gkr<C=8-u4bH-Yn2s@B%;NE3X9;SeO4I;*u^)cnyo$Nh<HF3^<7^Da
zCviBg#JjN;d*d1G%KX;$F0%+4VsQ`-#5A0RnqU(u19cdKO{kUpfvmuym_>o5VMm;Z
z%FIku|D~vmy@Kku&$WM{cILNVD8ynYQS`ySs1=Suwa-PRauw3mwqRG><?4r!SnMlT
zKZ^?BI%-S8=^TYA*dKFInO%%Nt>6_3>bMs*VFQvKYe9CyuA>5I&xbP93$?NdsOO8E
z7052w4jhRGP?@`iN_iMjX#63lfG0$ee+^VXg9a)?MfRHOSnE9Dyo@vWK8#V7p+(Mh
zs6$tS{If5eEvN~Vj!fu*88{UO;)ZDQ&zM$E1B<jK)E@qZ%D^>D!BC=9pc#D7WRE!K
zyY^M6z_(%Rcu4lG36<GjQ32jTMfD^;NQ&k8C@3|LVK%P79IQhfnh?U*A&ketI0n_f
z42NJ9CSoHhBY&Y562nQ;VH?0l7LG$=u(ha6zk@o&zK<wqPg_ujFNRTM7V3clY@HCb
zXH}?`e&B3G5B1B=NJb|HOLtC0t$ZPBYy7BjYl3y39j3tCb`q768<>Y-EMF^|f;zQ6
z9ELTh3^k($PW14O!10)iRhW%ua3uC2T8%RshvPcbIG^EU-T&KIUNQ~&n1;_`7S>`C
z-au_ZU(%xbcvQxgq5{~7I!wn<DZPmrCxI_D;iIVMHsJ_7gv8$3Ih*;-!&BpM7LLVT
zn1@%9xh;#=mR2web8szsumLsD1?0F`C!$np6H)aXWLIoD5|fo-Hok$GcnW<wEZrGh
zdpi)dN25@uIN#M5qxw~#`fbBv+=HnYdr$B%K8ypXKa0d<0o3jJ4)q>sPa1Tqx}k1a
zViNi1?}Me$z;@VZ)I?s?%9o=e--cT0A*={7JB?c5eD+PQM)iBs)jvTG^;52{C=^gE
z>a69Uo|~CM{&k<1)1VdmQ31S%EY9k22DYFExS!=I#S5JksQz!e`T=Bn?Ko;dS5Vh9
zngh}iA4Fv!A4!VM_fb$pm8byrVi!Du>UaXx@fXygis0mF;4!Gb1;waavjUZYO4RcQ
zUHu4#Qa_IjVONlt>~3BrTr~4drl379MXlsT)ct)6m6;D+y~%kIHDTv_gA*m70!hU%
zyx-NQIg6buP~*Og8h3ZF-DjUt;Go;Ls0`df1r$ZzR3G5X!*J@4qb6G9+E=27`esyM
z2T`Z~7;2Btqn>M*YR1sk9V2i&#_IkTP|!r>sMJ-W_ISH%uS0Fo*RK5{YT%CShx+wH
zUDr{lersI&c4r-G{1d2&e@89wwsf|E`7M@$4qFatz+zOytDUbpYn*kcGjJR=;U&~_
zVHv@Rl2GlVP<uQBm9cWvAzY7I=<DdygEj7p!x%}u5!K#;_hFl%!B=KB>KZOXUC%A3
zS92}4_D3#?wHX%N>p{*3Q45;qEJMzrtsh4IqbStVz{|**Fa~d;B9G1tt|SB1KFPTd
z6=(%&WmVW6_h2_XiaG;7xpuvW)gFsoaR{pa1H;L`z9@7Z%3KFO>Y7xeCi=$JFQDE7
zJxGf(F&cRnSs@a$t;c9Qg4uW)*&U1L4~nkeG*rC;^?bdLf(AH+y5}861poM?qKA43
zD%E~ezx~(~Poo06g}pG6*>omSQSDPu8C`(NTqW`{wD*yu*!QS$d{HBV6QrU}=Ty|5
z79$7UHX}F48c-{=QNaN-u?N*jn1)ZG0^Q--kD^k14z<N?a)OzTK}|du$&}9~P*AGp
zp(fspIxHWf_O=PNMdwff-a_qR#OUBk6H%vm1dhjtFb=n3AFM@X=too_w=f=~a`k59
zV<^A67CEgA{IAs2^HC4ZK~20GwYOVPE8FM%JU4Ko<C%~<lRc$Hg~i^|nMJb$Cp+(s
z@c$Vf9$HXZT2l1Hl2Wg~AtA;;ws(iX^Sxhd*QKmH(=&REr^GwQTjE_<=r8CJ8TY?7
zucxfsQ}nc_Xu$&S+=2!1{ys@hbQqd)?;y|MA(^QefelG9AtC93o&6g^x+Z%Hi)NNC
zTkQRJ3`<fLhlRK7dG~DfTg_E3w7gx@ytnG?${qe^vZDh(XEolD9-OebcH_CqYEAa@
d!FA2MUv1u69dBjj$;9{HuU@J+8`v0ce*@xqy}AGZ

delta 3407
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
GNBjqq0%ha?

diff --git a/cmdb-api/api/translations/zh/LC_MESSAGES/messages.po b/cmdb-api/api/translations/zh/LC_MESSAGES/messages.po
index 38c8949..dd3f1bc 100644
--- a/cmdb-api/api/translations/zh/LC_MESSAGES/messages.po
+++ b/cmdb-api/api/translations/zh/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2024-06-20 19:12+0800\n"
+"POT-Creation-Date: 2024-08-20 13:47+0800\n"
 "PO-Revision-Date: 2023-12-25 20:21+0800\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: zh\n"
@@ -16,7 +16,7 @@ msgstr ""
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.14.0\n"
+"Generated-By: Babel 2.16.0\n"
 
 #: api/lib/resp_format.py:7
 msgid "unauthorized"
@@ -169,8 +169,8 @@ msgstr "目前只允许 属性创建人、管理员 删除属性!"
 #: api/lib/cmdb/resp_format.py:37
 msgid ""
 "Attribute field names cannot be built-in fields: id, _id, ci_id, type, "
-"_type, ci_type"
-msgstr "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
+"_type, ci_type, ticket_id"
+msgstr "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type, ci_type, ticket_id"
 
 #: api/lib/cmdb/resp_format.py:39
 msgid "Predefined value: Other model request parameters are illegal!"
@@ -197,286 +197,298 @@ msgid "CI already exists!"
 msgstr "CI 已经存在!"
 
 #: api/lib/cmdb/resp_format.py:47
+msgid "{}: CI reference {} does not exist!"
+msgstr "{}: CI引用 {} 不存在!"
+
+#: api/lib/cmdb/resp_format.py:48
+msgid "{}: CI reference {} is illegal!"
+msgstr "{}, CI引用 {} 不合法!"
+
+#: api/lib/cmdb/resp_format.py:49
 msgid "Relationship constraint: {}, verification failed"
 msgstr "关系约束: {}, 校验失败"
 
-#: api/lib/cmdb/resp_format.py:49
+#: api/lib/cmdb/resp_format.py:51
 msgid ""
 "Many-to-many relationship constraint: Model {} <-> {} already has a many-"
 "to-many relationship!"
 msgstr "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
 
-#: api/lib/cmdb/resp_format.py:52
+#: api/lib/cmdb/resp_format.py:54
 msgid "CI relationship: {} does not exist"
 msgstr "CI关系: {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:55
+#: api/lib/cmdb/resp_format.py:57
 msgid "In search expressions, not supported before parentheses: or, not"
 msgstr "搜索表达式里小括号前不支持: 或、非"
 
-#: api/lib/cmdb/resp_format.py:57
+#: api/lib/cmdb/resp_format.py:59
 msgid "Model {} does not exist"
 msgstr "模型 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:58
+#: api/lib/cmdb/resp_format.py:60
 msgid "Model {} already exists"
 msgstr "模型 {} 已经存在"
 
-#: api/lib/cmdb/resp_format.py:59
+#: api/lib/cmdb/resp_format.py:61
 msgid "The primary key is undefined or has been deleted"
 msgstr "主键未定义或者已被删除"
 
-#: api/lib/cmdb/resp_format.py:60
+#: api/lib/cmdb/resp_format.py:62
 msgid "Only the creator can delete it!"
 msgstr "只有创建人才能删除它!"
 
-#: api/lib/cmdb/resp_format.py:61
+#: api/lib/cmdb/resp_format.py:63
 msgid "The model cannot be deleted because the CI already exists"
 msgstr "因为CI已经存在,不能删除模型"
 
-#: api/lib/cmdb/resp_format.py:63
+#: api/lib/cmdb/resp_format.py:65
 msgid "The inheritance cannot be deleted because the CI already exists"
 msgstr "因为CI已经存在,不能删除继承关系"
 
-#: api/lib/cmdb/resp_format.py:65
+#: api/lib/cmdb/resp_format.py:67
 msgid "The model is inherited and cannot be deleted"
 msgstr "该模型被继承, 不能删除"
 
 #: api/lib/cmdb/resp_format.py:68
+msgid "The model is referenced by attribute {} and cannot be deleted"
+msgstr "该模型被属性 {} 引用, 不能删除"
+
+#: api/lib/cmdb/resp_format.py:72
 msgid ""
 "The model cannot be deleted because the model is referenced by the "
 "relational view {}"
 msgstr "因为关系视图 {} 引用了该模型,不能删除模型"
 
-#: api/lib/cmdb/resp_format.py:70
+#: api/lib/cmdb/resp_format.py:74
 msgid "Model group {} does not exist"
 msgstr "模型分组 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:71
+#: api/lib/cmdb/resp_format.py:75
 msgid "Model group {} already exists"
 msgstr "模型分组 {} 已经存在"
 
-#: api/lib/cmdb/resp_format.py:72
+#: api/lib/cmdb/resp_format.py:76
 msgid "Model relationship {} does not exist"
 msgstr "模型关系 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:73
+#: api/lib/cmdb/resp_format.py:77
 msgid "Attribute group {} already exists"
 msgstr "属性分组 {} 已存在"
 
-#: api/lib/cmdb/resp_format.py:74
+#: api/lib/cmdb/resp_format.py:78
 msgid "Attribute group {} does not exist"
 msgstr "属性分组 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:76
+#: api/lib/cmdb/resp_format.py:80
 msgid "Attribute group <{0}> - attribute <{1}> does not exist"
 msgstr "属性组<{0}> - 属性<{1}> 不存在"
 
-#: api/lib/cmdb/resp_format.py:77
+#: api/lib/cmdb/resp_format.py:81
 msgid "The unique constraint already exists!"
 msgstr "唯一约束已经存在!"
 
-#: api/lib/cmdb/resp_format.py:79
+#: api/lib/cmdb/resp_format.py:83
 msgid "Uniquely constrained attributes cannot be JSON and multi-valued"
 msgstr "唯一约束的属性不能是 JSON 和 多值"
 
-#: api/lib/cmdb/resp_format.py:80
+#: api/lib/cmdb/resp_format.py:84
 msgid "Duplicated trigger"
 msgstr "重复的触发器"
 
-#: api/lib/cmdb/resp_format.py:81
+#: api/lib/cmdb/resp_format.py:85
 msgid "Trigger {} does not exist"
 msgstr "触发器 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:82
+#: api/lib/cmdb/resp_format.py:86
 msgid "Duplicated reconciliation rule"
 msgstr ""
 
-#: api/lib/cmdb/resp_format.py:83
+#: api/lib/cmdb/resp_format.py:87
 msgid "Reconciliation rule {} does not exist"
 msgstr "关系类型 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:85
+#: api/lib/cmdb/resp_format.py:89
 msgid "Operation record {} does not exist"
 msgstr "操作记录 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:86
+#: api/lib/cmdb/resp_format.py:90
 msgid "Unique identifier cannot be deleted"
 msgstr "不能删除唯一标识"
 
-#: api/lib/cmdb/resp_format.py:87
+#: api/lib/cmdb/resp_format.py:91
 msgid "Cannot delete default sorted attributes"
 msgstr "不能删除默认排序的属性"
 
-#: api/lib/cmdb/resp_format.py:89
+#: api/lib/cmdb/resp_format.py:93
 msgid "No node selected"
 msgstr "没有选择节点"
 
-#: api/lib/cmdb/resp_format.py:90
+#: api/lib/cmdb/resp_format.py:94
 msgid "This search option does not exist!"
 msgstr "该搜索选项不存在!"
 
-#: api/lib/cmdb/resp_format.py:91
+#: api/lib/cmdb/resp_format.py:95
 msgid "This search option has a duplicate name!"
 msgstr "该搜索选项命名重复!"
 
-#: api/lib/cmdb/resp_format.py:93
+#: api/lib/cmdb/resp_format.py:97
 msgid "Relationship type {} already exists"
 msgstr "关系类型 {} 已经存在"
 
-#: api/lib/cmdb/resp_format.py:94
+#: api/lib/cmdb/resp_format.py:98
 msgid "Relationship type {} does not exist"
 msgstr "关系类型 {} 不存在"
 
-#: api/lib/cmdb/resp_format.py:96
+#: api/lib/cmdb/resp_format.py:100
 msgid "Invalid attribute value: {}"
 msgstr "无效的属性值: {}"
 
-#: api/lib/cmdb/resp_format.py:97
+#: api/lib/cmdb/resp_format.py:101
 msgid "{} Invalid value: {}"
 msgstr "{} 无效的值: {}"
 
-#: api/lib/cmdb/resp_format.py:98
+#: api/lib/cmdb/resp_format.py:102
 msgid "{} is not in the predefined values"
 msgstr "{} 不在预定义值里"
 
-#: api/lib/cmdb/resp_format.py:100
+#: api/lib/cmdb/resp_format.py:104
 msgid "The value of attribute {} must be unique, {} already exists"
 msgstr "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
 
-#: api/lib/cmdb/resp_format.py:101
+#: api/lib/cmdb/resp_format.py:105
 msgid "Attribute {} value must exist"
 msgstr "属性 {} 值必须存在"
 
-#: api/lib/cmdb/resp_format.py:102
+#: api/lib/cmdb/resp_format.py:106
 msgid "Out of range value, the maximum value is 2147483647"
 msgstr "超过最大值限制, 最大值是2147483647"
 
-#: api/lib/cmdb/resp_format.py:104
+#: api/lib/cmdb/resp_format.py:108
 msgid "Unknown error when adding or modifying attribute value: {}"
 msgstr "新增或者修改属性值未知错误: {}"
 
-#: api/lib/cmdb/resp_format.py:106
+#: api/lib/cmdb/resp_format.py:110
 msgid "Duplicate custom name"
 msgstr "订制名重复"
 
-#: api/lib/cmdb/resp_format.py:108
+#: api/lib/cmdb/resp_format.py:112
 msgid "Number of models exceeds limit: {}"
 msgstr "模型数超过限制: {}"
 
-#: api/lib/cmdb/resp_format.py:109
+#: api/lib/cmdb/resp_format.py:113
 msgid "The number of CIs exceeds the limit: {}"
 msgstr "CI数超过限制: {}"
 
-#: api/lib/cmdb/resp_format.py:111
+#: api/lib/cmdb/resp_format.py:115
 msgid "Auto-discovery rule: {} already exists!"
 msgstr "自动发现规则: {} 已经存在!"
 
-#: api/lib/cmdb/resp_format.py:112
+#: api/lib/cmdb/resp_format.py:116
 msgid "Auto-discovery rule: {} does not exist!"
 msgstr "自动发现规则: {} 不存在!"
 
-#: api/lib/cmdb/resp_format.py:114
+#: api/lib/cmdb/resp_format.py:118
 msgid "This auto-discovery rule is referenced by the model and cannot be deleted!"
 msgstr "该自动发现规则被模型引用, 不能删除!"
 
-#: api/lib/cmdb/resp_format.py:116
+#: api/lib/cmdb/resp_format.py:120
 msgid "The application of auto-discovery rules cannot be defined repeatedly!"
 msgstr "自动发现规则的应用不能重复定义!"
 
-#: api/lib/cmdb/resp_format.py:117
+#: api/lib/cmdb/resp_format.py:121
 msgid "The auto-discovery you want to modify: {} does not exist!"
 msgstr "您要修改的自动发现: {} 不存在!"
 
-#: api/lib/cmdb/resp_format.py:118
+#: api/lib/cmdb/resp_format.py:122
 msgid "Attribute does not include unique identifier: {}"
 msgstr "属性字段没有包括唯一标识: {}"
 
-#: api/lib/cmdb/resp_format.py:119
+#: api/lib/cmdb/resp_format.py:123
 msgid "The auto-discovery instance does not exist!"
 msgstr "自动发现的实例不存在!"
 
-#: api/lib/cmdb/resp_format.py:120
+#: api/lib/cmdb/resp_format.py:124
 msgid "The model is not associated with this auto-discovery!"
 msgstr "模型并未关联该自动发现!"
 
-#: api/lib/cmdb/resp_format.py:121
+#: api/lib/cmdb/resp_format.py:125
 msgid "Only the creator can modify the Secret!"
 msgstr "只有创建人才能修改Secret!"
 
-#: api/lib/cmdb/resp_format.py:123
+#: api/lib/cmdb/resp_format.py:127
 msgid "This rule already has auto-discovery instances and cannot be deleted!"
 msgstr "该规则已经有自动发现的实例, 不能被删除!"
 
-#: api/lib/cmdb/resp_format.py:125
+#: api/lib/cmdb/resp_format.py:129
 msgid "The default auto-discovery rule is already referenced by model {}!"
 msgstr "该默认的自动发现规则 已经被模型 {} 引用!"
 
-#: api/lib/cmdb/resp_format.py:127
+#: api/lib/cmdb/resp_format.py:131
 msgid "The unique_key method must return a non-empty string!"
 msgstr "unique_key方法必须返回非空字符串!"
 
-#: api/lib/cmdb/resp_format.py:128
+#: api/lib/cmdb/resp_format.py:132
 msgid "The attributes method must return a list"
 msgstr "attributes方法必须返回的是list"
 
-#: api/lib/cmdb/resp_format.py:130
+#: api/lib/cmdb/resp_format.py:134
 msgid "The list returned by the attributes method cannot be empty!"
 msgstr "attributes方法返回的list不能为空!"
 
-#: api/lib/cmdb/resp_format.py:132
+#: api/lib/cmdb/resp_format.py:136
 msgid "Only administrators can define execution targets as: all nodes!"
 msgstr "只有管理员才可以定义执行机器为: 所有节点!"
 
-#: api/lib/cmdb/resp_format.py:133
+#: api/lib/cmdb/resp_format.py:137
 msgid "Execute targets permission check failed: {}"
 msgstr "执行机器权限检查不通过: {}"
 
-#: api/lib/cmdb/resp_format.py:135
+#: api/lib/cmdb/resp_format.py:139
 msgid "CI filter authorization must be named!"
 msgstr "CI过滤授权 必须命名!"
 
-#: api/lib/cmdb/resp_format.py:136
+#: api/lib/cmdb/resp_format.py:140
 msgid "CI filter authorization is currently not supported or query"
 msgstr "CI过滤授权 暂时不支持 或 查询"
 
-#: api/lib/cmdb/resp_format.py:139
+#: api/lib/cmdb/resp_format.py:143
 msgid "You do not have permission to operate attribute {}!"
 msgstr "您没有属性 {} 的操作权限!"
 
-#: api/lib/cmdb/resp_format.py:140
+#: api/lib/cmdb/resp_format.py:144
 msgid "You do not have permission to operate this CI!"
 msgstr "您没有该CI的操作权限!"
 
-#: api/lib/cmdb/resp_format.py:142
+#: api/lib/cmdb/resp_format.py:146
 msgid "Failed to save password: {}"
 msgstr "保存密码失败: {}"
 
-#: api/lib/cmdb/resp_format.py:143
+#: api/lib/cmdb/resp_format.py:147
 msgid "Failed to get password: {}"
 msgstr "获取密码失败: {}"
 
-#: api/lib/cmdb/resp_format.py:145
+#: api/lib/cmdb/resp_format.py:149
 msgid "Scheduling time format error"
 msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S"
 
-#: api/lib/cmdb/resp_format.py:146
+#: api/lib/cmdb/resp_format.py:150
 msgid "CMDB data reconciliation results"
 msgstr ""
 
-#: api/lib/cmdb/resp_format.py:147
+#: api/lib/cmdb/resp_format.py:151
 msgid "Number of {} illegal: {}"
 msgstr ""
 
-#: api/lib/cmdb/resp_format.py:149
+#: api/lib/cmdb/resp_format.py:153
 msgid "Topology view {} already exists"
 msgstr "拓扑视图 {} 已经存在"
 
-#: api/lib/cmdb/resp_format.py:150
+#: api/lib/cmdb/resp_format.py:154
 msgid "Topology group {} already exists"
 msgstr "拓扑视图分组 {} 已经存在"
 
-#: api/lib/cmdb/resp_format.py:152
+#: api/lib/cmdb/resp_format.py:156
 msgid "The group cannot be deleted because the topology view already exists"
 msgstr "因为该分组下定义了拓扑视图,不能删除"
 
diff --git a/cmdb-api/api/views/cmdb/ci.py b/cmdb-api/api/views/cmdb/ci.py
index 9afd217..b53f0c6 100644
--- a/cmdb-api/api/views/cmdb/ci.py
+++ b/cmdb-api/api/views/cmdb/ci.py
@@ -15,7 +15,7 @@ from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
 from api.lib.cmdb.const import RetKey
 from api.lib.cmdb.perms import has_perm_for_ci
 from api.lib.cmdb.search import SearchError
-from api.lib.cmdb.search.ci import search
+from api.lib.cmdb.search.ci import search as ci_search
 from api.lib.decorator import args_required
 from api.lib.perm.acl.acl import has_perm_from_args
 from api.lib.utils import get_page
@@ -160,7 +160,7 @@ class CISearchView(APIView):
         use_id_filter = request.values.get("use_id_filter", False) in current_app.config.get('BOOL_TRUE')
 
         start = time.time()
-        s = search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
+        s = ci_search(query, fl, facet, page, ret_key, count, sort, excludes, use_id_filter=use_id_filter)
         try:
             response, counter, total, page, numfound, facet = s.search()
         except SearchError as e:
diff --git a/cmdb-api/api/views/cmdb/ci_type.py b/cmdb-api/api/views/cmdb/ci_type.py
index bd17a97..04f02c9 100644
--- a/cmdb-api/api/views/cmdb/ci_type.py
+++ b/cmdb-api/api/views/cmdb/ci_type.py
@@ -48,16 +48,21 @@ class CITypeView(APIView):
         if request.url.endswith("icons"):
             return self.jsonify(CITypeManager().get_icons())
 
-        q = request.args.get("type_name")
+        q = request.values.get("type_name")
+        type_ids = handle_arg_list(request.values.get("type_ids"))
+        type_ids = type_ids or (type_id and [type_id])
+        if type_ids:
+            ci_types = []
+            for _type_id in type_ids:
+                ci_type = CITypeCache.get(_type_id)
+                if ci_type is None:
+                    return abort(404, ErrFormat.ci_type_not_found)
 
-        if type_id is not None:
-            ci_type = CITypeCache.get(type_id)
-            if ci_type is None:
-                return abort(404, ErrFormat.ci_type_not_found)
-
-            ci_type = ci_type.to_dict()
-            ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(type_id)
-            ci_types = [ci_type]
+                ci_type = ci_type.to_dict()
+                ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(_type_id)
+                ci_type['show_name'] = ci_type.get('show_id') and AttributeCache.get(ci_type['show_id']).name
+                ci_type['unique_name'] = ci_type['unique_id'] and AttributeCache.get(ci_type['unique_id']).name
+                ci_types.append(ci_type)
         elif type_name is not None:
             ci_type = CITypeCache.get(type_name).to_dict()
             ci_type['parent_ids'] = CITypeInheritanceManager.get_parents(ci_type['id'])