[fix] validate attribute is required

This commit is contained in:
pycook 2019-12-24 20:37:32 +08:00
parent 03bc27087d
commit 4e7e853173
6 changed files with 84 additions and 39 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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 = {

View File

@ -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)