feat(api): issue #212 (#279)

This commit is contained in:
pycook 2023-11-24 10:26:48 +08:00 committed by GitHub
parent 91555ffa64
commit 7ef23bd779
11 changed files with 294 additions and 71 deletions

View File

@ -19,6 +19,7 @@ from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI
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 ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum
@ -49,12 +50,17 @@ def cmdb_init_cache():
ci_relations = CIRelation.get_by(to_dict=False)
relations = dict()
relations2 = dict()
for cr in ci_relations:
relations.setdefault(cr.first_ci_id, {}).update({cr.second_ci_id: cr.second_ci.type_id})
if cr.ancestor_ids:
relations2.setdefault(cr.ancestor_ids, {}).update({cr.second_ci_id: cr.second_ci.type_id})
for i in relations:
relations[i] = json.dumps(relations[i])
if relations:
rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
if relations2:
rd.create_or_update(relations2, REDIS_PREFIX_CI_RELATION2)
es = None
if current_app.config.get("USE_ES"):

View File

@ -182,6 +182,9 @@ class CIManager(object):
need_children and res.update(CIRelationManager.get_children(ci_id, ret_key=ret_key)) # one floor
ci_type = CITypeCache.get(ci.type_id)
if not ci_type:
return res
res["ci_type"] = ci_type.name
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
@ -518,11 +521,13 @@ class CIManager(object):
item.delete(commit=False)
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
item.delete(commit=False)
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(
args=(item.first_ci_id, item.second_ci_id, item.ancestor_ids), queue=CMDB_QUEUE)
item.delete(commit=False)
ad_ci = AutoDiscoveryCI.get_by(ci_id=ci_id, to_dict=False, first=True)
@ -886,12 +891,14 @@ class CIRelationManager(object):
@classmethod
def get_ancestor_ids(cls, ci_ids, level=1):
for _ in range(level):
cis = db.session.query(CIRelation.first_ci_id).filter(
level2ids = dict()
for _level in range(1, level + 1):
cis = db.session.query(CIRelation.first_ci_id, CIRelation.ancestor_ids).filter(
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
ci_ids = [i.first_ci_id for i in cis]
level2ids[_level + 1] = {int(i.ancestor_ids.split(',')[-1]) for i in cis if i.ancestor_ids}
return ci_ids
return ci_ids, level2ids
@staticmethod
def _check_constraint(first_ci_id, first_type_id, second_ci_id, second_type_id, type_relation):
@ -918,13 +925,14 @@ class CIRelationManager(object):
return abort(400, ErrFormat.relation_constraint.format("1-N"))
@classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None):
first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id)
existed = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
ancestor_ids=ancestor_ids,
to_dict=False,
first=True)
if existed is not None:
@ -960,11 +968,12 @@ class CIRelationManager(object):
existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
relation_type_id=relation_type_id)
relation_type_id=relation_type_id,
ancestor_ids=ancestor_ids)
CIRelationHistoryManager().add(existed, OperateType.ADD)
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
if more is not None:
existed.upadte(more=more)
@ -988,53 +997,56 @@ class CIRelationManager(object):
his_manager = CIRelationHistoryManager()
his_manager.add(cr, operate_type=OperateType.DELETE)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
return cr_id
@classmethod
def delete_2(cls, first_ci_id, second_ci_id):
def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
ancestor_ids=ancestor_ids,
to_dict=False,
first=True)
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
return cls.delete(cr.id)
return cr and cls.delete(cr.id)
@classmethod
def batch_update(cls, ci_ids, parents, children):
def batch_update(cls, ci_ids, parents, children, ancestor_ids=None):
"""
only for many to one
:param ci_ids:
:param parents:
:param children:
:param ancestor_ids:
:return:
"""
if isinstance(parents, list):
for parent_id in parents:
for ci_id in ci_ids:
cls.add(parent_id, ci_id)
cls.add(parent_id, ci_id, ancestor_ids=ancestor_ids)
if isinstance(children, list):
for child_id in children:
for ci_id in ci_ids:
cls.add(ci_id, child_id)
cls.add(ci_id, child_id, ancestor_ids=ancestor_ids)
@classmethod
def batch_delete(cls, ci_ids, parents):
def batch_delete(cls, ci_ids, parents, ancestor_ids=None):
"""
only for many to one
:param ci_ids:
:param parents:
:param ancestor_ids:
:return:
"""
if isinstance(parents, list):
for parent_id in parents:
for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id)
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
class CITriggerManager(object):

