예제 #1
0
def handle_cycle_created(obj, manually):
    today = datetime.date.today()
    if manually:
        create_notification = "manual_cycle_created"
    else:
        create_notification = "cycle_created"
    pusher.update_or_create_notifications(obj, today, create_notification)
    if not obj.is_current:
        return
    notification_names = [
        get_notif_name_by_wf(obj.workflow), create_notification,
        "cycle_task_overdue", "cycle_task_due_today"
    ]
    tasks = list(obj.cycle_task_group_object_tasks)
    query = pusher.get_notification_query(
        *tasks, **{"notification_names": notification_names})
    exists_notifications = collections.defaultdict(set)
    for notification in query:
        exists_notifications[notification.notification_type_id].add(
            notification.object_id)
    for notification_type in pusher.get_notification_types(
            *notification_names):
        object_ids = exists_notifications[notification_type.id]
        notify_tasks = [
            t for t in tasks if not (t.id in object_ids or t.is_done)
        ]
        if create_notification == notification_type.name:
            pusher.create_notifications_for_objects(notification_type, today,
                                                    *notify_tasks)
        else:
            for task in notify_tasks:
                pusher.create_notifications_for_objects(
                    notification_type, task.end_date, task)
예제 #2
0
def handle_cycle_created(obj, manually):
  today = datetime.date.today()
  if manually:
    create_notification = "manual_cycle_created"
  else:
    create_notification = "cycle_created"
  pusher.update_or_create_notifications(obj, today, create_notification)
  if not obj.is_current:
    return
  notification_names = [get_notif_name_by_wf(obj.workflow),
                        create_notification,
                        "cycle_task_overdue",
                        "cycle_task_due_today"]
  tasks = list(obj.cycle_task_group_object_tasks)
  query = pusher.get_notification_query(
      *tasks,
      **{"notification_names": notification_names})
  exists_notifications = collections.defaultdict(set)
  for notification in query:
    exists_notifications[notification.notification_type_id].add(
        notification.object_id)
  for notification_type in pusher.get_notification_types(*notification_names):
    object_ids = exists_notifications[notification_type.id]
    notify_tasks = [t for t in tasks if not (t.id in object_ids or t.is_done)]
    if create_notification == notification_type.name:
      pusher.create_notifications_for_objects(notification_type,
                                              today,
                                              *notify_tasks)
    else:
      for task in notify_tasks:
        pusher.create_notifications_for_objects(notification_type,
                                                task.end_date,
                                                task)
예제 #3
0
def not_done_tasks_notify(tasks, day):
    """Notification handling for tasks that moved to active state.

  It will
    - create notification for tasks if it's needed
    - delete all notifications for related cycles
  """
    if not tasks:
        return
    cycles = {task.cycle for task in tasks}
    # delete all notifications for cycles about all tasks completed
    if cycles:
        pusher.get_notification_query(
            *(list(cycles)), **{
                "notification_names": ["all_cycle_tasks_completed"]
            }).delete(synchronize_session=False)
    # recreate notifications if it's necessary
    for task in tasks:
        pusher.update_or_create_notifications(task,
                                              task.end_date,
                                              get_notif_name_by_wf(
                                                  task.cycle.workflow),
                                              "cycle_task_due_today",
                                              "cycle_task_overdue",
                                              day=day)
예제 #4
0
def not_done_tasks_notify(tasks, day):
  """Notification handling for tasks that moved to active state.

  It will
    - create notification for tasks if it's needed
    - delete all notifications for related cycles
  """
  if not tasks:
    return
  cycles = {task.cycle for task in tasks}
  # delete all notifications for cycles about all tasks completed
  if cycles:
    pusher.get_notification_query(
        *(list(cycles)),
        **{"notification_names": ["all_cycle_tasks_completed"]}
    ).delete(
        synchronize_session=False
    )
  # recreate notifications if it's necessary
  for task in tasks:
    pusher.update_or_create_notifications(
        task,
        task.end_date,
        get_notif_name_by_wf(task.cycle.workflow),
        "cycle_task_due_today",
        "cycle_task_overdue",
        day=day
    )
