Beispiel #1
0
class Cycle(WithContact, Stateful, Timeboxed, Described, Titled, Slugged,
            Notifiable, Indexed, db.Model):
  """Workflow Cycle model
  """
  __tablename__ = 'cycles'
  _title_uniqueness = False

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

  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)

  _publish_attrs = [
      'workflow',
      'cycle_task_groups',
      'is_current',
      'next_due_date',
  ]

  _aliases = {
      "cycle_workflow": {
          "display_name": "Workflow",
          "filter_by": "_filter_by_cycle_workflow",
      },
      "status": {
          "display_name": "State",
          "mandatory": False,
          "description": "Options are: \n{} ".format('\n'.join(VALID_STATES))
      },
      "contact": "Assignee",
      "secondary_contact": None,
  }

  PROPERTY_TEMPLATE = u"cycle {}"

  _fulltext_attrs = [
      MultipleSubpropertyFullTextAttr(
          "group title", "cycle_task_groups", ["title"], False,
      ),
      MultipleSubpropertyFullTextAttr(
          "group assignee",
          lambda instance: [g.contact for g in instance.cycle_task_groups],
          ["name", "email"],
          False,
      ),
      DateMultipleSubpropertyFullTextAttr(
          "group due date",
          'cycle_task_groups',
          ["next_due_date"],
          False,
      ),
      MultipleSubpropertyFullTextAttr(
          "task title",
          'cycle_task_group_object_tasks',
          ["title"],
          False,
      ),
      MultipleSubpropertyFullTextAttr(
          "task assignee",
          lambda instance: [t.contact for t in
                            instance.cycle_task_group_object_tasks],
          ["name", "email"],
          False
      ),
      DateMultipleSubpropertyFullTextAttr(
          "task due date",
          "cycle_task_group_object_tasks",
          ["end_date"],
          False
      ),
      DateFullTextAttr("due date", "next_due_date"),
      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 = [
      ReindexRule("CycleTaskGroup", lambda x: x.cycle),
      ReindexRule("CycleTaskGroupObjectTask",
                  lambda x: x.cycle_task_group.cycle),
      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"
        ),
    )
class CycleTaskGroupObjectTask(WithContact, Stateful, Timeboxed, Relatable,
                               Notifiable, Described, Titled, Slugged, Base,
                               Indexed, db.Model):
    """Cycle task model
  """
    __tablename__ = 'cycle_task_group_object_tasks'
    _title_uniqueness = False

    IMPORTABLE_FIELDS = (
        'slug',
        'title',
        'description',
        'start_date',
        'end_date',
        'finished_date',
        'verified_date',
        'contact',
    )

    @classmethod
    def generate_slug_prefix_for(cls, obj):
        return "CYCLETASK"

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

    # Note: this statuses are used in utils/query_helpers to filter out the tasks
    # that should be visible on My Tasks pages.
    ACTIVE_STATES = ("Assigned", "InProgress", "Finished", "Declined")

    PROPERTY_TEMPLATE = u"task {}"

    _fulltext_attrs = [
        DateFullTextAttr(
            "end_date",
            'end_date',
        ),
        FullTextAttr("assignee", 'contact', ['name', 'email']),
        FullTextAttr("group title", 'cycle_task_group', ['title'], False),
        FullTextAttr("cycle title", 'cycle', ['title'], False),
        FullTextAttr("group assignee", lambda x: x.cycle_task_group.contact,
                     ['email', 'name'], False),
        FullTextAttr("cycle assignee", lambda x: x.cycle.contact,
                     ['email', 'name'], False),
        DateFullTextAttr("group due date",
                         lambda x: x.cycle_task_group.next_due_date,
                         with_template=False),
        DateFullTextAttr("cycle due date",
                         lambda x: x.cycle.next_due_date,
                         with_template=False),
        MultipleSubpropertyFullTextAttr("comments", "cycle_task_entries",
                                        ["description"]),
    ]

    AUTO_REINDEX_RULES = [
        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(JsonType(), nullable=False, default=[])
    selected_response_options = db.Column(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)

    object_approval = association_proxy('cycle', 'workflow.object_approval')
    object_approval.publish_raw = True

    @property
    def cycle_task_objects_for_cache(self):
        """Changing task state must invalidate `workflow_state` on objects
    """
        return [(object_.__class__.__name__, object_.id)
                for object_ in self.related_objects]  # pylint: disable=not-an-iterable

    _publish_attrs = [
        'cycle', 'cycle_task_group', 'task_group_task', 'cycle_task_entries',
        'sort_index', 'task_type', 'response_options',
        'selected_response_options',
        PublishOnly('object_approval'),
        PublishOnly('finished_date'),
        PublishOnly('verified_date')
    ]

    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",
        "contact": {
            "display_name": "Assignee",
            "mandatory": True,
        },
        "secondary_contact": None,
        "finished_date": "Actual Finish Date",
        "verified_date": "Actual Verified Date",
        "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,
        },
        "status": {
            "display_name":
            "State",
            "mandatory":
            False,
            "description":
            "Options are:\n{}".format('\n'.join(
                (item for item in VALID_STATES if item)))
        },
        "end_date": "Due Date",
        "start_date": "Start Date",
    }

    @computed_property
    def related_objects(self):
        """Compute and return a list of all the objects related to this cycle task.

    Related objects are those that are found either on the "source" side, or
    on the "destination" side of any of the instance's relations.

    Returns:
      (list) All objects related to the instance.
    """
        # pylint: disable=not-an-iterable
        sources = [r.source for r in self.related_sources]
        destinations = [r.destination for r in self.related_destinations]
        return sources + destinations

    @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.joinedload('cycle').joinedload('workflow').undefer_group(
                'Workflow_complete'),
            orm.joinedload('cycle_task_entries'),
        )

    @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"),
            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("contact").load_only(
                "email", "name", "id"),
        )
class CycleTaskGroup(WithContact, Stateful, Slugged, Timeboxed, Described,
                     Titled, Indexed, Base, db.Model):
    """Cycle Task Group model.
  """
    __tablename__ = 'cycle_task_groups'
    _title_uniqueness = False

    @classmethod
    def generate_slug_prefix_for(cls, obj):
        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 = [
        MultipleSubpropertyFullTextAttr("task title", 'cycle_task_group_tasks',
                                        ["title"], False),
        MultipleSubpropertyFullTextAttr(
            "task assignee", lambda instance:
            [t.contact for t in instance.cycle_task_group_tasks],
            ["name", "email"], False),
        DateMultipleSubpropertyFullTextAttr("task due date",
                                            "cycle_task_group_tasks",
                                            ["end_date"], False),
        DateFullTextAttr(
            "due date",
            'next_due_date',
        ),
        FullTextAttr("assignee", "contact", ['name', 'email']),
        FullTextAttr("cycle title", 'cycle', ['title'], False),
        FullTextAttr("cycle assignee", lambda x: x.cycle.contact,
                     ['email', 'name'], False),
        DateFullTextAttr("cycle due date",
                         lambda x: x.cycle.next_due_date,
                         with_template=False),
    ]

    AUTO_REINDEX_RULES = [
        ReindexRule("CycleTaskGroupObjectTask", lambda x: x.cycle_task_group),
        ReindexRule(
            "Person", lambda x: CycleTaskGroup.query.filter(
                CycleTaskGroup.contact_id == x.id)),
        ReindexRule(
            "Person", lambda x: [
                i.cycle for i in CycleTaskGroup.query.filter(
                    CycleTaskGroup.contact_id == x.id)
            ]),
    ]

    @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 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'))