Пример #1
0
class WithReadOnlyAccess(object):
  """Mixin for models which can be marked as read-only"""
  # pylint: disable=too-few-public-methods

  _read_only_model_relationships = (
      'Document'
  )

  readonly = db.Column(db.Boolean, nullable=False, default=False)

  _api_attrs = reflection.ApiAttributes(
      reflection.Attribute("readonly", create=True, update=True),
  )

  _aliases = {
      "readonly": {
          "display_name": "Read-only",
          "mandatory": False,
          "hidden": True,
      },
  }

  _fulltext_attrs = [
      attributes.BooleanFullTextAttr(
          'readonly',
          'readonly',
          true_value="yes",
          false_value="no",
      )
  ]

  def can_change_relationship_with(self, obj):
    """Check whether relationship from self to obj1 can be changed

    This function doesn't expect that another obj also has type
    WithReadOnlyAccess. In this case can_change_relationship_with() of
    another object have to be called also to ensure that relationship is
    not read-only. Final read-only flag can be calculated
    using the following expression:
      obj1.can_change_relationship_with(obj2) and \
      obj2.can_change_relationship_with(obj1)
    """

    if not self.readonly:
      return True

    return obj.__class__.__name__ not in self._read_only_model_relationships

  @validates('readonly')
  def validate_readonly(self, _, value):  # pylint: disable=no-self-use
    """Validate readonly"""
    if value is None:
      # if value is not specified or is set to None, use default value False
      return self.readonly

    if not isinstance(value, bool):
      raise ValidationError("Attribute 'readonly' has invalid value")

    return value