예제 #5
0
def build_cycle(workflow, cycle=None, current_user=None):
  """Build a cycle with it's child objects"""

  if not workflow.tasks:
    logger.error("Starting a cycle has failed on Workflow with "
                 "slug == '%s' and id == '%s'", workflow.slug, workflow.id)
    pusher.update_or_create_notifications(workflow, date.today(),
                                          "cycle_start_failed")
    return

  # Determine the relevant Workflow
  cycle = cycle or models.Cycle()

  # Use Admin role when this is called via the cron job.
  if not current_user:
    current_user = workflow.get_persons_for_rolename("Admin")[0]
  # Populate the top-level Cycle object
  cycle.workflow = workflow
  cycle.is_current = True
  cycle.context = workflow.context
  cycle.title = workflow.title
  cycle.description = workflow.description
  cycle.is_verification_needed = workflow.is_verification_needed
  cycle.status = models.Cycle.ASSIGNED

  # Populate CycleTaskGroups based on Workflow's TaskGroups
  for task_group in workflow.task_groups:
    cycle_task_group = models.CycleTaskGroup(
        context=cycle.context,
        cycle=cycle,
        task_group=task_group,
        title=task_group.title,
        description=task_group.description,
        end_date=cycle.end_date,
        modified_by=current_user,
        contact=task_group.contact,
        status=models.CycleTaskGroup.ASSIGNED,
        sort_index=task_group.sort_index,
    )

    # preserve the old cycle creation for old workflows, so each object
    # gets its own cycle task
    if workflow.is_old_workflow:
      create_old_style_cycle(cycle, task_group, cycle_task_group, current_user)
    else:
      for task_group_task in task_group.task_group_tasks:
        cycle_task_group_object_task = _create_cycle_task(
            task_group_task, cycle, cycle_task_group, current_user)

        for task_group_object in task_group.task_group_objects:
          object_ = task_group_object.object
          Relationship(source=cycle_task_group_object_task,
                       destination=object_)

  update_cycle_dates(cycle)
  workflow.repeat_multiplier += 1
  workflow.next_cycle_start_date = workflow.calc_next_adjusted_date(
      workflow.min_task_start_date)
  return cycle
def handle_cycle_task_group_object_task_put(obj):
    if inspect(obj).attrs._access_control_list.history.has_changes():
        types = [
            "cycle_task_reassigned", "cycle_created", "manual_cycle_created"
        ]
        if not pusher.notification_exists_for(obj, notification_names=types):
            pusher.push(obj,
                        pusher.get_notification_type("cycle_task_reassigned"))
    if inspect(obj).attrs.end_date.history.has_changes() and not obj.is_done:
        # if you change end date to past than overdue
        # notification should be send today
        pusher.update_or_create_notifications(
            obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow),
            "cycle_task_due_today", "cycle_task_overdue")
예제 #7
0
def handle_cycle_task_group_object_task_put(obj):
  if inspect(obj).attrs._access_control_list.history.has_changes():
    types = ["cycle_task_reassigned", "cycle_created", "manual_cycle_created"]
    if not pusher.notification_exists_for(obj, notification_names=types):
      pusher.push(obj, pusher.get_notification_type("cycle_task_reassigned"))
  if inspect(obj).attrs.end_date.history.has_changes() and not obj.is_done:
    # if you change end date to past than overdue
    # notification should be sent today
    pusher.update_or_create_notifications(
        obj,
        obj.end_date,
        get_notif_name_by_wf(obj.cycle.workflow),
        "cycle_task_due_today",
        "cycle_task_overdue")
def handle_workflow_modify(sender, obj=None, src=None, service=None):
    """Update or add notifications on a workflow update."""
    if obj.status != obj.ACTIVE or obj.unit is None:
        return
    if not obj.next_cycle_start_date:
        obj.next_cycle_start_date = obj.min_task_start_date
    pusher.update_or_create_notifications(
        obj, obj.next_cycle_start_date,
        "{}_workflow_starts_in".format(obj.unit), "cycle_start_failed")
    query = pusher.get_notification_query(
        *list(obj.tasks), **{"notification_names": ["cycle_start_failed"]})
    notif_type = pusher.get_notification_type("cycle_start_failed")
    exist_task_ids = set(query.distinct(Notification.object_id))
    for task in obj.tasks:
        if task.id not in exist_task_ids:
            pusher.push(task, notif_type, task.start_date)
