Exemple #1
0
class CycleTaskGroup(mixins.WithContact,
                     mixins.Stateful,
                     mixins.Slugged,
                     mixins.Timeboxed,
                     mixins.Described,
                     mixins.Titled,
                     mixins.Base,
                     index_mixin.Indexed,
                     db.Model):
  """Cycle Task Group model.
  """
  __tablename__ = 'cycle_task_groups'
  _title_uniqueness = False

  @classmethod
  def generate_slug_prefix_for(cls, obj):  # pylint: disable=unused-argument
    return "CYCLEGROUP"

  VALID_STATES = (
      u'Assigned', u'InProgress', u'Finished', u'Verified', u'Declined')

  cycle_id = db.Column(
      db.Integer,
      db.ForeignKey('cycles.id', ondelete="CASCADE"),
      nullable=False,
  )
  task_group_id = db.Column(
      db.Integer, db.ForeignKey('task_groups.id'), nullable=True)
  cycle_task_group_tasks = db.relationship(
      'CycleTaskGroupObjectTask',
      backref='cycle_task_group',
      cascade='all, delete-orphan'
  )
  sort_index = db.Column(
      db.String(length=250), default="", nullable=False)
  next_due_date = db.Column(db.Date)

  _publish_attrs = [
      'cycle',
      'task_group',
      'cycle_task_group_tasks',
      'sort_index',
      'next_due_date'
  ]

  _aliases = {
      "cycle": {
          "display_name": "Cycle",
          "filter_by": "_filter_by_cycle",
      },
  }

  PROPERTY_TEMPLATE = u"group {}"

  _fulltext_attrs = [
      attributes.MultipleSubpropertyFullTextAttr(
          "task title", 'cycle_task_group_tasks', ["title"], False
      ),
      attributes.MultipleSubpropertyFullTextAttr(
          "task assignee",
          lambda instance: [t.contact for t in
                            instance.cycle_task_group_tasks],
          ["name", "email"],
          False
      ),
      attributes.DateMultipleSubpropertyFullTextAttr(
          "task due date", "cycle_task_group_tasks", ["end_date"], False
      ),
      attributes.DateFullTextAttr("due date", 'next_due_date',),
      attributes.FullTextAttr("assignee", "contact", ['name', 'email']),
      attributes.FullTextAttr("cycle title", 'cycle', ['title'], False),
      attributes.FullTextAttr("cycle assignee",
                              lambda x: x.cycle.contact,
                              ['email', 'name'],
                              False),
      attributes.DateFullTextAttr("cycle due date",
                                  lambda x: x.cycle.next_due_date,
                                  with_template=False),
      attributes.MultipleSubpropertyFullTextAttr(
          "task comments",
          lambda instance: itertools.chain(*[
              t.cycle_task_entries for t in instance.cycle_task_group_tasks
          ]),
          ["description"],
          False
      ),
  ]

  AUTO_REINDEX_RULES = [
      index_mixin.ReindexRule(
          "CycleTaskGroupObjectTask", lambda x: x.cycle_task_group
      ),
      index_mixin.ReindexRule(
          "Person", _query_filtered_by_contact
      ),
      index_mixin.ReindexRule(
          "Person",
          lambda x: [i.cycle for i in _query_filtered_by_contact(x)]
      ),
  ]

  @classmethod
  def _filter_by_cycle(cls, predicate):
    """Get query that filters 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 task groups by related cycle.
    """
    return Cycle.query.filter(
        (Cycle.id == cls.cycle_id) &
        (predicate(Cycle.slug) | predicate(Cycle.title))
    ).exists()

  @classmethod
  def indexed_query(cls):
    return super(CycleTaskGroup, cls).indexed_query().options(
        orm.Load(cls).load_only(
            "next_due_date",
        ),
        orm.Load(cls).subqueryload("cycle_task_group_tasks").load_only(
            "id",
            "title",
            "end_date"
        ),
        orm.Load(cls).joinedload("cycle").load_only(
            "id",
            "title",
            "next_due_date"
        ),
        orm.Load(cls).subqueryload("cycle_task_group_tasks").joinedload(
            "contact"
        ).load_only(
            "email",
            "name",
            "id"
        ),
        orm.Load(cls).subqueryload("cycle_task_group_tasks").joinedload(
            "cycle_task_entries"
        ).load_only(
            "description",
            "id"
        ),
        orm.Load(cls).joinedload("cycle").joinedload(
            "contact"
        ).load_only(
            "email",
            "name",
            "id"
        ),
        orm.Load(cls).joinedload("contact").load_only(
            "email",
            "name",
            "id"
        ),
    )

  @classmethod
  def eager_query(cls):
    """Add cycle tasks and objects to cycle task group eager query.

    Make sure we load all cycle task group relevant data in a single query.

    Returns:
      a query object with cycle_task_group_tasks added to joined load options.
    """
    query = super(CycleTaskGroup, cls).eager_query()
    return query.options(
        orm.joinedload('cycle_task_group_tasks')
    )