class CycleTaskGroupObjectTask(
        roleable.Roleable, wf_mixins.CycleTaskStatusValidatedMixin,
        wf_mixins.WorkflowCommentable, mixins.WithLastDeprecatedDate,
        mixins.Timeboxed, relationship.Relatable, mixins.Notifiable,
        mixins.Described, mixins.Titled, mixins.Slugged, mixins.Base,
        base.ContextRBAC, ft_mixin.Indexed, db.Model):
    """Cycle task model
  """
    __tablename__ = 'cycle_task_group_object_tasks'

    readable_name_alias = 'cycle task'

    _title_uniqueness = False

    IMPORTABLE_FIELDS = (
        'slug',
        'title',
        'description',
        'start_date',
        'end_date',
        'finished_date',
        'verified_date',
        'status',
        '__acl__:Task Assignees',
        '__acl__:Task Secondary Assignees',
    )

    @classmethod
    def generate_slug_prefix(cls):
        return "CYCLETASK"

    # Note: this statuses are used in utils/query_helpers to filter out the tasks
    # that should be visible on My Tasks pages.

    PROPERTY_TEMPLATE = u"task {}"

    _fulltext_attrs = [
        ft_attributes.DateFullTextAttr(
            "end_date",
            'end_date',
        ),
        ft_attributes.FullTextAttr("group title", 'cycle_task_group',
                                   ['title'], False),
        ft_attributes.FullTextAttr("object_approval",
                                   'object_approval',
                                   with_template=False),
        ft_attributes.FullTextAttr("cycle title", 'cycle', ['title'], False),
        ft_attributes.FullTextAttr("group assignee",
                                   lambda x: x.cycle_task_group.contact,
                                   ['email', 'name'], False),
        ft_attributes.FullTextAttr("cycle assignee", lambda x: x.cycle.contact,
                                   ['email', 'name'], False),
        ft_attributes.DateFullTextAttr(
            "group due date",
            lambda x: x.cycle_task_group.next_due_date,
            with_template=False),
        ft_attributes.DateFullTextAttr("cycle due date",
                                       lambda x: x.cycle.next_due_date,
                                       with_template=False),
        ft_attributes.MultipleSubpropertyFullTextAttr("comments",
                                                      "cycle_task_entries",
                                                      ["description"]),
        ft_attributes.BooleanFullTextAttr("needs verification",
                                          "is_verification_needed",
                                          with_template=False,
                                          true_value="Yes",
                                          false_value="No"),
        "folder",
    ]

    # The app should not pass to the json representation of
    # relationships to the internal models
    IGNORED_RELATED_TYPES = ["CalendarEvent"]

    _custom_publish = {
        "related_sources":
        lambda obj: [
            rel.log_json() for rel in obj.related_sources
            if rel.source_type not in obj.IGNORED_RELATED_TYPES
        ],
        "related_destinations":
        lambda obj: [
            rel.log_json() for rel in obj.related_destinations
            if rel.destination_type not in obj.IGNORED_RELATED_TYPES
        ]
    }

    AUTO_REINDEX_RULES = [
        ft_mixin.ReindexRule("CycleTaskEntry",
                             lambda x: x.cycle_task_group_object_task),
    ]

    cycle_id = db.Column(
        db.Integer,
        db.ForeignKey('cycles.id', ondelete="CASCADE"),
        nullable=False,
    )
    cycle_task_group_id = db.Column(
        db.Integer,
        db.ForeignKey('cycle_task_groups.id', ondelete="CASCADE"),
        nullable=False,
    )
    task_group_task_id = db.Column(db.Integer,
                                   db.ForeignKey('task_group_tasks.id'),
                                   nullable=True)
    task_group_task = db.relationship(
        "TaskGroupTask",
        foreign_keys="CycleTaskGroupObjectTask.task_group_task_id")
    task_type = db.Column(db.String(length=250), nullable=False)
    response_options = db.Column(types.JsonType(), nullable=False, default=[])
    selected_response_options = db.Column(types.JsonType(),
                                          nullable=False,
                                          default=[])

    sort_index = db.Column(db.String(length=250), default="", nullable=False)

    finished_date = db.Column(db.DateTime)
    verified_date = db.Column(db.DateTime)

    # This parameter is overridden by cycle task group backref, but is here to
    # ensure pylint does not complain
    _cycle_task_group = None

    @hybrid.hybrid_property
    def cycle_task_group(self):
        """Getter for cycle task group foreign key."""
        return self._cycle_task_group

    @cycle_task_group.setter
    def cycle_task_group(self, cycle_task_group):
        """Setter for cycle task group foreign key."""
        if not self._cycle_task_group and cycle_task_group:
            relationship.Relationship(source=cycle_task_group,
                                      destination=self)
        self._cycle_task_group = cycle_task_group

    @hybrid.hybrid_property
    def object_approval(self):
        return self.cycle.workflow.object_approval

    @object_approval.expression
    def object_approval(cls):  # pylint: disable=no-self-argument
        return sa.select([
            Workflow.object_approval,
        ]).where(
            sa.and_(
                (Cycle.id == cls.cycle_id),
                (Cycle.workflow_id == Workflow.id))).label('object_approval')

    @builder.simple_property
    def folder(self):
        """Simple property for cycle folder."""
        if self.cycle:
            return self.cycle.folder
        return ""

    @builder.simple_property
    def is_in_history(self):
        """Used on UI to disable editing finished CycleTask which is in history"""
        return not self.cycle.is_current

    @property
    def cycle_task_objects_for_cache(self):
        """Get all related objects for this CycleTaskGroupObjectTask

    Returns:
      List of tuples with (related_object_type, related_object_id)
    """
        return [(object_.__class__.__name__, object_.id)
                for object_ in self.related_objects()]

    _api_attrs = reflection.ApiAttributes(
        'cycle',
        'cycle_task_group',
        'task_group_task',
        'cycle_task_entries',
        'sort_index',
        'task_type',
        'response_options',
        'selected_response_options',
        reflection.Attribute('related_sources', create=False, update=False),
        reflection.Attribute('related_destinations',
                             create=False,
                             update=False),
        reflection.Attribute('object_approval', create=False, update=False),
        reflection.Attribute('finished_date', create=False, update=False),
        reflection.Attribute('verified_date', create=False, update=False),
        reflection.Attribute('allow_change_state', create=False, update=False),
        reflection.Attribute('folder', create=False, update=False),
        reflection.Attribute('workflow', create=False, update=False),
        reflection.Attribute('workflow_title', create=False, update=False),
        reflection.Attribute('cycle_task_group_title',
                             create=False,
                             update=False),
        reflection.Attribute('is_in_history', create=False, update=False),
    )

    default_description = "<ol>"\
                          + "<li>Expand the object review task.</li>"\
                          + "<li>Click on the Object to be reviewed.</li>"\
                          + "<li>Review the object in the Info tab.</li>"\
                          + "<li>Click \"Approve\" to approve the object.</li>"\
                          + "<li>Click \"Decline\" to decline the object.</li>"\
                          + "</ol>"

    _aliases = {
        "title": "Summary",
        "description": "Task Details",
        "finished_date": {
            "display_name":
            "Actual Finish Date",
            "description": ("Make sure that 'Actual Finish Date' isn't set, "
                            "if cycle task state is <'Assigned' / "
                            "'In Progress' / 'Declined' / 'Deprecated'>. "
                            "Type double dash '--' into "
                            "'Actual Finish Date' cell to remove it.")
        },
        "verified_date": {
            "display_name":
            "Actual Verified Date",
            "description": ("Make sure that 'Actual Verified Date' isn't set, "
                            "if cycle task state is <'Assigned' / "
                            "'In Progress' / 'Declined' / 'Deprecated' / "
                            "'Finished'>. Type double dash '--' into "
                            "'Actual Verified Date' cell to remove it.")
        },
        "cycle": {
            "display_name": "Cycle",
            "filter_by": "_filter_by_cycle",
        },
        "cycle_task_group": {
            "display_name": "Task Group",
            "mandatory": True,
            "filter_by": "_filter_by_cycle_task_group",
        },
        "task_type": {
            "display_name": "Task Type",
            "mandatory": True,
        },
        "end_date": "Due Date",
        "start_date": "Start Date",
    }

    @builder.simple_property
    def cycle_task_group_title(self):
        """Property. Returns parent CycleTaskGroup title."""
        return self.cycle_task_group.title

    @builder.simple_property
    def workflow_title(self):
        """Property. Returns parent Workflow's title."""
        return self.workflow.title

    @builder.simple_property
    def workflow(self):
        """Property which returns parent workflow object."""
        return self.cycle.workflow

    @builder.simple_property
    def allow_change_state(self):
        return self.cycle.is_current and self.current_user_wfa_or_assignee()

    def current_user_wfa_or_assignee(self):
        """Current user is WF Admin, Assignee or Secondary Assignee for self."""
        wfa_ids = self.workflow.get_person_ids_for_rolename("Admin")
        ta_ids = self.get_person_ids_for_rolename("Task Assignees")
        tsa_ids = self.get_person_ids_for_rolename("Task Secondary Assignees")
        return login.get_current_user_id() in set().union(
            wfa_ids, ta_ids, tsa_ids)

    @classmethod
    def _filter_by_cycle(cls, predicate):
        """Get query that filters cycle tasks by related cycles.

    Args:
      predicate: lambda function that excepts a single parameter and returns
      true of false.

    Returns:
      An sqlalchemy query that evaluates to true or false and can be used in
      filtering cycle tasks by related cycles.
    """
        return Cycle.query.filter((Cycle.id == cls.cycle_id)
                                  & (predicate(Cycle.slug)
                                     | predicate(Cycle.title))).exists()

    @classmethod
    def _filter_by_cycle_task_group(cls, predicate):
        """Get query that filters cycle tasks by related cycle task groups.

    Args:
      predicate: lambda function that excepts a single parameter and returns
      true of false.

    Returns:
      An sqlalchemy query that evaluates to true or false and can be used in
      filtering cycle tasks by related cycle task groups.
    """
        return CycleTaskGroup.query.filter(
            (CycleTaskGroup.id == cls.cycle_id)
            & (predicate(CycleTaskGroup.slug)
               | predicate(CycleTaskGroup.title))).exists()

    @classmethod
    def eager_query(cls):
        """Add cycle task entries to cycle task eager query

    This function adds cycle_task_entries as a join option when fetching cycles
    tasks, and makes sure that with one query we fetch all cycle task related
    data needed for generating cycle taks json for a response.

    Returns:
      a query object with cycle_task_entries added to joined load options.
    """
        query = super(CycleTaskGroupObjectTask, cls).eager_query()
        return query.options(
            orm.subqueryload('cycle_task_entries'),
            orm.joinedload('cycle').undefer_group('Cycle_complete'),
            orm.joinedload('cycle').joinedload('workflow').undefer_group(
                'Workflow_complete'),
            orm.joinedload('cycle').joinedload('workflow').joinedload(
                '_access_control_list'),
            orm.joinedload('cycle_task_group').undefer_group(
                'CycleTaskGroup_complete'),
        )

    @classmethod
    def indexed_query(cls):
        return super(CycleTaskGroupObjectTask, cls).indexed_query().options(
            orm.Load(cls).load_only("end_date", "start_date", "created_at",
                                    "updated_at"),
            orm.Load(cls).joinedload("cycle_task_group").load_only(
                "id",
                "title",
                "end_date",
                "next_due_date",
            ),
            orm.Load(cls).joinedload("cycle").load_only(
                "id",
                "title",
                "next_due_date",
                "is_verification_needed",
            ),
            orm.Load(cls).joinedload("cycle_task_group").joinedload(
                "contact").load_only("email", "name", "id"),
            orm.Load(cls).joinedload("cycle").joinedload("contact").load_only(
                "email", "name", "id"),
            orm.Load(cls).subqueryload("cycle_task_entries").load_only(
                "description", "id"),
            orm.Load(cls).joinedload("cycle").joinedload(
                "workflow").undefer_group("Workflow_complete"),
        )

    def log_json(self):
        out_json = super(CycleTaskGroupObjectTask, self).log_json()
        out_json["folder"] = self.folder
        return out_json

    @classmethod
    def bulk_update(cls, src):
        """Update statuses for bunch of tasks in a bulk.

    Args:
        src: input json with next structure:
          [{"status": "Assigned", "id": 1}, {"status": "In Progress", "id": 2}]

    Returns:
        list of updated_instances
    """
        new_prv_state_map = {
            cls.DEPRECATED: (cls.ASSIGNED, cls.IN_PROGRESS, cls.FINISHED,
                             cls.VERIFIED, cls.DECLINED),
            cls.IN_PROGRESS: (cls.ASSIGNED, ),
            cls.FINISHED: (cls.IN_PROGRESS, cls.DECLINED),
            cls.VERIFIED: (cls.FINISHED, ),
            cls.DECLINED: (cls.FINISHED, ),
            cls.ASSIGNED: (),
        }
        uniq_states = set([item['state'] for item in src])
        if len(list(uniq_states)) != 1:
            raise BadRequest("Request's JSON contains multiple statuses for "
                             "CycleTasks")
        new_state = uniq_states.pop()
        LOGGER.info("Do bulk update CycleTasks with '%s' status", new_state)
        if new_state not in cls.VALID_STATES:
            raise BadRequest("Request's JSON contains invalid statuses for "
                             "CycleTasks")
        prv_states = new_prv_state_map[new_state]
        all_ids = {item['id'] for item in src}
        # Eagerly loading is needed to get user permissions for CycleTask faster
        updatable_objects = cls.eager_query().filter(
            cls.id.in_(list(all_ids)), cls.status.in_(prv_states))
        if new_state in (cls.VERIFIED, cls.DECLINED):
            updatable_objects = [
                obj for obj in updatable_objects
                if obj.cycle.is_verification_needed
            ]
        # Bulk update works only on MyTasks page. Don't need to check for
        # WorkflowMembers' permissions here. User should update only his own tasks.
        updatable_objects = [
            obj for obj in updatable_objects
            if obj.current_user_wfa_or_assignee()
        ]
        # Queries count is constant because we are using eager query for objects.
        for obj in updatable_objects:
            obj.status = new_state
            obj.modified_by_id = login.get_current_user_id()
        return updatable_objects
