Example #1
0
def get_parsers(klass, key):
    """Return tuple of 2 parsers related to current key and class"""
    fulltext_parser = get_fulltext_parsed_value(klass, key)
    if fulltext_parser:
        if isinstance(fulltext_parser, DatetimeValue):
            return (fulltext_parser, None)
        else:
            return (None, fulltext_parser)
    columns = {i.name: i.type for i in klass.__table__.columns}
    if key in columns:
        attr_type = columns[key]
    else:
        value_types = [
            i[0] for i in db.session.query(
                CustomAttributeDefinition.attribute_type).filter(
                    CustomAttributeDefinition.title == key,
                    CustomAttributeDefinition.definition_type ==
                    klass._inflector.table_singular  # pylint: disable=protected-access
                ).distinct()
        ]
        is_date = CustomAttributeDefinition.ValidTypes.DATE in value_types
        is_any_value = len(value_types) > int(is_date)
        return (DateValue() if is_date else None,
                FullTextAttr(key, key) if is_any_value else None)
    if isinstance(attr_type, sa.sql.sqltypes.DateTime):
        return (DatetimeValue(), None)
    elif isinstance(attr_type, sa.sql.sqltypes.Date):
        return (DateValue(), None)
    return (None, FullTextAttr(key, key))
Example #2
0
def get_fulltext_parsed_value(klass, key):
    """Get fulltext parser if it's exists """
    attrs = AttributeInfo.gather_attrs(klass, '_fulltext_attrs')
    if not issubclass(klass, Indexed):
        return
    for attr in attrs:
        if isinstance(attr, FullTextAttr) and attr.with_template:
            attr_key = klass.PROPERTY_TEMPLATE.format(attr.alias)
        elif isinstance(attr, FullTextAttr):
            attr_key = attr.alias
        else:
            attr_key = klass.PROPERTY_TEMPLATE.format(attr)
            attr = FullTextAttr(key, key)
        if attr_key == key:
            return attr
Example #3
0
def _get_class_properties():
    """Get indexable properties for all models

  Args:
    None
  Returns:
    class_properties dict - representing a list of searchable attributes
                            for every model
  """
    class_properties = defaultdict(list)
    for klass_name in Types.all:
        full_text_attrs = AttributeInfo.gather_attrs(
            getattr(all_models, klass_name), '_fulltext_attrs')
        for attr in full_text_attrs:
            if not isinstance(attr, FullTextAttr):
                attr = FullTextAttr(attr, attr)
            class_properties[klass_name].append(attr)
    return class_properties
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"),
        )
Example #5
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))
        }
    }

    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,
        ),
        MultipleSubpropertyFullTextAttr(
            "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),
        MultipleSubpropertyFullTextAttr("task due date",
                                        "cycle_task_group_object_tasks",
                                        ["end_date"], False),
        FullTextAttr("due date", "next_due_date"),
    ]

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

    @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'), )
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),
        MultipleSubpropertyFullTextAttr("task due date",
                                        "cycle_task_group_tasks", ["end_date"],
                                        False),
        FullTextAttr(
            "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),
        FullTextAttr("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'))