diff --git a/.gitignore b/.gitignore index 01ccfa2..a53b01e 100755 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ pip-log.txt .tox nosetests.xml .pytest_cache +cmdb-api/test-output # Translations *.mo diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index 8c7934d..bf936d4 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -23,6 +23,7 @@ from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeRelation from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceTreeView +from api.tasks.cmdb import ci_type_attribute_order_rebuild class CITypeManager(object): @@ -55,16 +56,32 @@ class CITypeManager(object): ci_type = CITypeCache.get(_type) or abort(404, "CIType <{0}> is not found".format(_type)) return ci_type.to_dict() + @staticmethod + def _validate_unique(type_id=None, name=None, alias=None): + if name is not None: + ci_type = CIType.get_by(name=name, first=True, to_dict=False) + elif alias is not None: + ci_type = CIType.get_by(alias=alias, first=True, to_dict=False) + else: + return + + if type_id is not None and ci_type.id != type_id: + return abort(400, "CIType <{0}> is already existed".format(name or alias)) + + if type_id is None and ci_type is not None: + return abort(400, "CIType <{0}> is already existed".format(name or alias)) + @classmethod @kwargs_required("name") def add(cls, **kwargs): unique_key = kwargs.pop("unique_key", None) unique_key = AttributeCache.get(unique_key) or abort(404, "Unique key is not defined") - CIType.get_by(name=kwargs['name']) and abort(404, "CIType <{0}> is already existed".format(kwargs.get("name"))) - kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"] + cls._validate_unique(name=kwargs['name']) + cls._validate_unique(alias=kwargs['alias']) + kwargs["unique_id"] = unique_key.id ci_type = CIType.create(**kwargs) @@ -88,6 +105,9 @@ 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')) + unique_key = kwargs.pop("unique_key", None) unique_key = AttributeCache.get(unique_key) if unique_key is not None: @@ -305,6 +325,31 @@ class CITypeAttributeManager(object): CITypeAttributesCache.clean(type_id) + @classmethod + def transfer(cls, type_id, _from, _to): + current_app.logger.info("[{0}] {1} -> {2}".format(type_id, _from, _to)) + attr_id = _from.get('attr_id') + from_group_id = _from.get('group_id') + to_group_id = _to.get('group_id') + order = _to.get('order') + + if from_group_id != to_group_id: + if from_group_id is not None: + CITypeAttributeGroupManager.delete_item(from_group_id, attr_id) + + if to_group_id is not None: + CITypeAttributeGroupManager.add_item(to_group_id, attr_id, order) + + elif from_group_id: + CITypeAttributeGroupManager.update_item(from_group_id, attr_id, order) + + else: # other attribute transfer + return abort(400, "invalid operation!!!") + + CITypeAttributesCache.clean(type_id) + + ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE) + class CITypeRelationManager(object): """ @@ -455,3 +500,86 @@ class CITypeAttributeGroupManager(object): item.soft_delete() return group_id + + @classmethod + def add_item(cls, group_id, attr_id, order): + db.session.remove() + + existed = CITypeAttributeGroupItem.get_by(group_id=group_id, + attr_id=attr_id, + first=True, + to_dict=False) + if existed is not None: + existed.update(order=order) + else: + CITypeAttributeGroupItem.create(group_id=group_id, attr_id=attr_id, order=order) + + gt_items = db.session.query(CITypeAttributeGroupItem).filter( + CITypeAttributeGroupItem.deleted.is_(False)).filter(CITypeAttributeGroupItem.order > order) + for _item in gt_items: + _order = _item.order + _item.update(order=_order + 1) + + @classmethod + def update_item(cls, group_id, attr_id, order): + db.session.remove() + + existed = CITypeAttributeGroupItem.get_by(group_id=group_id, + attr_id=attr_id, + first=True, + to_dict=False) + existed or abort(404, "Group<{0}> - Attribute<{1}> is not found".format(group_id, attr_id)) + + if existed.order > order: # forward, +1 + items = db.session.query(CITypeAttributeGroupItem).filter( + CITypeAttributeGroupItem.deleted.is_(False)).filter( + CITypeAttributeGroupItem.order >= order).filter( + CITypeAttributeGroupItem.order < existed.order) + for item in items: + item.update(order=item.order + 1) + + elif existed.order < order: # backward, -1 + items = db.session.query(CITypeAttributeGroupItem).filter( + CITypeAttributeGroupItem.deleted.is_(False)).filter( + CITypeAttributeGroupItem.order > existed.order).filter( + CITypeAttributeGroupItem.order <= order) + for item in items: + item.update(order=item.order - 1) + + existed.update(order=order) + + @classmethod + def delete_item(cls, group_id, attr_id): + db.session.remove() + + item = CITypeAttributeGroupItem.get_by(group_id=group_id, + attr_id=attr_id, + first=True, + to_dict=False) + + if item is not None: + item.soft_delete() + order = item.order + gt_items = db.session.query(CITypeAttributeGroupItem).filter( + CITypeAttributeGroupItem.deleted.is_(False)).filter(CITypeAttributeGroupItem.order > order) + for _item in gt_items: + _order = _item.order + _item.update(order=_order - 1) + + @classmethod + def transfer(cls, type_id, _from, _to): + current_app.logger.info("CIType[{0}] {1} -> {2}".format(type_id, _from, _to)) + from_group = CITypeAttributeGroup.get_by_id(_from) + from_group or abort(404, "Group <{0}> is not found".format(_from)) + + to_group = CITypeAttributeGroup.get_by_id(_to) + to_group or abort(404, "Group <{0}> is not found".format(_to)) + + from_order, to_order = from_group.order, to_group.order + + from_group.update(order=to_order) + to_group.update(order=from_order) + + CITypeAttributesCache.clean(type_id) + + ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE) diff --git a/cmdb-api/api/tasks/cmdb.py b/cmdb-api/api/tasks/cmdb.py index 36853eb..7560b5e 100644 --- a/cmdb-api/api/tasks/cmdb.py +++ b/cmdb-api/api/tasks/cmdb.py @@ -11,6 +11,7 @@ from api.extensions import celery from api.extensions import db from api.extensions import es from api.extensions import rd +from api.lib.cmdb.cache import CITypeAttributeCache 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 @@ -71,3 +72,23 @@ def ci_relation_delete(parent_id, child_id): rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION) current_app.logger.info("DELETE ci relation cache: {0} -> {1}".format(parent_id, child_id)) + + +@celery.task(name="cmdb.ci_type_attribute_order_rebuild", queue=CMDB_QUEUE) +def ci_type_attribute_order_rebuild(type_id): + current_app.logger.info('rebuild attribute order') + db.session.remove() + + from api.lib.cmdb.ci_type import CITypeAttributeGroupManager + + attrs = CITypeAttributeCache.get(type_id) + id2attr = {attr.attr_id: attr for attr in attrs} + + res = CITypeAttributeGroupManager.get_by_type_id(type_id, True) + order = 0 + for group in res: + for _attr in group.get('attributes'): + if order != id2attr.get(_attr['id']) and id2attr.get(_attr['id']): + id2attr.get(_attr['id']).update(order=order) + + order += 1 diff --git a/cmdb-api/api/views/cmdb/ci_type.py b/cmdb-api/api/views/cmdb/ci_type.py index 554655d..50fc386 100644 --- a/cmdb-api/api/views/cmdb/ci_type.py +++ b/cmdb-api/api/views/cmdb/ci_type.py @@ -164,6 +164,34 @@ class CITypeAttributeView(APIView): return self.jsonify(attributes=attr_id_list) +class CITypeAttributeTransferView(APIView): + url_prefix = "/ci_types//attributes/transfer" + + @args_required('from') + @args_required('to') + def post(self, type_id): + _from = request.values.get('from') # {'attr_id': xx, 'group_id': xx} + _to = request.values.get('to') # {'group_id': xx, 'order': xxx} + + CITypeAttributeManager.transfer(type_id, _from, _to) + + return self.jsonify(code=200) + + +class CITypeAttributeGroupTransferView(APIView): + url_prefix = "/ci_types//attribute_groups/transfer" + + @args_required('from') + @args_required('to') + def post(self, type_id): + _from = request.values.get('from') # group_id + _to = request.values.get('to') # group_id + + CITypeAttributeGroupManager.transfer(type_id, _from, _to) + + return self.jsonify(code=200) + + class CITypeAttributeGroupView(APIView): url_prefix = ("/ci_types//attribute_groups", "/ci_types/attribute_groups/")