예제 #9
0
def handle_workflow_modify(sender, obj=None, src=None, service=None):
  """Update or add notifications on a workflow update."""
  if obj.status != obj.ACTIVE or obj.unit is None:
    return
  if not obj.next_cycle_start_date:
    obj.next_cycle_start_date = obj.min_task_start_date
  pusher.update_or_create_notifications(
      obj,
      obj.next_cycle_start_date,
      "{}_workflow_starts_in".format(obj.unit),
      "cycle_start_failed")
  query = pusher.get_notification_query(
      *list(obj.tasks),
      **{"notification_names": ["cycle_start_failed"]})
  notif_type = pusher.get_notification_type("cycle_start_failed")
  exist_task_ids = set(query.distinct(Notification.object_id))
  for task in obj.tasks:
    if task.id not in exist_task_ids:
      pusher.push(task, notif_type, task.start_date)
예제 #10
0
def handle_cycle_created(obj, manually):
    today = datetime.date.today()
    if manually:
        create_notification = "manual_cycle_created"
    else:
        create_notification = "cycle_created"
    pusher.update_or_create_notifications(obj, today, create_notification)
    if not obj.is_current:
        return
    task_notif_name = get_notif_name_by_wf(obj.workflow)
    notification_names = [
        task_notif_name, create_notification, "cycle_task_overdue",
        "cycle_task_due_today"
    ]
    notif_dict = [pusher.get_notification_type(n) for n in notification_names]
    tasks = list(obj.cycle_task_group_object_tasks)
    query = pusher.get_notification_query(
        *tasks, **{"notification_names": notification_names})
    exists_notifications = collections.defaultdict(set)
    for notification in query:
        exists_notifications[notification.notification_type_id].add(
            notification.object_id)

    for notification_type in notif_dict:
        object_ids = exists_notifications[notification_type.id]
        repeatable_notification = (notification_type.name
                                   in pusher.REPEATABLE_NOTIFICATIONS)
        for task in tasks:
            if task.id in object_ids or task.is_done:
                continue
            if create_notification == notification_type.name:
                send_on = today
            else:
                send_on = task.end_date
            send_on -= datetime.timedelta(notification_type.advance_notice)
            if repeatable_notification:
                send_on = max(send_on, today)
            if send_on >= today:
                pusher.push(task, notification_type, send_on,
                            repeatable_notification)
예제 #11
0
def handle_cycle_created(obj, manually):
  today = datetime.date.today()
  if manually:
    create_notification = "manual_cycle_created"
  else:
    create_notification = "cycle_created"
  pusher.update_or_create_notifications(obj, today, create_notification)
  if not obj.is_current:
    return
  task_notif_name = get_notif_name_by_wf(obj.workflow)
  notification_names = [task_notif_name, create_notification,
                        "cycle_task_overdue", "cycle_task_due_today"]
  notif_dict = [pusher.get_notification_type(n) for n in notification_names]
  tasks = list(obj.cycle_task_group_object_tasks)
  query = pusher.get_notification_query(
      *tasks,
      **{"notification_names": notification_names})
  exists_notifications = collections.defaultdict(set)
  for notification in query:
    exists_notifications[notification.notification_type_id].add(
        notification.object_id)

  for notification_type in notif_dict:
    object_ids = exists_notifications[notification_type.id]
    repeatable_notification = (
        notification_type.name in pusher.REPEATABLE_NOTIFICATIONS
    )
    for task in tasks:
      if task.id in object_ids or task.is_done:
        continue
      if create_notification == notification_type.name:
        send_on = today
      else:
        send_on = task.end_date
      send_on -= datetime.timedelta(notification_type.advance_notice)
      if repeatable_notification:
        send_on = max(send_on, today)
      if send_on >= today:
        pusher.push(task, notification_type, send_on, repeatable_notification)
예제 #12
0
def handle_cycle_task_status_change(obj):
    if obj.is_done:
        query = pusher.get_notification_query(obj)
        query.delete(synchronize_session="fetch")
        # if all tasks are in inactive states then add notification to cycle
        if all(task.is_done
               for task in obj.cycle.cycle_task_group_object_tasks):
            pusher.update_or_create_notifications(obj.cycle,
                                                  datetime.date.today(),
                                                  "all_cycle_tasks_completed")
    else:
        pusher.get_notification_query(
            obj.cycle, **{
                "notification_names": ["all_cycle_tasks_completed"]
            }).delete(synchronize_session="fetch")
        if obj.status == models.CycleTaskGroupObjectTask.DECLINED:
            pusher.update_or_create_notifications(obj, datetime.date.today(),
                                                  "cycle_task_declined")
        pusher.update_or_create_notifications(
            obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow),
            "cycle_task_due_today", "cycle_task_overdue")
