Beispiel #1
0
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
Beispiel #2
0
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()
Beispiel #3
0
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()