class Review(mixins.person_relation_factory("last_reviewed_by"), mixins.person_relation_factory("created_by"), mixins.datetime_mixin_factory("last_reviewed_at"), mixins.Stateful, rest_handable.WithPostHandable, rest_handable.WithPutHandable, rest_handable.WithPostAfterCommitHandable, rest_handable.WithPutAfterCommitHandable, with_comment_created.WithCommentCreated, comment.CommentInitiator, roleable.Roleable, issue_tracker.IssueTracked, relationship.Relatable, mixins.base.ContextRBAC, mixins.Base, db.Model): """Review object""" # pylint: disable=too-few-public-methods __tablename__ = "reviews" REVIEWER_ROLE_NAME = "Reviewer" class STATES(object): """Review states container """ REVIEWED = "Reviewed" UNREVIEWED = "Unreviewed" VALID_STATES = [STATES.UNREVIEWED, STATES.REVIEWED] class NotificationTypes(object): """Notification types container """ EMAIL_TYPE = "email" ISSUE_TRACKER = "issue_tracker" class NotificationObjectTypes(object): """Review Notification Object types container """ STATUS_UNREVIEWED = "review_status_unreviewed" REVIEW_CREATED = "review_request_created" reviewable_id = db.Column(db.Integer, nullable=False) reviewable_type = db.Column(db.String, nullable=False) REVIEWABLE_TMPL = "{}_reviewable" reviewable = model_utils.json_polymorphic_relationship_factory( Reviewable )( "reviewable_id", "reviewable_type", REVIEWABLE_TMPL ) notification_type = db.Column( sa.types.Enum(NotificationTypes.EMAIL_TYPE, NotificationTypes.ISSUE_TRACKER), nullable=False, ) email_message = db.Column(db.Text, nullable=False, default=u"") _api_attrs = reflection.ApiAttributes( "notification_type", "email_message", reflection.Attribute("reviewable", update=False), reflection.Attribute("last_reviewed_by", create=False, update=False), reflection.Attribute("last_reviewed_at", create=False, update=False), "issuetracker_issue", "status", ) def validate_acl(self): """Reviewer is mandatory Role""" super(Review, self).validate_acl() review_global_roles = role.get_ac_roles_data_for("Review").values() mandatory_role_ids = {acr[0] for acr in review_global_roles if acr[3]} passed_acr_ids = {acl.ac_role_id for _, acl in self.access_control_list} missed_mandatory_roles = mandatory_role_ids - passed_acr_ids if missed_mandatory_roles: raise exceptions.ValidationError("{} roles are mandatory".format( ",".join(missed_mandatory_roles)) ) def _add_comment_about(self, text): """Create comment about proposal for reason with required text.""" if not isinstance(self.reviewable, comment.Commentable): return text = self.clear_text(text) # pylint: disable=not-an-iterable existing_people = set(acp.person.email for acl in self._access_control_list for acp in acl.access_control_people) comment_text = ( u"<p>Review requested from</p><p>{people}</p>" u"<p>with a comment: {text}</p>" ).format( people=', '.join(existing_people), text=text, ) self.add_comment( comment_text, source=self.reviewable, initiator_object=self ) def handle_post(self): """Handle POST request.""" if self.email_message: self._add_comment_about(self.email_message) self._create_relationship() self._update_new_reviewed_by() if (self.notification_type == Review.NotificationTypes.EMAIL_TYPE and self.status == Review.STATES.UNREVIEWED and not isinstance(self.reviewable, synchronizable.Synchronizable)): add_notification(self, Review.NotificationObjectTypes.REVIEW_CREATED) def is_status_changed(self): """Checks whether the status has changed.""" return inspect(self).attrs.status.history.has_changes() def handle_put(self): """Handle PUT request.""" if not self.is_status_changed() and self.email_message: self._add_comment_about(self.email_message) self._update_reviewed_by() def handle_posted_after_commit(self, event): """Handle POST after commit.""" self.apply_mentions_comment(obj=self.reviewable, event=event) def handle_put_after_commit(self, event): """Handle PUT after commit.""" self.apply_mentions_comment(obj=self.reviewable, event=event) def _create_relationship(self): """Create relationship for newly created review (used for ACL)""" if self in db.session.new: db.session.add( relationship.Relationship(source=self.reviewable, destination=self) ) def _update_new_reviewed_by(self): """When create new review with state REVIEWED set last_reviewed_by""" # pylint: disable=attribute-defined-outside-init if self.status == Review.STATES.REVIEWED: self.last_reviewed_by = get_current_user() self.last_reviewed_at = datetime.datetime.utcnow() def _update_reviewed_by(self): """Update last_reviewed_by, last_reviewed_at""" # pylint: disable=attribute-defined-outside-init if not db.inspect(self).attrs["status"].history.has_changes(): return self.reviewable.updated_at = datetime.datetime.utcnow() if self.status == Review.STATES.REVIEWED: self.last_reviewed_by = self.modified_by self.last_reviewed_at = datetime.datetime.utcnow() # pylint: disable=no-self-use @validates("reviewable_type") def validate_reviewable_type(self, _, reviewable_type): """Validate reviewable_type attribute. We preventing creation of reviews for external models. """ reviewable_class = inflector.get_model(reviewable_type) if issubclass(reviewable_class, synchronizable.Synchronizable): raise ValueError("Trying to create review for external model.") return reviewable_type
class Review(mixins.person_relation_factory("last_reviewed_by"), mixins.person_relation_factory("created_by"), mixins.datetime_mixin_factory("last_reviewed_at"), mixins.Stateful, rest_handable.WithPostHandable, rest_handable.WithPutHandable, roleable.Roleable, issue_tracker.IssueTracked, Relatable, mixins.base.ContextRBAC, mixins.Base, db.Model): """Review object""" # pylint: disable=too-few-public-methods __tablename__ = "reviews" REVIEWER_ROLE_NAME = "Reviewer" class STATES(object): """Review states container """ REVIEWED = "Reviewed" UNREVIEWED = "Unreviewed" VALID_STATES = [STATES.UNREVIEWED, STATES.REVIEWED] class NotificationTypes(object): """Notification types container """ EMAIL_TYPE = "email" ISSUE_TRACKER = "issue_tracker" class NotificationObjectTypes(object): """Review Notification Object types container """ STATUS_UNREVIEWED = "review_status_unreviewed" REVIEW_CREATED = "review_request_created" reviewable_id = db.Column(db.Integer, nullable=False) reviewable_type = db.Column(db.String, nullable=False) REVIEWABLE_TMPL = "{}_reviewable" reviewable = model_utils.json_polymorphic_relationship_factory(Reviewable)( "reviewable_id", "reviewable_type", REVIEWABLE_TMPL) notification_type = db.Column( sa.types.Enum(NotificationTypes.EMAIL_TYPE, NotificationTypes.ISSUE_TRACKER), nullable=False, ) email_message = db.Column(db.Text, nullable=False, default=u"") _api_attrs = reflection.ApiAttributes( "notification_type", "email_message", reflection.Attribute("reviewable", update=False), reflection.Attribute("last_reviewed_by", create=False, update=False), reflection.Attribute("last_reviewed_at", create=False, update=False), "issuetracker_issue", "status", ) def validate_acl(self): """Reviewer is mandatory Role""" super(Review, self).validate_acl() review_global_roles = role.get_ac_roles_data_for("Review").values() mandatory_role_ids = {acr[0] for acr in review_global_roles if acr[3]} passed_acr_ids = { acl.ac_role_id for _, acl in self.access_control_list } missed_mandatory_roles = mandatory_role_ids - passed_acr_ids if missed_mandatory_roles: raise exceptions.ValidationError("{} roles are mandatory".format( ",".join(missed_mandatory_roles))) def handle_post(self): self._create_relationship() self._update_new_reviewed_by() if (self.notification_type == Review.NotificationTypes.EMAIL_TYPE and self.status == Review.STATES.UNREVIEWED): add_notification(self, Review.NotificationObjectTypes.REVIEW_CREATED) def handle_put(self): self._update_reviewed_by() def _create_relationship(self): """Create relationship for newly created review (used for ACL)""" from ggrc.models import all_models if self in db.session.new: db.session.add( all_models.Relationship(source=self.reviewable, destination=self)) def _update_new_reviewed_by(self): """When create new review with state REVIEWED set last_reviewed_by""" # pylint: disable=attribute-defined-outside-init from ggrc.models import all_models if self.status == all_models.Review.STATES.REVIEWED: self.last_reviewed_by = get_current_user() self.last_reviewed_at = datetime.datetime.utcnow() def _update_reviewed_by(self): """Update last_reviewed_by, last_reviewed_at""" # pylint: disable=attribute-defined-outside-init from ggrc.models import all_models if not db.inspect(self).attrs["status"].history.has_changes(): return self.reviewable.updated_at = datetime.datetime.utcnow() if self.status == all_models.Review.STATES.REVIEWED: self.last_reviewed_by = self.modified_by self.last_reviewed_at = datetime.datetime.utcnow()
class Review(mixins.person_relation_factory("last_reviewed_by"), mixins.person_relation_factory("created_by"), mixins.datetime_mixin_factory("last_reviewed_at"), mixins.Stateful, rest_handable.WithPostHandable, rest_handable.WithPutHandable, roleable.Roleable, issue_tracker.IssueTracked, Relatable, mixins.base.ContextRBAC, mixins.Base, ft_mixin.Indexed, db.Model): """Review object""" # pylint: disable=too-few-public-methods __tablename__ = "reviews" def __init__(self, *args, **kwargs): super(Review, self).__init__(*args, **kwargs) self.last_reviewed_by = None self.last_reviewed_at = None class STATES(object): """Review states container """ REVIEWED = "Reviewed" UNREVIEWED = "Unreviewed" VALID_STATES = [STATES.UNREVIEWED, STATES.REVIEWED] class ACRoles(object): """ACR roles container """ REVIEWER = "Reviewer" REVIEWABLE_READER = "Reviewable Reader" REVIEW_EDITOR = "Review Editor" class NotificationTypes(object): """Notification types container """ EMAIL_TYPE = "email" ISSUE_TRACKER = "issue_tracker" reviewable_id = db.Column(db.Integer, nullable=False) reviewable_type = db.Column(db.String, nullable=False) REVIEWABLE_TMPL = "{}_reviewable" reviewable = model_utils.json_polymorphic_relationship_factory(Reviewable)( "reviewable_id", "reviewable_type", REVIEWABLE_TMPL) notification_type = db.Column( sa.types.Enum(NotificationTypes.EMAIL_TYPE, NotificationTypes.ISSUE_TRACKER), nullable=False, ) email_message = db.Column(db.Text, nullable=False, default=u"") _api_attrs = reflection.ApiAttributes( "notification_type", "email_message", reflection.Attribute("reviewable", update=False), reflection.Attribute("last_reviewed_by", create=False, update=False), reflection.Attribute("last_reviewed_at", create=False, update=False), "issuetracker_issue", "status", ) _fulltext_attrs = [ "reviewable_id", "reviewable_type", ] def handle_post(self): self._create_relationship() self._update_new_reviewed_by() def handle_put(self): self._update_reviewed_by() def _create_relationship(self): """Create relationship for newly created review (used for ACL)""" from ggrc.models import all_models if self in db.session.new: db.session.add( all_models.Relationship(source=self.reviewable, destination=self)) def _update_new_reviewed_by(self): """When create new review with state REVIEWED set last_reviewed_by""" from ggrc.models import all_models if self.status == all_models.Review.STATES.REVIEWED: self.last_reviewed_by = get_current_user() self.last_reviewed_at = datetime.datetime.utcnow() def _update_reviewed_by(self): """Update last_reviewed_by, last_reviewed_at""" # pylint: disable=attribute-defined-outside-init from ggrc.models import all_models if not db.inspect(self).attrs["status"].history.has_changes(): return self.reviewable.updated_at = datetime.datetime.utcnow() if self.status == all_models.Review.STATES.REVIEWED: self.last_reviewed_by = self.modified_by self.last_reviewed_at = datetime.datetime.utcnow()