View File

@ -637,6 +637,16 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
if constraint == ConstraintEnum.Many2Many:
other_c = CITypeRelation.get_by(parent_id=p.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
other_p = CITypeRelation.get_by(child_id=c.id, constraint=ConstraintEnum.Many2Many,
to_dict=False, first=True)
if other_c and other_c.child_id != c.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(p.name, other_c.child.name))
if other_p and other_p.parent_id != p.id:
return abort(400, ErrFormat.m2m_relation_constraint.format(other_p.parent.name, c.name))
existed = cls._get(p.id, c.id)
if existed is not None:
existed.update(relation_type_id=relation_type_id,
@ -686,6 +696,24 @@ class CITypeRelationManager(object):
cls.delete(ctr.id)
@staticmethod
def get_level2constraint(root_id, level):
level = level + 1 if level == 1 else level
ci = CI.get_by_id(root_id)
if ci is None:
return dict()
root_id = ci.type_id
level2constraint = dict()
for lv in range(1, int(level) + 1):
for i in CITypeRelation.get_by(parent_id=root_id, to_dict=False):
if i.constraint == ConstraintEnum.Many2Many:
root_id = i.child_id
level2constraint[lv] = ConstraintEnum.Many2Many
break
return level2constraint
class CITypeAttributeGroupManager(object):
cls = CITypeAttributeGroup

View File

@ -100,6 +100,7 @@ class AttributeDefaultValueEnum(BaseEnum):
CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2"
BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}

View File

