From 89ae89a44998ceebb1b2af4b28b8ecc47a2736c4 Mon Sep 17 00:00:00 2001 From: pycook Date: Tue, 24 Dec 2019 20:37:32 +0800 Subject: [PATCH] [fix] validate attribute is required --- cmdb-api/api/lib/cmdb/cache.py | 33 ++++++++++++++++-- cmdb-api/api/lib/cmdb/ci.py | 11 +++--- cmdb-api/api/lib/cmdb/ci_type.py | 17 ++++++---- cmdb-api/api/lib/cmdb/preference.py | 4 +-- cmdb-api/api/lib/cmdb/utils.py | 6 ++-- cmdb-api/api/lib/cmdb/value.py | 52 ++++++++++++++++++----------- 6 files changed, 84 insertions(+), 39 deletions(-) diff --git a/cmdb-api/api/lib/cmdb/cache.py b/cmdb-api/api/lib/cmdb/cache.py index 45a6ee2..4421e74 100644 --- a/cmdb-api/api/lib/cmdb/cache.py +++ b/cmdb-api/api/lib/cmdb/cache.py @@ -107,13 +107,13 @@ class RelationTypeCache(object): cache.delete(cls.PREFIX_ID.format(ct.id)) -class CITypeAttributeCache(object): +class CITypeAttributesCache(object): """ key is type_id or type_name """ - PREFIX_ID = "CITypeAttribute::ID::{0}" - PREFIX_NAME = "CITypeAttribute::Name::{0}" + PREFIX_ID = "CITypeAttributes::TypeID::{0}" + PREFIX_NAME = "CITypeAttributes::TypeName::{0}" @classmethod def get(cls, key): @@ -146,3 +146,30 @@ class CITypeAttributeCache(object): if attrs is not None and ci_type: cache.delete(cls.PREFIX_ID.format(ci_type.id)) cache.delete(cls.PREFIX_NAME.format(ci_type.name)) + + +class CITypeAttributeCache(object): + """ + key is type_id & attr_id + """ + + PREFIX_ID = "CITypeAttribute::TypeID::{0}::AttrID::{1}" + + @classmethod + def get(cls, type_id, attr_id): + + attr = cache.get(cls.PREFIX_ID.format(type_id, attr_id)) + attr = attr or cache.get(cls.PREFIX_ID.format(type_id, attr_id)) + if not attr: + attr = CITypeAttribute.get_by(type_id=type_id, attr_id=attr_id, first=True, to_dict=False) + if attr is not None: + cls.set(type_id, attr_id, attr) + return attr + + @classmethod + def set(cls, type_id, attr_id, attr): + cache.set(cls.PREFIX_ID.format(type_id, attr_id), attr) + + @classmethod + def clean(cls, type_id, attr_id): + cache.delete(cls.PREFIX_ID.format(type_id, attr_id)) diff --git a/cmdb-api/api/lib/cmdb/ci.py b/cmdb-api/api/lib/cmdb/ci.py index 0c77bcc..2594a08 100644 --- a/cmdb-api/api/lib/cmdb/ci.py +++ b/cmdb-api/api/lib/cmdb/ci.py @@ -190,7 +190,7 @@ class CIManager(object): value_manager = AttributeValueManager() for p, v in ci_dict.items(): try: - value_manager.create_or_update_attr_value(p, v, ci.id, _no_attribute_policy) + value_manager.create_or_update_attr_value(p, v, ci, _no_attribute_policy) except BadRequest as e: if existed is None: cls.delete(ci.id) @@ -201,11 +201,12 @@ class CIManager(object): return ci.id def update(self, ci_id, **ci_dict): - self.confirm_ci_existed(ci_id) + ci = self.confirm_ci_existed(ci_id) + value_manager = AttributeValueManager() for p, v in ci_dict.items(): try: - value_manager.create_or_update_attr_value(p, v, ci_id) + value_manager.create_or_update_attr_value(p, v, ci) except BadRequest as e: raise e @@ -213,9 +214,9 @@ class CIManager(object): @staticmethod def update_unique_value(ci_id, unique_name, unique_value): - CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id)) + ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id)) - AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci_id) + AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci) ci_cache.apply_async([ci_id], queue=CMDB_QUEUE) diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index 6c32746..0c607a8 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -8,6 +8,7 @@ from api.extensions import db from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import CITypeAttributeCache +from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeCache from api.lib.decorator import kwargs_required from api.models.cmdb import CI @@ -18,8 +19,8 @@ from api.models.cmdb import CITypeAttributeGroupItem from api.models.cmdb import CITypeGroup from api.models.cmdb import CITypeGroupItem from api.models.cmdb import CITypeRelation -from api.models.cmdb import PreferenceTreeView from api.models.cmdb import PreferenceShowAttributes +from api.models.cmdb import PreferenceTreeView class CITypeManager(object): @@ -201,11 +202,11 @@ class CITypeAttributeManager(object): @staticmethod def get_attr_names_by_type_id(type_id): - return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributeCache.get(type_id)] + return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)] @staticmethod def get_attributes_by_type_id(type_id): - attrs = CITypeAttributeCache.get(type_id) + attrs = CITypeAttributesCache.get(type_id) result = list() for attr in sorted(attrs, key=lambda x: (x.order, x.id)): attr_dict = AttributeManager().get_attribute(attr.attr_id) @@ -247,7 +248,7 @@ class CITypeAttributeManager(object): current_app.logger.debug(attr_id) CITypeAttribute.create(type_id=type_id, attr_id=attr_id, **kwargs) - CITypeAttributeCache.clean(type_id) + CITypeAttributesCache.clean(type_id) @classmethod def update(cls, type_id, attributes): @@ -269,7 +270,9 @@ class CITypeAttributeManager(object): existed.update(**attr) - CITypeAttributeCache.clean(type_id) + CITypeAttributeCache.clean(type_id, existed.attr_id) + + CITypeAttributesCache.clean(type_id) @classmethod def delete(cls, type_id, attr_ids=None): @@ -289,7 +292,9 @@ class CITypeAttributeManager(object): if existed is not None: existed.soft_delete() - CITypeAttributeCache.clean(type_id) + CITypeAttributeCache.clean(type_id, attr_id) + + CITypeAttributesCache.clean(type_id) class CITypeRelationManager(object): diff --git a/cmdb-api/api/lib/cmdb/preference.py b/cmdb-api/api/lib/cmdb/preference.py index aca9a4b..2570d2a 100644 --- a/cmdb-api/api/lib/cmdb/preference.py +++ b/cmdb-api/api/lib/cmdb/preference.py @@ -13,7 +13,7 @@ from flask import g from api.extensions import db from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.cache import AttributeCache -from api.lib.cmdb.cache import CITypeAttributeCache +from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum from api.lib.exception import AbortException @@ -100,7 +100,7 @@ class PreferenceManager(object): @staticmethod def create_or_update_tree_view(type_id, levels): - attrs = CITypeAttributeCache.get(type_id) + attrs = CITypeAttributesCache.get(type_id) for idx, i in enumerate(levels): for attr in attrs: attr = AttributeCache.get(attr.attr_id) diff --git a/cmdb-api/api/lib/cmdb/utils.py b/cmdb-api/api/lib/cmdb/utils.py index 2f12abb..47131f7 100644 --- a/cmdb-api/api/lib/cmdb/utils.py +++ b/cmdb-api/api/lib/cmdb/utils.py @@ -34,7 +34,7 @@ class ValueTypeMap(object): ValueTypeEnum.TIME: lambda x: escape(x).encode('utf-8').decode('utf-8'), ValueTypeEnum.DATETIME: str2datetime, ValueTypeEnum.DATE: str2datetime, - ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) else x, + ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, } serialize = { @@ -44,7 +44,7 @@ class ValueTypeMap(object): ValueTypeEnum.TIME: lambda x: x if isinstance(x, six.string_types) else str(x), ValueTypeEnum.DATE: lambda x: x.strftime("%Y-%m-%d"), ValueTypeEnum.DATETIME: lambda x: x.strftime("%Y-%m-%d %H:%M:%S"), - ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) else x, + ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, } serialize2 = { @@ -54,7 +54,7 @@ class ValueTypeMap(object): ValueTypeEnum.TIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x, ValueTypeEnum.DATE: lambda x: x.decode() if not isinstance(x, six.string_types) else x, 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) else x, + ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, } choice = { diff --git a/cmdb-api/api/lib/cmdb/value.py b/cmdb-api/api/lib/cmdb/value.py index 742717a..f262002 100644 --- a/cmdb-api/api/lib/cmdb/value.py +++ b/cmdb-api/api/lib/cmdb/value.py @@ -8,6 +8,7 @@ from flask import abort from api.extensions import db from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.cache import AttributeCache +from api.lib.cmdb.cache import CITypeAttributeCache from api.lib.cmdb.const import ExistPolicy from api.lib.cmdb.const import OperateType from api.lib.cmdb.history import AttributeHistoryManger @@ -77,23 +78,31 @@ class AttributeValueManager(object): return abort(400, "attribute value <{0}> is invalid".format(value)) @staticmethod - def __check_is_choice(attr_id, value_type, value): - choice_values = AttributeManager.get_choice_values(attr_id, value_type) + def __check_is_choice(attr, value_type, value): + choice_values = AttributeManager.get_choice_values(attr.id, value_type) if value not in choice_values: return abort(400, "{0} does not existed in choice values".format(value)) @staticmethod - def __check_is_unique(value_table, attr_id, ci_id, value): + def __check_is_unique(value_table, attr, ci_id, value): existed = db.session.query(value_table.attr_id).filter( - value_table.attr_id == attr_id).filter(value_table.deleted.is_(False)).filter( + value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter( value_table.value == value).filter(value_table.ci_id != ci_id).first() - existed and abort(400, "attribute <{0}> value {1} must be unique".format(attr_id, value)) + existed and abort(400, "attribute <{0}> value {1} must be unique".format(attr.alias, value)) - def _validate(self, attr, value, value_table, ci_id): + @staticmethod + def __check_is_required(type_id, attr, value): + type_attr = CITypeAttributeCache.get(type_id, attr.id) + if type_attr and type_attr.is_required and not value and value != 0: + return abort(400, "attribute <{0}> value is required".format(attr.alias)) + + def _validate(self, attr, value, value_table, ci): v = self.__deserialize_value(attr.value_type, value) - attr.is_choice and value and self.__check_is_choice(attr.id, attr.value_type, v) - attr.is_unique and self.__check_is_unique(value_table, attr.id, ci_id, v) + 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.id, v) + + self.__check_is_required(ci.type_id, attr, v) return v @@ -101,12 +110,12 @@ class AttributeValueManager(object): def _write_change(ci_id, attr_id, operate_type, old, new): AttributeHistoryManger.add(ci_id, [(attr_id, operate_type, old, new)]) - def create_or_update_attr_value(self, key, value, ci_id, _no_attribute_policy=ExistPolicy.IGNORE): + def create_or_update_attr_value(self, key, value, ci, _no_attribute_policy=ExistPolicy.IGNORE): """ add or update attribute value, then write history :param key: id, name or alias :param value: - :param ci_id: + :param ci: instance object :param _no_attribute_policy: ignore or reject :return: """ @@ -120,31 +129,34 @@ class AttributeValueManager(object): value_table = TableMap(attr_name=attr.name).table if attr.is_list: - value_list = [self._validate(attr, i, value_table, ci_id) for i in handle_arg_list(value)] + value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)] + if not value_list: + self.__check_is_required(ci.type_id, attr, '') + existed_attrs = value_table.get_by(attr_id=attr.id, - ci_id=ci_id, + ci_id=ci.id, to_dict=False) existed_values = [i.value for i in existed_attrs] added = set(value_list) - set(existed_values) deleted = set(existed_values) - set(value_list) for v in added: - value_table.create(ci_id=ci_id, attr_id=attr.id, value=v) - self._write_change(ci_id, attr.id, OperateType.ADD, None, v) + value_table.create(ci_id=ci.id, attr_id=attr.id, value=v) + self._write_change(ci.id, attr.id, OperateType.ADD, None, v) for v in deleted: existed_attr = existed_attrs[existed_values.index(v)] existed_attr.delete() - self._write_change(ci_id, attr.id, OperateType.DELETE, v, None) + self._write_change(ci.id, attr.id, OperateType.DELETE, v, None) else: - value = self._validate(attr, value, value_table, ci_id) + value = self._validate(attr, value, value_table, ci) existed_attr = value_table.get_by(attr_id=attr.id, - ci_id=ci_id, + ci_id=ci.id, first=True, to_dict=False) existed_value = existed_attr and existed_attr.value if existed_value is None: - value_table.create(ci_id=ci_id, attr_id=attr.id, value=value) - self._write_change(ci_id, attr.id, OperateType.ADD, None, value) + value_table.create(ci_id=ci.id, attr_id=attr.id, value=value) + self._write_change(ci.id, attr.id, OperateType.ADD, None, value) else: existed_attr.update(value=value) - self._write_change(ci_id, attr.id, OperateType.UPDATE, existed_value, value) + self._write_change(ci.id, attr.id, OperateType.UPDATE, existed_value, value)