Пример #3
0
class Control(WithLastAssessmentDate, HasObjectState, Roleable, Relatable,
              CustomAttributable, Personable, ControlCategorized,
              PublicDocumentable, AssertionCategorized, Hierarchical,
              LastDeprecatedTimeboxed, Auditable, TestPlanned, BusinessObject,
              Indexed, db.Model):
    __tablename__ = 'controls'

    company_control = deferred(db.Column(db.Boolean), 'Control')
    directive_id = deferred(
        db.Column(db.Integer, db.ForeignKey('directives.id')), 'Control')
    kind_id = deferred(db.Column(db.Integer), 'Control')
    means_id = deferred(db.Column(db.Integer), 'Control')
    version = deferred(db.Column(db.String), 'Control')
    documentation_description = deferred(db.Column(db.Text), 'Control')
    verify_frequency_id = deferred(db.Column(db.Integer), 'Control')
    fraud_related = deferred(db.Column(db.Boolean), 'Control')
    key_control = deferred(db.Column(db.Boolean), 'Control')
    active = deferred(db.Column(db.Boolean), 'Control')
    principal_assessor_id = deferred(
        db.Column(db.Integer, db.ForeignKey('people.id')), 'Control')
    secondary_assessor_id = deferred(
        db.Column(db.Integer, db.ForeignKey('people.id')), 'Control')

    principal_assessor = db.relationship(
        'Person', uselist=False, foreign_keys='Control.principal_assessor_id')
    secondary_assessor = db.relationship(
        'Person', uselist=False, foreign_keys='Control.secondary_assessor_id')

    kind = db.relationship(
        'Option',
        primaryjoin='and_(foreign(Control.kind_id) == Option.id, '
        'Option.role == "control_kind")',
        uselist=False)
    means = db.relationship(
        'Option',
        primaryjoin='and_(foreign(Control.means_id) == Option.id, '
        'Option.role == "control_means")',
        uselist=False)
    verify_frequency = db.relationship(
        'Option',
        primaryjoin='and_(foreign(Control.verify_frequency_id) == Option.id, '
        'Option.role == "verify_frequency")',
        uselist=False)

    @staticmethod
    def _extra_table_args(_):
        return (
            db.Index('ix_controls_principal_assessor',
                     'principal_assessor_id'),
            db.Index('ix_controls_secondary_assessor',
                     'secondary_assessor_id'),
        )

    # REST properties
    _api_attrs = reflection.ApiAttributes(
        'active',
        'company_control',
        'directive',
        'documentation_description',
        'fraud_related',
        'key_control',
        'kind',
        'means',
        'verify_frequency',
        'version',
        'principal_assessor',
        'secondary_assessor',
    )

    _fulltext_attrs = [
        'active',
        'company_control',
        'directive',
        'documentation_description',
        attributes.BooleanFullTextAttr('fraud_related',
                                       'fraud_related',
                                       true_value="yes",
                                       false_value="no"),
        attributes.BooleanFullTextAttr('key_control',
                                       'key_control',
                                       true_value="key",
                                       false_value="non-key"),
        'kind',
        'means',
        'verify_frequency',
        'version',
        attributes.FullTextAttr("principal_assessor", "principal_assessor",
                                ["name", "email"]),
        attributes.FullTextAttr('secondary_assessor', 'secondary_assessor',
                                ["name", "email"]),
    ]

    _sanitize_html = [
        'documentation_description',
        'version',
    ]

    @classmethod
    def indexed_query(cls):
        return super(Control, cls).indexed_query().options(
            orm.Load(cls).undefer_group("Control_complete"),
            orm.Load(cls).joinedload("directive").undefer_group(
                "Directive_complete"),
            orm.Load(cls).joinedload("principal_assessor").undefer_group(
                "Person_complete"),
            orm.Load(cls).joinedload("secondary_assessor").undefer_group(
                "Person_complete"),
            orm.Load(cls).joinedload(
                'kind', ).undefer_group("Option_complete"),
            orm.Load(cls).joinedload(
                'means', ).undefer_group("Option_complete"),
            orm.Load(cls).joinedload(
                'verify_frequency', ).undefer_group("Option_complete"),
        )

    _include_links = []

    _aliases = {
        "kind": "Kind/Nature",
        "means": "Type/Means",
        "verify_frequency": "Frequency",
        "fraud_related": "Fraud Related",
        "key_control": {
            "display_name": "Significance",
            "description": "Allowed values are:\nkey\nnon-key\n---",
        },
        # overrides values from PublicDocumentable mixin
        "document_url": None,
        "test_plan": "Assessment Procedure",
    }

    @validates('kind', 'means', 'verify_frequency')
    def validate_control_options(self, key, option):
        desired_role = key if key == 'verify_frequency' else 'control_' + key
        return validate_option(self.__class__.__name__, key, option,
                               desired_role)

    @classmethod
    def eager_query(cls):
        query = super(Control, cls).eager_query()
        return cls.eager_inclusions(query, Control._include_links).options(
            orm.joinedload('directive'),
            orm.joinedload('principal_assessor'),
            orm.joinedload('secondary_assessor'),
            orm.joinedload('kind'),
            orm.joinedload('means'),
            orm.joinedload('verify_frequency'),
        )

    def log_json(self):
        out_json = super(Control, self).log_json()
        # so that event log can refer to deleted directive
        if self.directive:
            out_json["mapped_directive"] = self.directive.display_name
        return out_json