Exemple #2
0
class CycleTaskGroup(roleable.Roleable, relationship.Relatable,
                     mixins.WithContact,
                     wf_mixins.CycleTaskGroupRelatedStatusValidatedMixin,
                     mixins.Slugged, mixins.Timeboxed, mixins.Described,
                     mixins.Titled, base.ContextRBAC, mixins.Base,
                     index_mixin.Indexed, db.Model):
    """Cycle Task Group model.
  """
    __tablename__ = 'cycle_task_groups'
    _title_uniqueness = False

    @classmethod
    def generate_slug_prefix(cls):  # pylint: disable=unused-argument
        return "CYCLEGROUP"

    cycle_id = db.Column(
        db.Integer,
        db.ForeignKey('cycles.id', ondelete="CASCADE"),
        nullable=False,
    )
    task_group_id = db.Column(db.Integer,
                              db.ForeignKey('task_groups.id'),
                              nullable=True)
    cycle_task_group_tasks = db.relationship('CycleTaskGroupObjectTask',
                                             backref='_cycle_task_group',
                                             cascade='all, delete-orphan')
    sort_index = db.Column(db.String(length=250), default="", nullable=False)
    next_due_date = db.Column(db.Date)

    _api_attrs = reflection.ApiAttributes('cycle', 'task_group',
                                          'cycle_task_group_tasks',
                                          'sort_index', 'next_due_date')

    _aliases = {
        "cycle": {
            "display_name": "Cycle",
            "filter_by": "_filter_by_cycle",
        },
    }

    PROPERTY_TEMPLATE = u"group {}"

    _fulltext_attrs = [
        attributes.DateFullTextAttr(
            "due date",
            'next_due_date',
        ),
        attributes.FullTextAttr("assignee", "contact", ['email', 'name']),
        attributes.FullTextAttr("cycle title", 'cycle', ['title'], False),
        attributes.FullTextAttr("cycle assignee", lambda x: x.cycle.contact,
                                ['email', 'name'], False),
        attributes.DateFullTextAttr("cycle due date",
                                    lambda x: x.cycle.next_due_date,
                                    with_template=False),
        attributes.MultipleSubpropertyFullTextAttr("task title",
                                                   "cycle_task_group_tasks",
                                                   ["title"], False),
        attributes.MultipleSubpropertyFullTextAttr("task assignees",
                                                   "_task_assignees",
                                                   ["email", "name"], False),
        attributes.MultipleSubpropertyFullTextAttr("task state",
                                                   "cycle_task_group_tasks",
                                                   ["status"], False),
        attributes.MultipleSubpropertyFullTextAttr(
            "task secondary assignees", "_task_secondary_assignees",
            ["email", "name"], False),
        attributes.DateMultipleSubpropertyFullTextAttr(
            "task due date", "cycle_task_group_tasks", ["end_date"], False),
        attributes.MultipleSubpropertyFullTextAttr(
            "task comment", lambda instance: itertools.chain(
                *[t.comments for t in instance.cycle_task_group_tasks]),
            ["description"], False),
    ]

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

    @hybrid.hybrid_property
    def cycle(self):
        """Getter for cycle foreign key."""
        return self._cycle

    @cycle.setter
    def cycle(self, cycle):
        """Set cycle foreign key and relationship."""
        if not self._cycle and cycle:
            relationship.Relationship(source=cycle, destination=self)
        self._cycle = cycle

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

    @property
    def _task_assignees(self):
        """Property. Return the list of persons as assignee of related tasks."""
        people = set()
        for ctask in self.cycle_task_group_tasks:
            people.update(ctask.get_persons_for_rolename("Task Assignees"))
        return list(people)

    @property
    def _task_secondary_assignees(self):
        """Property. Returns people list as Secondary Assignee of related tasks."""
        people = set()
        for ctask in self.cycle_task_group_tasks:
            people.update(
                ctask.get_persons_for_rolename("Task Secondary Assignees"))
        return list(people)

    AUTO_REINDEX_RULES = [
        index_mixin.ReindexRule("CycleTaskGroupObjectTask",
                                lambda x: x.cycle_task_group),
        index_mixin.ReindexRule("Person", _query_filtered_by_contact),
        index_mixin.ReindexRule(
            "Person",
            lambda x: [i.cycle for i in _query_filtered_by_contact(x)]),
    ]

    @classmethod
    def _filter_by_cycle(cls, predicate):
        """Get query that filters 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 task groups by related cycle.
    """
        return Cycle.query.filter((Cycle.id == cls.cycle_id)
                                  & (predicate(Cycle.slug)
                                     | predicate(Cycle.title))).exists()

    @classmethod
    def indexed_query(cls):
        return super(CycleTaskGroup, cls).indexed_query().options(
            orm.Load(cls).load_only("next_due_date", ),
            orm.Load(cls).subqueryload("cycle_task_group_tasks").load_only(
                "id",
                "title",
                "end_date",
                "status",
            ),
            orm.Load(cls).subqueryload("cycle_task_group_tasks").subqueryload(
                "_access_control_list", ).load_only(
                    "ac_role_id", ).subqueryload(
                        "access_control_people", ).load_only("person_id", ),
            orm.Load(cls).subqueryload("cycle_task_group_tasks"),
            orm.Load(cls).joinedload("cycle").load_only(
                "id",
                "title",
                "next_due_date",
            ),
            orm.Load(cls).joinedload("cycle").joinedload("contact").load_only(
                "name",
                "email",
                "id",
            ),
            orm.Load(cls).joinedload("contact").load_only(
                "name",
                "email",
                "id",
            ),
        )

    @classmethod
    def eager_query(cls):
        """Add cycle tasks and objects to cycle task group eager query.

    Make sure we load all cycle task group relevant data in a single query.

    Returns:
      a query object with cycle_task_group_tasks added to joined load options.
    """
        query = super(CycleTaskGroup, cls).eager_query()
        return query.options(
            orm.subqueryload("cycle_task_group_tasks"),
            orm.joinedload("cycle").undefer_group("Cycle_complete"),
            orm.joinedload("cycle").joinedload("contact"))
