mirror of
				https://github.com/veops/cmdb.git
				synced 2025-11-04 05:36:17 +08:00 
			
		
		
		
	[fix] validate attribute is required
This commit is contained in:
		@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user