class WithLastAssessmentDate(attributable.Attributable): """Defines logic to get max finished_date of all Asmts over own Snapshots.""" # pylint: disable=too-few-public-methods _api_attrs = reflection.ApiAttributes( reflection.Attribute("last_assessment_date", create=False, update=False), ) _aliases = { "last_assessment_date": { "display_name": "Last Assessment Date", "view_only": True, }, } _fulltext_attrs = [ attributes.DatetimeFullTextAttr("last_assessment_date", "last_assessment_date") ] @simple_property def last_assessment_date(self): lad_attr = self.attributes.get("last_assessment_date") return lad_attr.value_datetime if lad_attr else None
class VerifiedDate(object): """Adds 'Verified Date' which is set when status is set to 'Verified'. When object is verified the status is overridden to 'Final' and the information about verification exposed as the 'verified' boolean. Requires Stateful to be mixed in as well. """ VERIFIED_STATES = {u"Verified"} DONE_STATES = {} # pylint: disable=method-hidden # because validator only sets date per model instance @declared_attr def verified_date(cls): # pylint: disable=no-self-argument return deferred(db.Column(db.DateTime, nullable=True), cls.__name__) @hybrid_property def verified(self): return self.verified_date != None # noqa _api_attrs = reflection.ApiAttributes( reflection.Attribute('verified', create=False, update=False), reflection.Attribute('verified_date', create=False, update=False), ) _aliases = { "verified_date": { "display_name": "Verified Date", "description": "Automatically provided values", } } _fulltext_attrs = [ attributes.DatetimeFullTextAttr("verified_date", "verified_date"), "verified", ] @classmethod def indexed_query(cls): return super(VerifiedDate, cls).indexed_query().options( orm.Load(cls).load_only("verified_date"), ) @validates('status') def validate_status(self, key, value): """Update verified_date on status change, make verified status final.""" # Sqlalchemy only uses one validator per status (not necessarily the # first) and ignores others. This enables cooperation between validators # since 'status' is not defined here. if hasattr(super(VerifiedDate, self), "validate_status"): value = super(VerifiedDate, self).validate_status(key, value) if (value in self.VERIFIED_STATES and self.status not in self.VERIFIED_STATES): self.verified_date = datetime.datetime.utcnow() value = self.FINAL_STATE elif (value not in self.END_STATES and (self.status in self.VERIFIED_STATES or self.status in self.DONE_STATES)): self.verified_date = None return value
class FinishedDate(object): """Adds 'Finished Date' which is set when status is set to a finished state. Requires Stateful to be mixed in as well. """ NOT_DONE_STATES = None DONE_STATES = {} # pylint: disable=method-hidden # because validator only sets date per model instance @declared_attr def finished_date(cls): # pylint: disable=no-self-argument return deferred( db.Column(db.DateTime, nullable=True), cls.__name__ ) _api_attrs = reflection.ApiAttributes( reflection.Attribute('finished_date', create=False, update=False), ) _aliases = { "finished_date": "Finished Date" } _fulltext_attrs = [ attributes.DatetimeFullTextAttr('finished_date', 'finished_date'), ] @validates('status') def validate_status(self, key, value): """Update finished_date given the right status change.""" # Sqlalchemy only uses one validator per status (not necessarily the # first) and ignores others. This enables cooperation between validators # since 'status' is not defined here. if hasattr(super(FinishedDate, self), "validate_status"): value = super(FinishedDate, self).validate_status(key, value) # pylint: disable=unsupported-membership-test # short circuit if (value in self.DONE_STATES and (self.NOT_DONE_STATES is None or self.status in self.NOT_DONE_STATES)): self.finished_date = datetime.datetime.now() elif ((self.NOT_DONE_STATES is None or value in self.NOT_DONE_STATES) and self.status in self.DONE_STATES): self.finished_date = None return value @classmethod def indexed_query(cls): return super(FinishedDate, cls).indexed_query().options( orm.Load(cls).load_only("finished_date"), )
class VerifiedDate(object): """Adds 'Verified Date' which is set when status is set to 'Verified'. When object is verified the status is overridden to 'Final' and the information about verification exposed as the 'verified' boolean. Requires Stateful to be mixed in as well. """ VERIFIED_STATES = {u"Verified"} DONE_STATES = {} # pylint: disable=method-hidden # because validator only sets date per model instance @declared_attr def verified_date(cls): return deferred(db.Column(db.DateTime, nullable=True), cls.__name__) @hybrid_property def verified(self): return self.verified_date != None # noqa _publish_attrs = [ reflection.PublishOnly('verified'), reflection.PublishOnly('verified_date'), ] _aliases = {"verified_date": "Verified Date"} _fulltext_attrs = [ attributes.DatetimeFullTextAttr("verified_date", "verified_date"), "verified", ] @validates('status') def validate_status(self, key, value): """Update verified_date on status change, make verified status final.""" # Sqlalchemy only uses one validator per status (not necessarily the # first) and ignores others. This enables cooperation between validators # since 'status' is not defined here. if hasattr(super(VerifiedDate, self), "validate_status"): value = super(VerifiedDate, self).validate_status(key, value) if (value in self.VERIFIED_STATES and self.status not in self.VERIFIED_STATES): self.verified_date = datetime.datetime.now() value = self.FINAL_STATE elif (value not in self.VERIFIED_STATES and value not in self.DONE_STATES and (self.status in self.VERIFIED_STATES or self.status in self.DONE_STATES)): self.verified_date = None return value
class CreationTimeTracked(object): """ Mixing for created_at column support. `created_at` column will keep track of db record creation time. """ @declared_attr def created_at(self): """ Date of creation. Set to current time on object creation. """ column = db.Column( db.DateTime, nullable=False, default=lambda: datetime.utcnow().replace(microsecond=0).isoformat(), ) return column _api_attrs = reflection.ApiAttributes( reflection.Attribute('created_at', create=False, update=False), ) _fulltext_attrs = [ attributes.DatetimeFullTextAttr('created_at', 'created_at'), ] _filterable_attrs = [ "created_at", ] _aliases = { "created_at": { "display_name": "Created Date", "mandatory": False, "description": "Automatically provided values" }, } @classmethod def indexed_query(cls): return super(CreationTimeTracked, cls).indexed_query().options( orm.Load(cls).load_only("created_at"), )
class ChangeTracked(object): """A model with fields to tracked the last user to modify the model, the creation time of the model, and the last time the model was updated. """ @declared_attr def modified_by_id(cls): # pylint: disable=no-self-argument """Id of user who did the last modification of the object.""" return db.Column(db.Integer) @declared_attr def created_at(cls): # pylint: disable=no-self-argument """Date of creation. Set to current time on object creation.""" column = db.Column( db.DateTime, nullable=False, default=db.text('current_timestamp'), ) return column @declared_attr def updated_at(cls): # pylint: disable=no-self-argument """Date of last update. Set to current time on object creation/update.""" column = db.Column( db.DateTime, nullable=False, default=db.text('current_timestamp'), onupdate=db.text('current_timestamp'), ) return column @declared_attr def modified_by(cls): # pylint: disable=no-self-argument """Relationship to user referenced by modified_by_id.""" return db.relationship( 'Person', primaryjoin='{0}.modified_by_id == Person.id'.format(cls.__name__), foreign_keys='{0}.modified_by_id'.format(cls.__name__), uselist=False, ) @staticmethod def _extra_table_args(model): """Apply extra table args (like indexes) to model definition.""" return ( db.Index('ix_{}_updated_at'.format(model.__tablename__), 'updated_at'), ) # TODO Add a transaction id, this will be handy for generating etags # and for tracking the changes made to several resources together. # transaction_id = db.Column(db.Integer) # REST properties _api_attrs = reflection.ApiAttributes( reflection.Attribute('modified_by', create=False, update=False), reflection.Attribute('created_at', create=False, update=False), reflection.Attribute('updated_at', create=False, update=False), ) _fulltext_attrs = [ attributes.DatetimeFullTextAttr('created_at', 'created_at'), attributes.DatetimeFullTextAttr('updated_at', 'updated_at'), attributes.FullTextAttr( "modified_by", "modified_by", ["email", "name"] ), ] _aliases = { "updated_at": "Last Updated", "created_at": "Created Date", "modified_by": "Last Updated By", } @classmethod def indexed_query(cls): return super(ChangeTracked, cls).indexed_query().options( orm.Load(cls).load_only("created_at", "updated_at"), orm.Load(cls).joinedload( "modified_by" ).load_only( "name", "email", "id" ), )
class ChangeTracked(object): """A model with fields to tracked the last user to modify the model, the creation time of the model, and the last time the model was updated. """ @declared_attr def modified_by_id(cls): """Id of user who did the last modification of the object.""" return deferred(db.Column(db.Integer), cls.__name__) @declared_attr def created_at(cls): """Date of creation. Set to current time on object creation.""" column = db.Column( db.DateTime, nullable=False, default=db.text('current_timestamp'), ) return deferred(column, cls.__name__) @declared_attr def updated_at(cls): """Date of last update. Set to current time on object creation/update.""" column = db.Column( db.DateTime, nullable=False, default=db.text('current_timestamp'), onupdate=db.text('current_timestamp'), ) return deferred(column, cls.__name__) @declared_attr def modified_by(cls): """Relationship to user referenced by modified_by_id.""" return db.relationship( 'Person', primaryjoin='{0}.modified_by_id == Person.id'.format(cls.__name__), foreign_keys='{0}.modified_by_id'.format(cls.__name__), uselist=False, ) @staticmethod def _extra_table_args(model): """Apply extra table args (like indexes) to model definition.""" return (db.Index('ix_{}_updated_at'.format(model.__tablename__), 'updated_at'), ) # TODO Add a transaction id, this will be handy for generating etags # and for tracking the changes made to several resources together. # transaction_id = db.Column(db.Integer) # REST properties _publish_attrs = [ 'modified_by', 'created_at', 'updated_at', ] _fulltext_attrs = [ attributes.DatetimeFullTextAttr('created_at', 'created_at'), attributes.DatetimeFullTextAttr('updated_at', 'updated_at'), attributes.FullTextAttr("modified_by", "modified_by", ["name", "email"]), ] _update_attrs = [] _aliases = { "updated_at": { "display_name": "Last Updated", "filter_only": True, }, "created_at": { "display_name": "Created Date", "filter_only": True, }, }
class Risk(synchronizable.Synchronizable, synchronizable.RoleableSynchronizable, mixins.ExternalCustomAttributable, Relatable, PublicDocumentable, comment.ExternalCommentable, mixins.TestPlanned, mixins.LastDeprecatedTimeboxed, mixins.base.ContextRBAC, mixins.BusinessObject, mixins.Folderable, Indexed, db.Model): """Basic Risk model.""" __tablename__ = 'risks' # GGRCQ attributes external_id = db.Column(db.Integer, nullable=False) due_date = db.Column(db.Date, nullable=True) created_by_id = db.Column(db.Integer, nullable=False) review_status = deferred(db.Column(db.String, nullable=True), "Risk") review_status_display_name = deferred(db.Column(db.String, nullable=True), "Risk") # pylint: disable=no-self-argument @declared_attr def created_by(cls): """Relationship to user referenced by created_by_id.""" return utils.person_relationship(cls.__name__, "created_by_id") last_submitted_at = db.Column(db.DateTime, nullable=True) last_submitted_by_id = db.Column(db.Integer, nullable=True) @declared_attr def last_submitted_by(cls): """Relationship to user referenced by last_submitted_by_id.""" return utils.person_relationship(cls.__name__, "last_submitted_by_id") last_verified_at = db.Column(db.DateTime, nullable=True) last_verified_by_id = db.Column(db.Integer, nullable=True) @declared_attr def last_verified_by(cls): """Relationship to user referenced by last_verified_by_id.""" return utils.person_relationship(cls.__name__, "last_verified_by_id") # Overriding mixin to make mandatory @declared_attr def description(cls): # pylint: disable=no-self-argument return deferred(db.Column(db.Text, nullable=False, default=u""), cls.__name__) risk_type = db.Column(db.Text, nullable=True) threat_source = db.Column(db.Text, nullable=True) threat_event = db.Column(db.Text, nullable=True) vulnerability = db.Column(db.Text, nullable=True) @validates('review_status') def validate_review_status(self, _, value): """Add explicit non-nullable validation.""" # pylint: disable=no-self-use if value is None: raise exceptions.ValidationError( "Review status for the object is not specified") return value @validates('review_status_display_name') def validate_review_status_display_name(self, _, value): """Add explicit non-nullable validation.""" # pylint: disable=no-self-use # pylint: disable=invalid-name if value is None: raise exceptions.ValidationError( "Review status display for the object is not specified") return value _sanitize_html = [ 'risk_type', 'threat_source', 'threat_event', 'vulnerability' ] _fulltext_attrs = [ 'risk_type', 'threat_source', 'threat_event', 'vulnerability', 'review_status_display_name', attributes.DateFullTextAttr('due_date', 'due_date'), attributes.DatetimeFullTextAttr('last_submitted_at', 'last_submitted_at'), attributes.DatetimeFullTextAttr('last_verified_at', 'last_verified_at'), attributes.FullTextAttr("created_by", "created_by", ["email", "name"]), attributes.FullTextAttr("last_submitted_by", "last_submitted_by", ["email", "name"]), attributes.FullTextAttr("last_verified_by", "last_verified_by", ["email", "name"]) ] _custom_publish = { 'created_by': ggrc_utils.created_by_stub, 'last_submitted_by': ggrc_utils.last_submitted_by_stub, 'last_verified_by': ggrc_utils.last_verified_by_stub, } _api_attrs = reflection.ApiAttributes( 'risk_type', 'threat_source', 'threat_event', 'vulnerability', 'external_id', 'due_date', reflection.ExternalUserAttribute('created_by', force_create=True), reflection.ExternalUserAttribute('last_submitted_by', force_create=True), reflection.ExternalUserAttribute('last_verified_by', force_create=True), 'last_submitted_at', 'last_verified_at', 'external_slug', 'review_status', 'review_status_display_name', ) _aliases = { "description": { "display_name": "Description", "mandatory": True }, "risk_type": { "display_name": "Risk Type", "mandatory": False }, "threat_source": { "display_name": "Threat Source", "mandatory": False }, "threat_event": { "display_name": "Threat Event", "mandatory": False }, "vulnerability": { "display_name": "Vulnerability", "mandatory": False }, "documents_file": None, "status": { "display_name": "State", "mandatory": False, "description": "Options are: \n {}".format('\n'.join( mixins.BusinessObject.VALID_STATES)) }, "review_status": { "display_name": "Review State", "mandatory": False, "filter_only": True, }, "review_status_display_name": { "display_name": "Review Status", "mandatory": False, }, "due_date": { "display_name": "Due Date", "mandatory": False, }, "created_by": { "display_name": "Created By", "mandatory": False, }, "last_submitted_at": { "display_name": "Last Owner Reviewed Date", "mandatory": False, }, "last_submitted_by": { "display_name": "Last Owner Reviewed By", "mandatory": False, }, "last_verified_at": { "display_name": "Last Compliance Reviewed Date", "mandatory": False, }, "last_verified_by": { "display_name": "Last Compliance Reviewed By", "mandatory": False, }, } def log_json(self): res = super(Risk, self).log_json() res["created_by"] = ggrc_utils.created_by_stub(self) res["last_submitted_by"] = ggrc_utils.last_submitted_by_stub(self) res["last_verified_by"] = ggrc_utils.last_verified_by_stub(self) return res