Exemple #3
0
class Cycle(roleable.Roleable, relationship.Relatable, mixins.WithContact,
            wf_mixins.CycleStatusValidatedMixin, mixins.Timeboxed,
            mixins.Described, mixins.Titled, base.ContextRBAC, mixins.Slugged,
            mixins.Notifiable, ft_mixin.Indexed, db.Model):
    """Workflow Cycle model
  """
    # pylint: disable=too-many-instance-attributes

    __tablename__ = 'cycles'
    _title_uniqueness = False

    workflow_id = db.Column(
        db.Integer,
        db.ForeignKey('workflows.id', ondelete="CASCADE"),
        nullable=False,
    )
    cycle_task_groups = db.relationship('CycleTaskGroup',
                                        backref='_cycle',
                                        cascade='all, delete-orphan')
    cycle_task_group_object_tasks = db.relationship(
        'CycleTaskGroupObjectTask',
        backref='cycle',
        cascade='all, delete-orphan')
    is_current = db.Column(db.Boolean, default=True, nullable=False)
    next_due_date = db.Column(db.Date)

    # This parameter is overridden by workflow backref, but is here to ensure
    # pylint does not complain
    _workflow = None

    @hybrid.hybrid_property
    def workflow(self):
        """Getter for workflow foreign key."""
        return self._workflow

    @workflow.setter
    def workflow(self, workflow):
        """Set workflow foreign key and relationship."""
        if not self._workflow and workflow:
            relationship.Relationship(source=workflow, destination=self)
        self._workflow = workflow

    @property
    def is_done(self):
        """Check if cycle's done

    Overrides StatusValidatedMixin method because cycle's is_done state
    depends on is_verification_needed flag
    """
        if super(Cycle, self).is_done:
            return True
        if self.cycle_task_group_object_tasks:
            return False
        return True

    @builder.simple_property
    def folder(self):
        """Get the workflow folder."""
        if self.workflow:
            return self.workflow.folder
        return ""

    _api_attrs = reflection.ApiAttributes(
        'workflow',
        'cycle_task_groups',
        'is_current',
        'next_due_date',
        reflection.Attribute('folder', create=False, update=False),
    )

    _aliases = {
        "cycle_workflow": {
            "display_name": "Workflow",
            "filter_by": "_filter_by_cycle_workflow",
        },
        "contact": "Assignee",
        "secondary_contact": None,
    }

    PROPERTY_TEMPLATE = u"cycle {}"

    _fulltext_attrs = [
        "folder",
        ft_attributes.DateFullTextAttr("due date", "next_due_date"),
        ft_attributes.MultipleSubpropertyFullTextAttr("group title",
                                                      "cycle_task_groups",
                                                      ["title"], False),
        ft_attributes.MultipleSubpropertyFullTextAttr(
            "group assignee",
            lambda instance: [g.contact for g in instance.cycle_task_groups],
            ["email", "name"], False),
        ft_attributes.DateMultipleSubpropertyFullTextAttr(
            "group due date", 'cycle_task_groups', ["next_due_date"], False),
        ft_attributes.MultipleSubpropertyFullTextAttr(
            "task title", 'cycle_task_group_object_tasks', ["title"], False),
        ft_attributes.MultipleSubpropertyFullTextAttr(
            "task state", 'cycle_task_group_object_tasks', ["status"], False),
        ft_attributes.DateMultipleSubpropertyFullTextAttr(
            "task due date", "cycle_task_group_object_tasks", ["end_date"],
            False),
        ft_attributes.MultipleSubpropertyFullTextAttr("task assignees",
                                                      "_task_assignees",
                                                      ["name", "email"],
                                                      False),
        ft_attributes.MultipleSubpropertyFullTextAttr(
            "task secondary assignees", "_task_secondary_assignees",
            ["name", "email"], False),
        ft_attributes.MultipleSubpropertyFullTextAttr(
            "task comment", lambda instance: itertools.chain(
                *[t.comments for t in instance.cycle_task_group_object_tasks]),
            ["description"], False),
    ]

    @property
    def _task_assignees(self):
        """Property. Return the list of persons as assignee of related tasks."""
        people = set()
        for ctask in self.cycle_task_group_object_tasks:
            people.update(ctask.get_persons_for_rolename("Task Assignees"))
        return list(people)

    @property
    def _task_secondary_assignees(self):
        """Property. Returns people list as Secondary Assignee of related tasks."""
        people = set()
        for ctask in self.cycle_task_group_object_tasks:
            people.update(
                ctask.get_persons_for_rolename("Task Secondary Assignees"))
        return list(people)

    AUTO_REINDEX_RULES = [
        ft_mixin.ReindexRule("CycleTaskGroup", lambda x: x.cycle),
        ft_mixin.ReindexRule("CycleTaskGroupObjectTask",
                             lambda x: x.cycle_task_group.cycle),
        ft_mixin.ReindexRule("Person", _query_filtered_by_contact),
    ]

    @classmethod
    def _filter_by_cycle_workflow(cls, predicate):
        """Filter by cycle workflow."""
        from ggrc_workflows.models.workflow import Workflow
        return Workflow.query.filter((Workflow.id == cls.workflow_id)
                                     & (predicate(Workflow.slug)
                                        | predicate(Workflow.title))).exists()

    @classmethod
    def eager_query(cls, **kwargs):
        """Add cycle task groups to cycle eager query

    This function adds cycle_task_groups as a join option when fetching cycles,
    and makes sure we fetch all cycle related data needed for generating cycle
    json, in one query.

    Returns:
      a query object with cycle_task_groups added to joined load options.
    """
        query = super(Cycle, cls).eager_query(**kwargs)
        return query.options(
            orm.joinedload('cycle_task_groups'),
            orm.Load(cls).joinedload("workflow").undefer_group(
                "Workflow_complete"),
        )

    @classmethod
    def indexed_query(cls):
        return super(Cycle, cls).indexed_query().options(
            orm.Load(cls).load_only("next_due_date"),
            orm.Load(cls).subqueryload(
                "cycle_task_group_object_tasks").load_only(
                    "end_date",
                    "id",
                    "status",
                    "title",
                ),
            orm.Load(cls).subqueryload("cycle_task_groups").load_only(
                "id",
                "title",
                "next_due_date",
            ),
            orm.Load(cls).subqueryload(
                "cycle_task_group_object_tasks", ).subqueryload(
                    "_access_control_list").load_only(
                        "ac_role_id", ).subqueryload(
                            "access_control_people").load_only("person_id", ),
            orm.Load(cls).subqueryload("cycle_task_group_object_tasks"),
            orm.Load(cls).subqueryload("cycle_task_groups").joinedload(
                "contact").load_only(
                    "name",
                    "email",
                    "id",
                ),
            orm.Load(cls).joinedload("workflow").undefer_group(
                "Workflow_complete", ),
        )

    def _get_cycle_url(self, widget_name):
        return urljoin(
            get_url_root(),
            "workflows/{workflow_id}#{widget_name}/cycle/{cycle_id}".format(
                workflow_id=self.workflow.id,
                cycle_id=self.id,
                widget_name=widget_name))

    @property
    def cycle_url(self):
        return self._get_cycle_url("current")

    @property
    def cycle_inactive_url(self):
        return self._get_cycle_url("history")

    def log_json(self):
        out_json = super(Cycle, self).log_json()
        out_json["folder"] = self.folder
        return out_json