Пример #4
0
class Control(synchronizable.Synchronizable, categorizable.Categorizable,
              WithLastAssessmentDate, synchronizable.RoleableSynchronizable,
              Relatable, mixins.CustomAttributable, Personable,
              PublicDocumentable, mixins.LastDeprecatedTimeboxed,
              mixins.TestPlanned, comment.ExternalCommentable,
              WithSimilarityScore, base.ContextRBAC, mixins.BusinessObject,
              Indexed, mixins.Folderable, proposal.Proposalable, db.Model):
    """Control model definition."""
    __tablename__ = 'controls'

    company_control = deferred(db.Column(db.Boolean), 'Control')
    directive_id = deferred(
        db.Column(db.Integer, db.ForeignKey('directives.id')), 'Control')
    version = deferred(db.Column(db.String), 'Control')
    fraud_related = deferred(db.Column(db.Boolean), 'Control')
    key_control = deferred(db.Column(db.Boolean), 'Control')
    active = deferred(db.Column(db.Boolean), 'Control')
    kind = deferred(db.Column(db.String), "Control")
    means = deferred(db.Column(db.String), "Control")
    verify_frequency = deferred(db.Column(db.String), "Control")
    review_status = deferred(db.Column(db.String, nullable=True), "Control")
    review_status_display_name = deferred(db.Column(db.String, nullable=True),
                                          "Control")

    # GGRCQ attributes
    due_date = db.Column(db.Date, nullable=True)
    created_by_id = db.Column(db.Integer, nullable=False)

    # 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)

    # pylint: disable=no-self-argument
    @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)

    # pylint: disable=no-self-argument
    @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")

    _title_uniqueness = False

    _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,
    }

    # REST properties
    _api_attrs = reflection.ApiAttributes(
        'active',
        'company_control',
        'directive',
        'fraud_related',
        'key_control',
        'kind',
        'means',
        'verify_frequency',
        'version',
        'review_status',
        'review_status_display_name',
        'due_date',
        reflection.ExternalUserAttribute('created_by', force_create=True),
        'last_submitted_at',
        reflection.ExternalUserAttribute('last_submitted_by',
                                         force_create=True),
        'last_verified_at',
        reflection.ExternalUserAttribute('last_verified_by',
                                         force_create=True),
    )

    _fulltext_attrs = [
        'active',
        'company_control',
        'directive',
        attributes.BooleanFullTextAttr('fraud_related',
                                       'fraud_related',
                                       true_value="yes",
                                       false_value="no"),
        attributes.BooleanFullTextAttr('key_control',
                                       'key_control',
                                       true_value="key",
                                       false_value="non-key"),
        'kind',
        'means',
        'verify_frequency',
        'version',
        'review_status_display_name',
    ]

    _sanitize_html = [
        'version',
    ]

    @classmethod
    def indexed_query(cls):
        return super(Control, cls).indexed_query().options(
            orm.Load(cls).undefer_group("Control_complete"),
            orm.Load(cls).joinedload("directive").undefer_group(
                "Directive_complete"),
        )

    _include_links = []

    _aliases = {
        "kind": "Kind/Nature",
        "means": "Type/Means",
        "verify_frequency": "Frequency",
        "fraud_related": "Fraud Related",
        "key_control": {
            "display_name": "Significance",
            "description": "Allowed values are:\nkey\nnon-key\n---",
        },
        "test_plan": "Assessment Procedure",
        "review_status": {
            "display_name": "Review State",
            "mandatory": False,
            "filter_only": True
        },
        "review_status_display_name": {
            "display_name": "Review Status",
            "mandatory": False
        },
    }

    @classmethod
    def eager_query(cls, **kwargs):
        query = super(Control, cls).eager_query(**kwargs)
        return cls.eager_inclusions(query, Control._include_links).options(
            orm.joinedload('directive'), )

    def log_json(self):
        out_json = super(Control, self).log_json()
        out_json["created_by"] = ggrc_utils.created_by_stub(self)
        out_json["last_submitted_by"] = ggrc_utils.last_submitted_by_stub(self)
        out_json["last_verified_by"] = ggrc_utils.last_verified_by_stub(self)
        # so that event log can refer to deleted directive
        if self.directive:
            out_json["mapped_directive"] = self.directive.display_name
        return out_json

    @validates('review_status')
    def validate_review_status(self, _, value):  # pylint: disable=no-self-use
        """Add explicit non-nullable validation."""
        if value is None:
            raise ValidationError(
                "review_status for the object is not specified")

        return value

    # pylint: disable=invalid-name
    @validates('review_status_display_name')
    def validate_review_status_display_name(self, _, value):
        """Add explicit non-nullable validation."""
        # pylint: disable=no-self-use,invalid-name

        if value is None:
            raise ValidationError(
                "review_status_display_name for the object is not specified")

        return value