@ -14,7 +14,10 @@ from api.lib.cmdb.attribute import AttributeManager
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.exception import AbortException
@ -229,14 +232,28 @@ class PreferenceManager(object):
if not parents:
return
for l in leaf:
_find_parent(l)
for _l in leaf:
_find_parent(_l)
for node_id in node2show_types:
node2show_types[node_id] = [CITypeCache.get(i).to_dict() for i in set(node2show_types[node_id])]
topo_flatten = list(toposort.toposort_flatten(topo))
level2constraint = {}
for i, _ in enumerate(topo_flatten[1:]):
ctr = CITypeRelation.get_by(
parent_id=topo_flatten[i], child_id=topo_flatten[i + 1], first=True, to_dict=False)
level2constraint[i + 1] = ctr and ctr.constraint
if leaf2show_types.get(topo_flatten[-1]):
ctr = CITypeRelation.get_by(
parent_id=topo_flatten[-1],
child_id=leaf2show_types[topo_flatten[-1]][0], first=True, to_dict=False)
level2constraint[len(topo_flatten)] = ctr and ctr.constraint
result[view_name] = dict(topo=list(map(list, toposort.toposort(topo))),
topo_flatten=list(toposort.toposort_flatten(topo)),
topo_flatten=topo_flatten,
level2constraint=level2constraint,
leaf=leaf,
leaf2show_types=leaf2show_types,
node2show_types=node2show_types,
@ -338,3 +355,29 @@ class PreferenceManager(object):
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
i.soft_delete()
@staticmethod
def can_edit_relation(parent_id, child_id):
views = PreferenceRelationView.get_by(to_dict=False)
for view in views:
has_m2m = False
last_node_id = None
for cr in view.cr_ids:
_rel = CITypeRelation.get_by(parent_id=cr['parent_id'], child_id=cr['child_id'],
first=True, to_dict=False)
if _rel and _rel.constraint == ConstraintEnum.Many2Many:
has_m2m = True
if parent_id == _rel.parent_id and child_id == _rel.child_id:
return False
if _rel:
last_node_id = _rel.child_id
if parent_id == last_node_id:
rels = CITypeRelation.get_by(parent_id=last_node_id, to_dict=False)
for rel in rels:
if rel.child_id == child_id and has_m2m:
return False
return True

View File

@ -31,6 +31,7 @@ class ErrFormat(CommonErrFormat):
unique_key_required = "主键字段 {} 缺失"
ci_is_already_existed = "CI 已经存在!"
relation_constraint = "关系约束: {}, 校验失败 "
m2m_relation_constraint = "多对多关系 限制: 模型 {} <-> {} 已经存在多对多关系!"
relation_not_found = "CI关系: {} 不存在"
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"

View File

@ -1,6 +1,4 @@
# -*- coding:utf-8 -*-
import json
from collections import Counter
@ -10,11 +8,14 @@ from flask import current_app
from api.extensions import rd
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
from api.models.cmdb import CI
from api.models.cmdb import CIRelation
class Search(object):
@ -26,7 +27,8 @@ class Search(object):
page=1,
count=None,
sort=None,
reverse=False):
reverse=False,
ancestor_ids=None):
self.orig_query = query
self.fl = fl
self.facet_field = facet_field
@ -38,25 +40,81 @@ class Search(object):
self.level = level or 0
self.reverse = reverse
def _get_ids(self):
self.level2constraint = CITypeRelationManager.get_level2constraint(
root_id[0] if root_id and isinstance(root_id, list) else root_id,
level[0] if isinstance(level, list) and level else level)
self.ancestor_ids = ancestor_ids
self.has_m2m = False
if self.ancestor_ids:
self.has_m2m = True
else:
level = level[0] if isinstance(level, list) and level else level
for _l, c in self.level2constraint.items():
if _l < int(level) and c == ConstraintEnum.Many2Many:
self.has_m2m = True
def _get_ids(self, ids):
if self.level[-1] == 1 and len(ids) == 1:
if self.ancestor_ids is None:
return [i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0], to_dict=False)]
else:
seconds = {i.second_ci_id for i in CIRelation.get_by(first_ci_id=ids[0],
ancestor_ids=self.ancestor_ids,
to_dict=False)}
return list(seconds)
merge_ids = []
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
key = []
_tmp = []
for level in range(1, sorted(self.level)[-1] + 1):
_tmp = list(map(lambda x: list(json.loads(x).keys()),
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
if not self.has_m2m:
_tmp = map(lambda x: json.loads(x).keys(),
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or []))
ids = [j for i in _tmp for j in i]
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
if not self.ancestor_ids:
if level == 1:
key, prefix = list(map(str, ids)), REDIS_PREFIX_CI_RELATION
else:
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
else:
if level == 1:
key, prefix = ["{},{}".format(self.ancestor_ids, i) for i in ids], REDIS_PREFIX_CI_RELATION2
else:
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
if not key:
return []
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
ids = [j for i in _tmp for j in i]
if level in self.level:
merge_ids.extend(ids)
return merge_ids
def _get_reverse_ids(self):
def _get_reverse_ids(self, ids):
merge_ids = []
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
level2ids = {}
for level in range(1, sorted(self.level)[-1] + 1):
ids = CIRelationManager.get_ancestor_ids(ids, 1)
ids, _level2ids = CIRelationManager.get_ancestor_ids(ids, 1)
if _level2ids.get(2):
level2ids[level + 1] = _level2ids[2]
if level in self.level:
merge_ids.extend(ids)
if level in level2ids and level2ids[level]:
merge_ids.extend(set(ids) & set(level2ids[level]))
else:
merge_ids.extend(ids)
return merge_ids
@ -64,7 +122,7 @@ class Search(object):
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
merge_ids = self._get_ids(ids) if not self.reverse else self._get_reverse_ids(ids)
if not self.orig_query or ("_type:" not in self.orig_query
and "type_id:" not in self.orig_query
@ -76,11 +134,11 @@ class Search(object):
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
else:
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
type_ids = list(set(type_ids))
type_ids = set(type_ids)
if self.orig_query:
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
self.orig_query = "_type:({0}),{1}".format(";".join(map(str, type_ids)), self.orig_query)
else:
self.orig_query = "_type:({0})".format(";".join(list(map(str, type_ids))))
self.orig_query = "_type:({0})".format(";".join(map(str, type_ids)))
if not merge_ids:
# cis, counter, total, self.page, numfound, facet_
@ -105,35 +163,65 @@ class Search(object):
def statistics(self, type_ids):
self.level = int(self.level)
_tmp = []
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
for lv in range(0, self.level):
if not lv:
if type_ids and lv == self.level - 1:
_tmp = []
level2ids = {}
for lv in range(1, self.level + 1):
level2ids[lv] = []
if lv == 1:
if not self.has_m2m:
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
if not self.ancestor_ids:
key, prefix = ids, REDIS_PREFIX_CI_RELATION
else:
key = ["{},{}".format(self.ancestor_ids, _id) for _id in ids]
prefix = REDIS_PREFIX_CI_RELATION2
level2ids[lv] = [[i] for i in key]
if not key:
_tmp = []
continue
if type_ids and lv == self.level:
_tmp = list(map(lambda x: [i for i in x if i[1] in type_ids],
(map(lambda x: list(json.loads(x).items()),
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))))
[i or '{}' for i in rd.get(key, prefix) or []]))))
else:
_tmp = list(map(lambda x: list(json.loads(x).items()),
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
[i or '{}' for i in rd.get(key, prefix) or []]))
else:
for idx, item in enumerate(_tmp):
if item:
if type_ids and lv == self.level - 1:
__tmp = list(
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
if type_id in type_ids],
filter(lambda x: x is not None,
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
if not self.has_m2m:
key, prefix = [i[0] for i in item], REDIS_PREFIX_CI_RELATION
else:
key = list(set(['{},{}'.format(j, i[0]) for i in item for j in level2ids[lv - 1][idx]]))
prefix = REDIS_PREFIX_CI_RELATION2
__tmp = list(map(lambda x: list(json.loads(x).items()),
filter(lambda x: x is not None,
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
level2ids[lv].append(key)
if key:
if type_ids and lv == self.level:
__tmp = map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
if type_id in type_ids],
filter(lambda x: x is not None,
rd.get(key, prefix) or []))
else:
__tmp = map(lambda x: list(json.loads(x).items()),
filter(lambda x: x is not None,
rd.get(key, prefix) or []))
else:
__tmp = []
_tmp[idx] = [j for i in __tmp for j in i]
else:
_tmp[idx] = []
level2ids[lv].append([])
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}

View File

@ -218,6 +218,8 @@ class CIRelation(Model):
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
ancestor_ids = db.Column(db.String(128), index=True)
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")

View File

@ -16,6 +16,7 @@ from api.lib.cmdb.cache import CITypeAttributesCache
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION2
from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db
from api.lib.perm.acl.cache import UserCache
@ -97,16 +98,30 @@ def ci_delete_trigger(trigger, operate_type, ci_dict):
@celery.task(name="cmdb.ci_relation_cache", queue=CMDB_QUEUE)
@flush_db
@reconnect_db
def ci_relation_cache(parent_id, child_id):
def ci_relation_cache(parent_id, child_id, ancestor_ids):
with Lock("CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if str(child_id) not in children:
children[str(child_id)] = cr.second_ci.type_id
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, ancestor_ids=ancestor_ids,
first=True, to_dict=False)
if cr and str(cr.second_ci_id) not in grandson:
grandson[str(cr.second_ci_id)] = cr.second_ci.type_id
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
current_app.logger.info("ADD ci relation cache: {0} -> {1}".format(parent_id, child_id))
@ -156,20 +171,31 @@ def ci_relation_add(parent_dict, child_id, uid):
try:
db.session.commit()
except:
pass
db.session.rollback()
@celery.task(name="cmdb.ci_relation_delete", queue=CMDB_QUEUE)
@reconnect_db
def ci_relation_delete(parent_id, child_id):
def ci_relation_delete(parent_id, child_id, ancestor_ids):
with Lock("CIRelation_{}".format(parent_id)):
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if ancestor_ids is None:
children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
children = json.loads(children) if children is not None else {}
if str(child_id) in children:
children.pop(str(child_id))
if str(child_id) in children:
children.pop(str(child_id))
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
else:
key = "{},{}".format(ancestor_ids, parent_id)
grandson = rd.get([key], REDIS_PREFIX_CI_RELATION2)[0]
grandson = json.loads(grandson) if grandson is not None else {}
if str(child_id) in grandson:
grandson.pop(str(child_id))
rd.create_or_update({key: json.dumps(grandson)}, REDIS_PREFIX_CI_RELATION2)
current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id))