Exemple #4
0
class Cycle(mixins.WithContact,
            wf_mixins.CycleStatusValidatedMixin,
            mixins.Timeboxed,
            mixins.Described,
            mixins.Titled,
            mixins.Slugged,
            mixins.Notifiable,
            ft_mixin.Indexed,
            db.Model):
  """Workflow Cycle model
  """
  __tablename__ = 'cycles'
  _title_uniqueness = False

  workflow_id = db.Column(
      db.Integer,
      db.ForeignKey('workflows.id', ondelete="CASCADE"),
      nullable=False,
  )
  cycle_task_groups = db.relationship(
      'CycleTaskGroup', backref='cycle', cascade='all, delete-orphan')
  cycle_task_group_object_tasks = db.relationship(
      'CycleTaskGroupObjectTask', backref='cycle',
      cascade='all, delete-orphan')
  cycle_task_entries = db.relationship(
      'CycleTaskEntry', backref='cycle', cascade='all, delete-orphan')
  is_current = db.Column(db.Boolean, default=True, nullable=False)
  next_due_date = db.Column(db.Date)

  @property
  def is_done(self):
    """Check if cycle's done

    Overrides StatusValidatedMixin method because cycle's is_done state
    depends on is_verification_needed flag
    """
    if super(Cycle, self).is_done:
      return True
    if self.cycle_task_group_object_tasks:
      return False
    return True

  _api_attrs = reflection.ApiAttributes(
      'workflow',
      'cycle_task_groups',
      'is_current',
      'next_due_date',
  )

  _aliases = {
      "cycle_workflow": {
          "display_name": "Workflow",
          "filter_by": "_filter_by_cycle_workflow",
      },
      "contact": "Assignee",
      "secondary_contact": None,
  }

  PROPERTY_TEMPLATE = u"cycle {}"

  _fulltext_attrs = [
      ft_attributes.MultipleSubpropertyFullTextAttr(
          "group title", "cycle_task_groups", ["title"], False,
      ),
      ft_attributes.MultipleSubpropertyFullTextAttr(
          "group assignee",
          lambda instance: [g.contact for g in instance.cycle_task_groups],
          ["name", "email"],
          False,
      ),
      ft_attributes.DateMultipleSubpropertyFullTextAttr(
          "group due date",
          'cycle_task_groups',
          ["next_due_date"],
          False,
      ),
      ft_attributes.MultipleSubpropertyFullTextAttr(
          "task title",
          'cycle_task_group_object_tasks',
          ["title"],
          False,
      ),
      ft_attributes.MultipleSubpropertyFullTextAttr(
          "task assignee",
          lambda instance: [t.contact for t in
                            instance.cycle_task_group_object_tasks],
          ["name", "email"],
          False
      ),
      ft_attributes.DateMultipleSubpropertyFullTextAttr(
          "task due date",
          "cycle_task_group_object_tasks",
          ["end_date"],
          False
      ),
      ft_attributes.DateFullTextAttr("due date", "next_due_date"),
      ft_attributes.MultipleSubpropertyFullTextAttr(
          "task comments",
          lambda instance: list(itertools.chain(*[
              t.cycle_task_entries
              for t in instance.cycle_task_group_object_tasks
          ])),
          ["description"],
          False
      ),
  ]

  AUTO_REINDEX_RULES = [
      ft_mixin.ReindexRule("CycleTaskGroup", lambda x: x.cycle),
      ft_mixin.ReindexRule("CycleTaskGroupObjectTask",
                           lambda x: x.cycle_task_group.cycle),
      ft_mixin.ReindexRule("Person", _query_filtered_by_contact)
  ]

  @classmethod
  def _filter_by_cycle_workflow(cls, predicate):
    from ggrc_workflows.models.workflow import Workflow
    return Workflow.query.filter(
        (Workflow.id == cls.workflow_id) &
        (predicate(Workflow.slug) | predicate(Workflow.title))
    ).exists()

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

    This function adds cycle_task_groups as a join option when fetching cycles,
    and makes sure we fetch all cycle related data needed for generating cycle
    json, in one query.

    Returns:
      a query object with cycle_task_groups added to joined load options.
    """
    query = super(Cycle, cls).eager_query()
    return query.options(
        orm.joinedload('cycle_task_groups'),
    )

  @classmethod
  def indexed_query(cls):
    return super(Cycle, cls).indexed_query().options(
        orm.Load(cls).load_only("next_due_date"),
        orm.Load(cls).subqueryload("cycle_task_group_object_tasks").load_only(
            "id",
            "title",
            "end_date"
        ),
        orm.Load(cls).subqueryload("cycle_task_groups").load_only(
            "id",
            "title",
            "end_date",
            "next_due_date",
        ),
        orm.Load(cls).subqueryload("cycle_task_group_object_tasks").joinedload(
            "contact"
        ).load_only(
            "email",
            "name",
            "id"
        ),
        orm.Load(cls).subqueryload("cycle_task_group_object_tasks").joinedload(
            "cycle_task_entries"
        ).load_only(
            "description",
            "id"
        ),
        orm.Load(cls).subqueryload("cycle_task_groups").joinedload(
            "contact"
        ).load_only(
            "email",
            "name",
            "id"
        ),
        orm.Load(cls).joinedload("contact").load_only(
            "email",
            "name",
            "id"
        ),
    )