def _object_changed(self, action, ob, date_active=None, note=None, procedure="a", ): """() -> domain.Change date_active: the UI for some changes allow the user to manually set the date_active -- this is what should be used as the *effective* date i.e. the date to be used for all intents and purposes other than for data auditing. When not user-modified, the value should be equal to date_audit. """ domain.assert_valid_change_action(action) user = utils.get_login_user() assert user is not None, "Audit error. No user logged in." # carry over a snapshot of head values def get_field_names_to_audit(kls): names_to_audit = [] table = self.audit_table for column in table.columns: # skip all fields starting with "audit_" if column.name.startswith("audit_"): continue # ok, column must therefore be a proper attribute from ob's class assert column.name in kls.__dict__, \ "Not in class: %s" % column.name # skip all primary keys (audit_head_id managed separately) if column.primary_key: continue names_to_audit.append(column.name) for vp_name, vp_type in kls.extended_properties: names_to_audit.append(vp_name) return names_to_audit def copy_field_values(head_cls, source, dest): for name in get_field_names_to_audit(head_cls): setattr(dest, name, getattr(source, name)) # ensure real head object, in case we are dealing with reversioning # off an Audit instance head_ob = ob if action == "version" and issubclass(ob.__class__, domain.Audit): # ob is an Audit instance, so need its head_ob head_ob = ob.audit_head # audit snapshot - done first, to ensure a valid audit_id... au = self.audit_class() au.audit_head = head_ob # attach audit log item to parent object copy_field_values(head_ob.__class__, ob, au) session = Session() session.add(au) # change/audit record # !+version Version instances are created as Change instances! ch = domain.Change() ch.seq = 0 # !+ reset below, to avoid sqlalchemy violates not-null constraint ch.audit = au # ensures ch.audit_id, ch.note.object_id ch.user_id = user.user_id ch.action = action ch.seq = self._get_seq(ch) # !+translate_seq(mr, feb-2013) this should be divided by language? # !+translate_action(mr, feb-2013) shoudl there be an action per # language e.g. translate-fr for translations to french (resolves the # noted translate_seq issue) ch.procedure = procedure ch.date_audit = datetime.now() if date_active: ch.date_active = date_active else: ch.date_active = ch.date_audit if note: ch.note = note # !+SUBITEM_CHANGES_PERMISSIONS(mr, jan-2012) permission on change # records for something like item[@draft]->file[@attached]->fileversion # need to remember also the root item's state, else when item later # becomes visible to clerk and others, the file itself will also become # visible to the clerk (CORRECT) but so will ALL changes on the file # while that file itself was @attached (WRONG!). May best be addressed # when change persistence is reworked along with single document table. session.flush() log.debug("AUDIT [%s] %s" % (au, au.__dict__)) log.debug("CHANGE [%s] %s" % (action, ch.__dict__)) return ch
def _object_changed( self, action, ob, date_active=None, note=None, procedure="a", ): """ date_active: the UI for some changes allow the user to manually set the date_active -- this is what should be used as the *effective* date i.e. the date to be used for all intents and purposes other than for data auditing. When not user-modified, the value should be equal to date_audit. """ domain.assert_valid_change_action(action) user_id = get_db_user_id() assert user_id is not None, "Audit error. No user logged in." # carry over a snapshot of head values def get_field_names_to_audit(kls): names_to_audit = [] table = self.audit_table for column in table.columns: # skip all fields starting with "audit_" if column.name.startswith("audit_"): continue # ok, column must therefore be a proper attribute from ob's class assert column.name in kls.__dict__, \ "Not in class: %s" % column.name # skip all primary keys (audit_head_id managed separately) if column.primary_key: continue names_to_audit.append(column.name) for vp_name, vp_type in kls.extended_properties: names_to_audit.append(vp_name) return names_to_audit def copy_field_values(head_cls, source, dest): for name in get_field_names_to_audit(head_cls): setattr(dest, name, getattr(source, name)) # ensure real head object, in case we are dealing with reversioning # off an Audit instance head_ob = ob if action == "version" and issubclass(ob.__class__, domain.Audit): # ob is an Audit instance, so need its head_ob head_ob = ob.audit_head # audit snapshot - done first, to ensure a valid audit_id... au = self.audit_class() au.audit_head = head_ob # attach audit log item to parent object copy_field_values(head_ob.__class__, ob, au) session = Session() session.add(au) # change/audit record # !+version Version instances are created as Change instances! ch = domain.Change() ch.seq = 0 # !+ reset below, to avoid sqlalchemy violates not-null constraint ch.audit = au # ensures ch.audit_id, ch.note.object_id ch.user_id = user_id ch.action = action ch.seq = self._get_seq(ch) ch.procedure = procedure ch.date_audit = datetime.now() if date_active: ch.date_active = date_active else: ch.date_active = ch.date_audit if note: ch.note = note # !+SUBITEM_CHANGES_PERMISSIONS(mr, jan-2012) permission on change # records for something like item[@draft]->file[@attached]->fileversion # need to remember also the root item's state, else when item later # becomes visible to clerk and others, the file itself will also become # visible to the clerk (CORRECT) but so will ALL changes on the file # while that file itself was @attached (WRONG!). May best be addressed # when change persistence is reworked along with single document table. session.flush() log.debug("AUDIT [%s] %s" % (au, au.__dict__)) log.debug("CHANGE [%s] %s" % (action, ch.__dict__)) return ch
def _object_changed(self, action, ob, description="", extras=None, date_active=None): """ description: this is a non-localized string as base description of the log item, offers a (building block) for the description of this log item. UI components may use this in any of the following ways: - AS IS, optionally localized - as a building block for an elaborated description e.g. for generating descriptions that are hyperlinks to an event or version objects - ignore it entirely, and generate a custom description via other means e.g. from the "notes" extras dict. extras: !+CHANGE_EXTRAS(mr, dec-2010) a python dict, containing "extra" information about the log item, with the "key/value" entries depending on the change "action"; Specific examples, for actions: workflow: self.object_workflow() source destination transition comment version: self.object_version() version_id modify: self.object_modify() comment For now, this dict is serialized (using repr(), values are assumed to be simple strings or numbers) as the value of the notes column, for storing in the db--but if and when the big picture of these extra keys is understood clearly then the changes table may be remodeled with dedicated table columns. date_active: the UI for some changes allow the user to manually set the date_active -- this is what should be used as the *effective* date i.e. the date to be used for all intents and purposes other than for data auditing. When not user-modified, the value should be equal to date_audit. """ domain.assert_valid_change_action(action) oid, otype = self._getKey(ob) user_id = get_db_user_id() assert user_id is not None, "Audit error. No user logged in." session = Session() change = self.change_class() change.action = action change.date_audit = datetime.now() if date_active: change.date_active = date_active else: change.date_active = change.date_audit change.user_id = user_id change.description = description change.extras = extras change.content_type = otype change.head = ob # attach change to parent object change.status = ob.status # remember parent's status at time of change # !+SUBITEM_CHANGES_PERMISSIONS(mr, jan-2012) permission on change # records for something like item[@draft]->file[@attached]->fileversion # need to remember also the root item's state, else when item later # becomes visible to clerk and others, the file itself will also become # visible to the clerk (CORRECT) but so will ALL changes on the file # while that file itself was @attached (WRONG!). May best be addressed # when change persistence is reworked along with single document table. session.add(change) session.flush() log.debug("AUDITED [%s] %s" % (action, change.__dict__)) return change.change_id