예제 #13
0
def handle_cycle_task_status_change(obj):
  if obj.is_done:
    query = pusher.get_notification_query(obj)
    query.delete(synchronize_session="fetch")
    # if all tasks are in inactive states then add notification to cycle
    if all(task.is_done for task in obj.cycle.cycle_task_group_object_tasks):
      pusher.update_or_create_notifications(
          obj.cycle,
          datetime.date.today(),
          "all_cycle_tasks_completed")
  else:
    pusher.get_notification_query(
        obj.cycle,
        **{"notification_names": ["all_cycle_tasks_completed"]}
    ).delete(synchronize_session="fetch")
    if obj.status == models.CycleTaskGroupObjectTask.DECLINED:
      pusher.update_or_create_notifications(
          obj, datetime.date.today(), "cycle_task_declined")
    pusher.update_or_create_notifications(
        obj,
        obj.end_date,
        get_notif_name_by_wf(obj.cycle.workflow),
        "cycle_task_due_today",
        "cycle_task_overdue")
예제 #14
0
def build_cycle(workflow, cycle=None, current_user=None):
  """Build a cycle with it's child objects"""
  build_failed = False

  if not workflow.tasks:
    logger.error("Starting a cycle has failed on Workflow with "
                 "slug == '%s' and id == '%s', due to empty setup",
                 workflow.slug, workflow.id)
    build_failed = True

  # Use Admin role when this is called via the cron job.
  if not current_user:
    admins = workflow.get_persons_for_rolename("Admin")
    if admins:
      current_user = admins[0]
    else:
      logger.error("Cannot start cycle on Workflow with slug == '%s' and "
                   "id == '%s', cause it doesn't have Admins",
                   workflow.slug, workflow.id)
      build_failed = True

  if build_failed:
    pusher.update_or_create_notifications(workflow, date.today(),
                                          "cycle_start_failed")
    return

  # Determine the relevant Workflow
  cycle = cycle or models.Cycle()

  # Populate the top-level Cycle object
  cycle.workflow = workflow
  cycle.is_current = True
  cycle.context = workflow.context
  cycle.title = workflow.title
  cycle.description = workflow.description
  cycle.is_verification_needed = workflow.is_verification_needed
  cycle.status = models.Cycle.ASSIGNED

  # Populate CycleTaskGroups based on Workflow's TaskGroups
  for task_group in workflow.task_groups:
    cycle_task_group = models.CycleTaskGroup(
        context=cycle.context,
        cycle=cycle,
        task_group=task_group,
        title=task_group.title,
        description=task_group.description,
        end_date=cycle.end_date,
        modified_by=current_user,
        contact=task_group.contact,
        status=models.CycleTaskGroup.ASSIGNED,
    )

    # preserve the old cycle creation for old workflows, so each object
    # gets its own cycle task
    if workflow.is_old_workflow:
      create_old_style_cycle(cycle, task_group, cycle_task_group, current_user)
    else:
      for task_group_task in task_group.task_group_tasks:
        cycle_task_group_object_task = _create_cycle_task(
            task_group_task, cycle, cycle_task_group, current_user)
        related_objs = [obj for obj in task_group.related_objects()
                        if not isinstance(obj, (
                            all_models.TaskGroupTask, all_models.Workflow
                        ))]
        for obj in related_objs:
          Relationship(source=cycle_task_group_object_task,
                       destination=obj)

  update_cycle_dates(cycle)
  workflow.repeat_multiplier += 1
  workflow.next_cycle_start_date = workflow.calc_next_adjusted_date(
      workflow.min_task_start_date)
  return cycle