Пример #5
0
class Control(WithLastAssessmentDate,
              HasObjectState,
              Roleable,
              Relatable,
              mixins.CustomAttributable,
              Personable,
              ControlCategorized,
              PublicDocumentable,
              AssertionCategorized,
              mixins.LastDeprecatedTimeboxed,
              mixins.TestPlanned,
              Commentable,
              WithSimilarityScore,
              base.ContextRBAC,
              mixins.BusinessObject,
              Indexed,
              mixins.Folderable,
              proposal.Proposalable,
              db.Model):
  """Control model definition."""
  __tablename__ = 'controls'

  company_control = deferred(db.Column(db.Boolean), 'Control')
  directive_id = deferred(
      db.Column(db.Integer, db.ForeignKey('directives.id')), 'Control')
  kind_id = deferred(db.Column(db.Integer), 'Control')
  means_id = deferred(db.Column(db.Integer), 'Control')
  version = deferred(db.Column(db.String), 'Control')
  verify_frequency_id = deferred(db.Column(db.Integer), 'Control')
  fraud_related = deferred(db.Column(db.Boolean), 'Control')
  key_control = deferred(db.Column(db.Boolean), 'Control')
  active = deferred(db.Column(db.Boolean), 'Control')

  kind = db.relationship(
      'Option',
      primaryjoin='and_(foreign(Control.kind_id) == Option.id, '
                  'Option.role == "control_kind")',
      uselist=False)
  means = db.relationship(
      'Option',
      primaryjoin='and_(foreign(Control.means_id) == Option.id, '
                  'Option.role == "control_means")',
      uselist=False)
  verify_frequency = db.relationship(
      'Option',
      primaryjoin='and_(foreign(Control.verify_frequency_id) == Option.id, '
                  'Option.role == "verify_frequency")',
      uselist=False)

  # REST properties
  _api_attrs = reflection.ApiAttributes(
      'active',
      'company_control',
      'directive',
      'fraud_related',
      'key_control',
      'kind',
      'means',
      'verify_frequency',
      'version',
  )

  _fulltext_attrs = [
      'active',
      'company_control',
      'directive',
      attributes.BooleanFullTextAttr(
          'fraud_related',
          'fraud_related',
          true_value="yes", false_value="no"),
      attributes.BooleanFullTextAttr(
          'key_control',
          'key_control',
          true_value="key", false_value="non-key"),
      'kind',
      'means',
      'verify_frequency',
      'version',
  ]

  _sanitize_html = [
      'version',
  ]

  @classmethod
  def indexed_query(cls):
    return super(Control, cls).indexed_query().options(
        orm.Load(cls).undefer_group(
            "Control_complete"
        ),
        orm.Load(cls).joinedload(
            "directive"
        ).undefer_group(
            "Directive_complete"
        ),
        orm.Load(cls).joinedload(
            'kind',
        ).undefer_group(
            "Option_complete"
        ),
        orm.Load(cls).joinedload(
            'means',
        ).undefer_group(
            "Option_complete"
        ),
        orm.Load(cls).joinedload(
            'verify_frequency',
        ).undefer_group(
            "Option_complete"
        ),
    )

  _include_links = []

  _aliases = {
      "kind": "Kind/Nature",
      "means": "Type/Means",
      "verify_frequency": "Frequency",
      "fraud_related": "Fraud Related",
      "key_control": {
          "display_name": "Significance",
          "description": "Allowed values are:\nkey\nnon-key\n---",
      },
      "test_plan": "Assessment Procedure",
  }

  @validates('kind', 'means', 'verify_frequency')
  def validate_control_options(self, key, option):
    """Validate control 'kind', 'means', 'verify_frequency'"""
    desired_role = key if key == 'verify_frequency' else 'control_' + key
    return validate_option(self.__class__.__name__, key, option, desired_role)

  @classmethod
  def eager_query(cls):
    query = super(Control, cls).eager_query()
    return cls.eager_inclusions(query, Control._include_links).options(
        orm.joinedload('directive'),
        orm.joinedload('kind'),
        orm.joinedload('means'),
        orm.joinedload('verify_frequency'),
    )

  def log_json(self):
    out_json = super(Control, self).log_json()
    # so that event log can refer to deleted directive
    if self.directive:
      out_json["mapped_directive"] = self.directive.display_name
    return out_json