def _set_attr_name_map(self): """ build a map for attributes names and display names Dict containing all display_name to attr_name mappings for all objects used in the current query Example: { Program: {"Program URL": "url", "Code": "slug", ...} ...} """ self.attr_name_map = {} for object_query in self.query: object_name = object_query["object_name"] object_class = self.object_map[object_name] aliases = AttributeInfo.gather_aliases(object_class) self.attr_name_map[object_class] = {} for key, value in aliases.items(): filter_by = None if isinstance(value, dict): filter_name = value.get("filter_by", None) if filter_name is not None: filter_by = getattr(object_class, filter_name, None) value = value["display_name"] if value: self.attr_name_map[object_class][value.lower()] = ( key.lower(), filter_by) if not self.ca_disabled: custom_attrs = AttributeInfo.get_custom_attr_definitions( object_class) else: custom_attrs = {} for key, definition in custom_attrs.items(): if not key.startswith("__custom__:") or \ "display_name" not in definition: continue try: # Global custom attribute definition can only have a single id on # their name, so it is safe for that. Currently the filters do not # work with object level custom attributes. attr_id = definition["definition_ids"][0] except KeyError: continue filter_by = CustomAttributeValue.mk_filter_by_custom( object_class, attr_id) name = definition["display_name"].lower() self.attr_name_map[object_class][name] = (name, filter_by)
def _add_ca_value_dicts(self, values): """Add CA dict representations to _custom_attributes_values property. This adds or updates the _custom_attribute_values with the values in the custom attribute values serialized dictionary. Args: values: List of dictionaries that represent custom attribute values. """ from ggrc.models.custom_attribute_value import CustomAttributeValue for value in values: if not value.get("attribute_object_id"): # value.get("attribute_object", {}).get("id") won't help because # value["attribute_object"] can be None value["attribute_object_id"] = ( value["attribute_object"].get("id") if value.get("attribute_object") else None) attr = self._values_map.get(value.get("custom_attribute_id")) if attr: attr.attributable = self attr.attribute_value = value.get("attribute_value") attr.attribute_object_id = value.get("attribute_object_id") elif "custom_attribute_id" in value: # this is automatically appended to self._custom_attribute_values # on attributable=self CustomAttributeValue( attributable=self, custom_attribute_id=value.get("custom_attribute_id"), attribute_value=value.get("attribute_value"), attribute_object_id=value.get("attribute_object_id"), ) elif "href" in value: # Ignore setting of custom attribute stubs. Getting here means that the # front-end is not using the API correctly and needs to be updated. logger.info("Ignoring post/put of custom attribute stubs.") else: raise BadRequest("Bad custom attribute value inserted")
def custom_attributes(self, src): """Legacy setter for custom attribute values and definitions. This code should only be used for custom attribute definitions until setter for that is updated. """ # pylint: disable=too-many-locals from ggrc.models.custom_attribute_value import CustomAttributeValue ca_values = src.get("custom_attribute_values") if ca_values and "attribute_value" in ca_values[0]: # This indicates that the new CA API is being used and the legacy API # should be ignored. If we need to use the legacy API the # custom_attribute_values property should contain stubs instead of entire # objects. return definitions = src.get("custom_attribute_definitions") if definitions is not None: self.process_definitions(definitions) attributes = src.get("custom_attributes") if not attributes: return old_values = collections.defaultdict(list) # attributes looks like this: # [ {<id of attribute definition> : attribute value, ... }, ... ] # 1) Get all custom attribute values for the CustomAttributable instance attr_values = db.session.query(CustomAttributeValue).filter( and_( CustomAttributeValue.attributable_type == self.__class__.__name__, CustomAttributeValue.attributable_id == self.id)).all() # Save previous value of custom attribute. This is a bit complicated by # the fact that imports can save multiple values at the time of writing. # old_values holds all previous values of attribute, last_values holds # chronologically last value. for value in attr_values: old_values[value.custom_attribute_id].append( (value.created_at, value.attribute_value)) self._remove_existing_items(attr_values) # 4) Instantiate custom attribute values for each of the definitions # passed in (keys) # pylint: disable=not-an-iterable # filter out attributes like Person:None attributes = { k: v for k, v in attributes.items() if v != "Person:None" } definitions = { d.id: d for d in self.get_custom_attribute_definitions() } for ad_id in attributes.keys(): obj_type = self.__class__.__name__ obj_id = self.id new_value = CustomAttributeValue( custom_attribute_id=int(ad_id), attributable=self, attribute_value=attributes[ad_id], ) if definitions[int(ad_id)].attribute_type.startswith("Map:"): obj_type, obj_id = new_value.attribute_value.split(":") new_value.attribute_value = obj_type new_value.attribute_object_id = long(obj_id) elif definitions[int(ad_id)].attribute_type == "Checkbox": new_value.attribute_value = "1" if new_value.attribute_value else "0"
def _add_ca_value_dicts(self, values): """Add CA dict representations to _custom_attributes_values property. This adds or updates the _custom_attribute_values with the values in the custom attribute values serialized dictionary. Args: values: List of dictionaries that represent custom attribute values. """ from ggrc.utils import referenced_objects from ggrc.models.custom_attribute_value import CustomAttributeValue for value in values: # TODO: decompose to smaller methods # TODO: remove complicated nested conditions, better to use # instant exception raising if not value.get("attribute_object_id"): # value.get("attribute_object", {}).get("id") won't help because # value["attribute_object"] can be None value["attribute_object_id"] = ( value["attribute_object"].get("id") if value.get("attribute_object") else None) attr = self._values_map.get(value.get("custom_attribute_id")) if attr: attr.attributable = self attr.attribute_value = value.get("attribute_value") attr.attribute_object_id = value.get("attribute_object_id") elif "custom_attribute_id" in value: # this is automatically appended to self._custom_attribute_values # on attributable=self custom_attribute_id = value.get("custom_attribute_id") custom_attribute = referenced_objects.get( "CustomAttributeDefinition", custom_attribute_id) attribute_object = value.get("attribute_object") if attribute_object is None: CustomAttributeValue( attributable=self, custom_attribute=custom_attribute, custom_attribute_id=custom_attribute_id, attribute_value=value.get("attribute_value"), attribute_object_id=value.get("attribute_object_id"), ) elif isinstance(attribute_object, dict): attribute_object_type = attribute_object.get("type") attribute_object_id = attribute_object.get("id") attribute_object = referenced_objects.get( attribute_object_type, attribute_object_id) cav = CustomAttributeValue( attributable=self, custom_attribute=custom_attribute, custom_attribute_id=custom_attribute_id, attribute_value=value.get("attribute_value"), attribute_object_id=value.get("attribute_object_id"), ) cav.attribute_object = attribute_object else: raise BadRequest("Bad custom attribute value inserted") elif "href" in value: # Ignore setting of custom attribute stubs. Getting here means that the # front-end is not using the API correctly and needs to be updated. logger.info("Ignoring post/put of custom attribute stubs.") else: raise BadRequest("Bad custom attribute value inserted")
def custom_attributes(self, src): """Legacy setter for custom attribute values and definitions. This code should only be used for custom attribute definitions until setter for that is updated. """ from ggrc.models.custom_attribute_value import CustomAttributeValue from ggrc.services import signals ca_values = src.get("custom_attribute_values") if ca_values and "attribute_value" in ca_values[0]: # This indicates that the new CA API is being used and the legacy API # should be ignored. If we need to use the legacy API the # custom_attribute_values property should contain stubs instead of entire # objects. return definitions = src.get("custom_attribute_definitions") if definitions: self.process_definitions(definitions) attributes = src.get("custom_attributes") if not attributes: return old_values = collections.defaultdict(list) last_values = dict() # attributes looks like this: # [ {<id of attribute definition> : attribute value, ... }, ... ] # 1) Get all custom attribute values for the CustomAttributable instance attr_values = db.session.query(CustomAttributeValue).filter( and_( CustomAttributeValue.attributable_type == self.__class__.__name__, CustomAttributeValue.attributable_id == self.id)).all() # Save previous value of custom attribute. This is a bit complicated by # the fact that imports can save multiple values at the time of writing. # old_values holds all previous values of attribute, last_values holds # chronologically last value. for value in attr_values: old_values[value.custom_attribute_id].append( (value.created_at, value.attribute_value)) last_values = { str(key): max(old_vals, key=lambda (created_at, _): created_at) for key, old_vals in old_values.iteritems() } self._remove_existing_items(attr_values) # 4) Instantiate custom attribute values for each of the definitions # passed in (keys) # pylint: disable=not-an-iterable # filter out attributes like Person:None attributes = { k: v for k, v in attributes.items() if v != "Person:None" } definitions = { d.id: d for d in self.get_custom_attribute_definitions() } for ad_id in attributes.keys(): obj_type = self.__class__.__name__ obj_id = self.id new_value = CustomAttributeValue( custom_attribute_id=int(ad_id), attributable=self, attribute_value=attributes[ad_id], ) if definitions[int(ad_id)].attribute_type.startswith("Map:"): obj_type, obj_id = new_value.attribute_value.split(":") new_value.attribute_value = obj_type new_value.attribute_object_id = long(obj_id) elif definitions[int(ad_id)].attribute_type == "Checkbox": new_value.attribute_value = "1" if new_value.attribute_value else "0" # 5) Set the context_id for each custom attribute value to the context id # of the custom attributable. # TODO: We are ignoring contexts for now # new_value.context_id = cls.context_id # new value is appended to self.custom_attribute_values by the ORM # self.custom_attribute_values.append(new_value) if ad_id in last_values: _, previous_value = last_values[ad_id] if previous_value != attributes[ad_id]: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "UPDATE", "value": new_value, "old": previous_value }, service=self.__class__.__name__) else: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "INSERT", "value": new_value, }, service=self.__class__.__name__)
def custom_attributes(self, src): """Legacy setter for custom attribute values and definitions. This code should only be used for custom attribute definitions until setter for that is updated. """ # pylint: disable=too-many-locals from ggrc.models.custom_attribute_value import CustomAttributeValue ca_values = src.get("custom_attribute_values") if ca_values and "attribute_value" in ca_values[0]: # This indicates that the new CA API is being used and the legacy API # should be ignored. If we need to use the legacy API the # custom_attribute_values property should contain stubs instead of entire # objects. return definitions = src.get("custom_attribute_definitions") if definitions is not None: self.process_definitions(definitions) attributes = src.get("custom_attributes") if not attributes: return old_values = collections.defaultdict(list) # attributes looks like this: # [ {<id of attribute definition> : attribute value, ... }, ... ] # 1) Get all custom attribute values for the CustomAttributable instance attr_values = db.session.query(CustomAttributeValue).filter(and_( CustomAttributeValue.attributable_type == self.__class__.__name__, CustomAttributeValue.attributable_id == self.id)).all() # Save previous value of custom attribute. This is a bit complicated by # the fact that imports can save multiple values at the time of writing. # old_values holds all previous values of attribute, last_values holds # chronologically last value. for value in attr_values: old_values[value.custom_attribute_id].append( (value.created_at, value.attribute_value)) self._remove_existing_items(attr_values) # 4) Instantiate custom attribute values for each of the definitions # passed in (keys) # pylint: disable=not-an-iterable # filter out attributes like Person:None attributes = {k: v for k, v in attributes.items() if v != "Person:None"} definitions = {d.id: d for d in self.get_custom_attribute_definitions()} for ad_id in attributes.keys(): obj_type = self.__class__.__name__ obj_id = self.id new_value = CustomAttributeValue( custom_attribute_id=int(ad_id), attributable=self, attribute_value=attributes[ad_id], ) if definitions[int(ad_id)].attribute_type.startswith("Map:"): obj_type, obj_id = new_value.attribute_value.split(":") new_value.attribute_value = obj_type new_value.attribute_object_id = long(obj_id) elif definitions[int(ad_id)].attribute_type == "Checkbox": new_value.attribute_value = "1" if new_value.attribute_value else "0"
def _add_ca_value_dicts(self, values): """Add CA dict representations to _custom_attributes_values property. This adds or updates the _custom_attribute_values with the values in the custom attribute values serialized dictionary. Args: values: List of dictionaries that represent custom attribute values. """ from ggrc.utils import referenced_objects from ggrc.models.custom_attribute_value import CustomAttributeValue for value in values: if not value.get("attribute_object_id"): # value.get("attribute_object", {}).get("id") won't help because # value["attribute_object"] can be None value["attribute_object_id"] = (value["attribute_object"].get("id") if value.get("attribute_object") else None) attr = self._values_map.get(value.get("custom_attribute_id")) if attr: attr.attributable = self attr.attribute_value = value.get("attribute_value") attr.attribute_object_id = value.get("attribute_object_id") elif "custom_attribute_id" in value: # this is automatically appended to self._custom_attribute_values # on attributable=self custom_attribute_id = value.get("custom_attribute_id") custom_attribute = referenced_objects.get( "CustomAttributeDefinition", custom_attribute_id ) attribute_object = value.get("attribute_object") if attribute_object is None: CustomAttributeValue( attributable=self, custom_attribute=custom_attribute, custom_attribute_id=custom_attribute_id, attribute_value=value.get("attribute_value"), attribute_object_id=value.get("attribute_object_id"), ) elif isinstance(attribute_object, dict): attribute_object_type = attribute_object.get("type") attribute_object_id = attribute_object.get("id") attribute_object = referenced_objects.get( attribute_object_type, attribute_object_id ) cav = CustomAttributeValue( attributable=self, custom_attribute=custom_attribute, custom_attribute_id=custom_attribute_id, attribute_value=value.get("attribute_value") ) cav.attribute_object = attribute_object else: raise BadRequest("Bad custom attribute value inserted") elif "href" in value: # Ignore setting of custom attribute stubs. Getting here means that the # front-end is not using the API correctly and needs to be updated. logger.info("Ignoring post/put of custom attribute stubs.") else: raise BadRequest("Bad custom attribute value inserted")
def custom_attributes(self, src): """Legacy setter for custom attribute values and definitions. This code should only be used for custom attribute definitions until setter for that is updated. """ # pylint: disable=too-many-locals from ggrc.models.custom_attribute_value import CustomAttributeValue from ggrc.services import signals ca_values = src.get("custom_attribute_values") if ca_values and "attribute_value" in ca_values[0]: # This indicates that the new CA API is being used and the legacy API # should be ignored. If we need to use the legacy API the # custom_attribute_values property should contain stubs instead of entire # objects. return definitions = src.get("custom_attribute_definitions") if definitions is not None: self.process_definitions(definitions) attributes = src.get("custom_attributes") if not attributes: return old_values = collections.defaultdict(list) last_values = dict() # attributes looks like this: # [ {<id of attribute definition> : attribute value, ... }, ... ] # 1) Get all custom attribute values for the CustomAttributable instance attr_values = db.session.query(CustomAttributeValue).filter(and_( CustomAttributeValue.attributable_type == self.__class__.__name__, CustomAttributeValue.attributable_id == self.id)).all() # Save previous value of custom attribute. This is a bit complicated by # the fact that imports can save multiple values at the time of writing. # old_values holds all previous values of attribute, last_values holds # chronologically last value. for value in attr_values: old_values[value.custom_attribute_id].append( (value.created_at, value.attribute_value)) last_values = {str(key): max(old_vals, key=lambda (created_at, _): created_at) for key, old_vals in old_values.iteritems()} self._remove_existing_items(attr_values) # 4) Instantiate custom attribute values for each of the definitions # passed in (keys) # pylint: disable=not-an-iterable # filter out attributes like Person:None attributes = {k: v for k, v in attributes.items() if v != "Person:None"} definitions = {d.id: d for d in self.get_custom_attribute_definitions()} for ad_id in attributes.keys(): obj_type = self.__class__.__name__ obj_id = self.id new_value = CustomAttributeValue( custom_attribute_id=int(ad_id), attributable=self, attribute_value=attributes[ad_id], ) if definitions[int(ad_id)].attribute_type.startswith("Map:"): obj_type, obj_id = new_value.attribute_value.split(":") new_value.attribute_value = obj_type new_value.attribute_object_id = long(obj_id) elif definitions[int(ad_id)].attribute_type == "Checkbox": new_value.attribute_value = "1" if new_value.attribute_value else "0" # 5) Set the context_id for each custom attribute value to the context id # of the custom attributable. # TODO: We are ignoring contexts for now # new_value.context_id = cls.context_id # new value is appended to self.custom_attribute_values by the ORM # self.custom_attribute_values.append(new_value) if ad_id in last_values: _, previous_value = last_values[ad_id] if previous_value != attributes[ad_id]: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "UPDATE", "value": new_value, "old": previous_value }, service=self.__class__.__name__) else: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "INSERT", "value": new_value, }, service=self.__class__.__name__)
def custom_attributes(self, src): """Legacy setter for custom attribute values and definitions. This code should only be used for custom attribute definitions until setter for that is updated. """ from ggrc.fulltext.mysql import MysqlRecordProperty from ggrc.models.custom_attribute_value import CustomAttributeValue from ggrc.services import signals ca_values = src.get("custom_attribute_values") if ca_values and "attribute_value" in ca_values[0]: # This indicates that the new CA API is being used and the legacy API # should be ignored. If we need to use the legacy API the # custom_attribute_values property should contain stubs instead of entire # objects. return definitions = src.get("custom_attribute_definitions") if definitions: self.process_definitions(definitions) attributes = src.get("custom_attributes") if not attributes: return old_values = collections.defaultdict(list) last_values = dict() # attributes looks like this: # [ {<id of attribute definition> : attribute value, ... }, ... ] # 1) Get all custom attribute values for the CustomAttributable instance attr_values = db.session.query(CustomAttributeValue).filter(and_( CustomAttributeValue.attributable_type == self.__class__.__name__, CustomAttributeValue.attributable_id == self.id)).all() attr_value_ids = [value.id for value in attr_values] ftrp_properties = [ "attribute_value_{id}".format(id=_id) for _id in attr_value_ids] # Save previous value of custom attribute. This is a bit complicated by # the fact that imports can save multiple values at the time of writing. # old_values holds all previous values of attribute, last_values holds # chronologically last value. for value in attr_values: old_values[value.custom_attribute_id].append( (value.created_at, value.attribute_value)) last_values = {str(key): max(old_vals, key=lambda (created_at, _): created_at) for key, old_vals in old_values.iteritems()} # 2) Delete all fulltext_record_properties for the list of values if len(attr_value_ids) > 0: db.session.query(MysqlRecordProperty)\ .filter( and_( MysqlRecordProperty.type == self.__class__.__name__, MysqlRecordProperty.property.in_(ftrp_properties)))\ .delete(synchronize_session='fetch') # 3) Delete the list of custom attribute values db.session.query(CustomAttributeValue)\ .filter(CustomAttributeValue.id.in_(attr_value_ids))\ .delete(synchronize_session='fetch') db.session.commit() # 4) Instantiate custom attribute values for each of the definitions # passed in (keys) # pylint: disable=not-an-iterable definitions = {d.id: d for d in self.get_custom_attribute_definitions()} for ad_id in attributes.keys(): obj_type = self.__class__.__name__ obj_id = self.id new_value = CustomAttributeValue( custom_attribute_id=ad_id, attributable=self, attribute_value=attributes[ad_id], ) if definitions[int(ad_id)].attribute_type.startswith("Map:"): obj_type, obj_id = new_value.attribute_value.split(":") new_value.attribute_value = obj_type new_value.attribute_object_id = long(obj_id) # 5) Set the context_id for each custom attribute value to the context id # of the custom attributable. # TODO: We are ignoring contexts for now # new_value.context_id = cls.context_id self.custom_attribute_values.append(new_value) if ad_id in last_values: _, previous_value = last_values[ad_id] if previous_value != attributes[ad_id]: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "UPDATE", "value": new_value, "old": previous_value }, service=self.__class__.__name__) else: signals.Signals.custom_attribute_changed.send( self.__class__, obj=self, src={ "type": obj_type, "id": obj_id, "operation": "INSERT", "value": new_value, }, service=self.__class__.__name__)