View File

@ -35,6 +35,7 @@ class CIRelationSearchView(APIView):
count = get_page_size(request.values.get("count") or request.values.get("page_size"))
root_id = request.values.get('root_id')
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
level = list(map(int, handle_arg_list(request.values.get('level', '1'))))
query = request.values.get('q', "")
@ -44,7 +45,7 @@ class CIRelationSearchView(APIView):
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE')
start = time.time()
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse)
s = Search(root_id, level, query, fl, facet, page, count, sort, reverse, ancestor_ids=ancestor_ids)
try:
response, counter, total, page, numfound, facet = s.search()
except SearchError as e:
@ -67,9 +68,10 @@ class CIRelationStatisticsView(APIView):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1)
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
start = time.time()
s = Search(root_ids, level)
s = Search(root_ids, level, ancestor_ids=ancestor_ids)
try:
result = s.statistics(type_ids)
except SearchError as e:
@ -121,14 +123,18 @@ class CIRelationView(APIView):
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
def post(self, first_ci_id, second_ci_id):
ancestor_ids = request.values.get('ancestor_ids') or None
manager = CIRelationManager()
res = manager.add(first_ci_id, second_ci_id)
res = manager.add(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
return self.jsonify(cr_id=res)
def delete(self, first_ci_id, second_ci_id):
ancestor_ids = request.values.get('ancestor_ids') or None
manager = CIRelationManager()
manager.delete_2(first_ci_id, second_ci_id)
manager.delete_2(first_ci_id, second_ci_id, ancestor_ids=ancestor_ids)
return self.jsonify(message="CIType Relation is deleted")
@ -151,8 +157,9 @@ class BatchCreateOrUpdateCIRelationView(APIView):
ci_ids = list(map(int, request.values.get('ci_ids')))
parents = list(map(int, request.values.get('parents', [])))
children = list(map(int, request.values.get('children', [])))
ancestor_ids = request.values.get('ancestor_ids') or None
CIRelationManager.batch_update(ci_ids, parents, children)
CIRelationManager.batch_update(ci_ids, parents, children, ancestor_ids=ancestor_ids)
return self.jsonify(code=200)
@ -166,7 +173,8 @@ class BatchCreateOrUpdateCIRelationView(APIView):
def delete(self):
ci_ids = list(map(int, request.values.get('ci_ids')))
parents = list(map(int, request.values.get('parents', [])))
ancestor_ids = request.values.get('ancestor_ids') or None
CIRelationManager.batch_delete(ci_ids, parents)
CIRelationManager.batch_delete(ci_ids, parents, ancestor_ids=ancestor_ids)
return self.jsonify(code=200)

View File

@ -9,6 +9,7 @@ from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager
@ -109,3 +110,10 @@ class CITypeRelationRevokeView(APIView):
acl.revoke_resource_from_role_by_rid(resource_name, rid, ResourceTypeEnum.CI_TYPE_RELATION, perms)
return self.jsonify(code=200)
class CITypeRelationCanEditView(APIView):
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>/can_edit"
def get(self, parent_id, child_id):
return self.jsonify(result=PreferenceManager.can_edit_relation(parent_id, child_id))