예제 #15
0
def build_cycle(workflow, cycle=None, current_user=None):
  """Build a cycle with it's child objects"""
  build_failed = False

  if not workflow.tasks:
    logger.error("Starting a cycle has failed on Workflow with "
                 "slug == '%s' and id == '%s', due to empty setup",
                 workflow.slug, workflow.id)
    build_failed = True

  # Use Admin role when this is called via the cron job.
  if not current_user:
    admins = workflow.get_persons_for_rolename("Admin")
    if admins:
      current_user = admins[0]
    else:
      logger.error("Cannot start cycle on Workflow with slug == '%s' and "
                   "id == '%s', cause it doesn't have Admins",
                   workflow.slug, workflow.id)
      build_failed = True

  if build_failed:
    pusher.update_or_create_notifications(workflow, date.today(),
                                          "cycle_start_failed")
    return

  # Determine the relevant Workflow
  cycle = cycle or models.Cycle()

  # Populate the top-level Cycle object
  cycle.workflow = workflow
  cycle.is_current = True
  cycle.context = workflow.context
  cycle.title = workflow.title
  cycle.description = workflow.description
  cycle.is_verification_needed = workflow.is_verification_needed
  cycle.status = models.Cycle.ASSIGNED

  # Populate CycleTaskGroups based on Workflow's TaskGroups
  for task_group in workflow.task_groups:
    cycle_task_group = models.CycleTaskGroup(
        context=cycle.context,
        cycle=cycle,
        task_group=task_group,
        title=task_group.title,
        description=task_group.description,
        end_date=cycle.end_date,
        modified_by=current_user,
        contact=task_group.contact,
        status=models.CycleTaskGroup.ASSIGNED,
        sort_index=task_group.sort_index,
    )

    # preserve the old cycle creation for old workflows, so each object
    # gets its own cycle task
    if workflow.is_old_workflow:
      create_old_style_cycle(cycle, task_group, cycle_task_group, current_user)
    else:
      for task_group_task in task_group.task_group_tasks:
        cycle_task_group_object_task = _create_cycle_task(
            task_group_task, cycle, cycle_task_group, current_user)

        for task_group_object in task_group.task_group_objects:
          object_ = task_group_object.object
          Relationship(source=cycle_task_group_object_task,
                       destination=object_)

  update_cycle_dates(cycle)
  workflow.repeat_multiplier += 1
  workflow.next_cycle_start_date = workflow.calc_next_adjusted_date(
      workflow.min_task_start_date)
  return cycle
예제 #16
0
def build_cycle(workflow, cycle=None, current_user=None):
  """Build a cycle with it's child objects"""

  if not workflow.tasks:
    logger.error("Starting a cycle has failed on Workflow with "
                 "slug == '%s' and id == '%s'", workflow.slug, workflow.id)
    pusher.update_or_create_notifications(workflow, date.today(),
                                          "cycle_start_failed")
    return

  # Determine the relevant Workflow
  cycle = cycle or models.Cycle()

  # Use WorkflowOwner role when this is called via the cron job.
  if not current_user:
    for user_role in workflow.context.user_roles:
      if user_role.role.name == "WorkflowOwner":
        current_user = user_role.person
        break
  # Populate the top-level Cycle object
  cycle.workflow = workflow
  cycle.is_current = True
  cycle.context = workflow.context
  cycle.title = workflow.title
  cycle.description = workflow.description
  cycle.is_verification_needed = workflow.is_verification_needed
  cycle.status = models.Cycle.ASSIGNED

  # Populate CycleTaskGroups based on Workflow's TaskGroups
  for task_group in workflow.task_groups:
    cycle_task_group = models.CycleTaskGroup(
        context=cycle.context,
        cycle=cycle,
        task_group=task_group,
        title=task_group.title,
        description=task_group.description,
        end_date=cycle.end_date,
        modified_by=current_user,
        contact=task_group.contact,
        status=models.CycleTaskGroup.ASSIGNED,
        sort_index=task_group.sort_index,
    )

    # preserve the old cycle creation for old workflows, so each object
    # gets its own cycle task
    if workflow.is_old_workflow:
      create_old_style_cycle(cycle, task_group, cycle_task_group, current_user)
    else:
      for task_group_task in task_group.task_group_tasks:
        cycle_task_group_object_task = _create_cycle_task(
            task_group_task, cycle, cycle_task_group, current_user)

        for task_group_object in task_group.task_group_objects:
          object_ = task_group_object.object
          Relationship(source=cycle_task_group_object_task,
                       destination=object_)

  update_cycle_dates(cycle)
  Signals.workflow_cycle_start.send(
      cycle.__class__,
      obj=cycle,
      new_status=cycle.status,
      old_status=None
  )
  workflow.repeat_multiplier += 1
  workflow.next_cycle_start_date = workflow.calc_next_adjusted_date(
      workflow.min_task_start_date)
  return cycle