Ocyan Task
========================
Installation
-------------

Add `ocyan.task` to your projects dependencies.


About
----------------------------

This library contains a Snippetviewset. This viewset provides an interface to view the status of tasks, restart tasks, and cancel tasks.

If u want to use this viewset, u need register it.

The moment you create a task it goes to pending, every minute pending orders are started. the status then becomes 'Progress'. if an order is failed and the 'retry_after' duration has expired (start counting from the timestamp the status went to failed), it will also be restarted.

if you don't want to wait a minute you can call `tak.run_async()` than the task will start soon.

Usage
----------------------------

If you want only 1 of a certain action to exist you can override the validate method. In this you can raise a Validationerror. If you do that the task cannot be created.

Note
====

   Ocyan.task comes with 3 different action classes for some common cases;
   
  1. Action, just a regular action with sane defaults for name and retry_after
  2. ExclusiveAction, only on of these actions can run at the same time, based on the action ``code``
  3. UniqueAction, this one is even more restrictive because the action can only be executed once for each ``code``


Example: 

```python
from datetime import timedelta
from django.core.exceptions import ValidationError

from ocyan.task.models import Task
from ocyan.task.action import Action

from .models import Order

class SyncOrder(Action):
    retry_after=timedelta(minutes=2)
    name_pattern = "%(verbose_name)s %(name)s %(code)s"

    def __init__(self, order, **kwargs):
        super().__init__({"pk": order.pk, code=order.number})
        self.order = order

    @classmethod
    def deserialize(cls, task):
        return cls(Order.objects.get(**task.data))

    def validate_task(self, task):
        Task = task._meta.model
        if Task.objects.filter(code=task.code).exists():
            raise ValidationError(
                gettext("Order %s is allready being synchronized") % self.order
            )

    def run(self, log):
        log.info("Starting sync of %s" % self.order)
        result = controller.sync_order(self.order)

        log.debug("Got result %s" % result)

        if not result.ok:
            raise Exception("Got wring result from ordersync %s %s" % self.order, result)

sync_action = SyncOrder(order)
task = Task.create(sync_action)

task.run()

# or asynchronous
task.run_async()
```

Hook and class variables
========================

For each action the following things can be configured by class variables:

- retry_after: a timedelta object specifying after how much time an action is retried when failing.
- name: The display name of the task
- code: The identifier of the task.
- is_highvolume: A high volume task is hidden in the task view by default. It will show if the task failed or it can be made visible with a filter

There are several hook methods that are used to read the values from an action, and can be reimplemented in your subclasses:

- deserialize: A class method that is used to deserialize an action from a task.
- get_name: compute the name for a task
- validate_task: validate a task in the context of the action
- get_code: return the action code to be saved to the task
- get_data: returns the data to be saved to the task
- get_retry_after: return the timedelta to be saved to the task
- get_is_highvolume: returns if the action is highvolume to be save to the task
- get_importstring: a classmethod that returns where the class can be loaded from when deserializing

Registering tasks buttons in wagtail
====================================

You can register a button to start the task in wagtail using ``ocyan.task.wagtailadmin.action_registry.register``:

```
from ocyan.task.wagtailadmin.registry import action_registry

@action_registry.register
class SyncOrder(Action):
    name = "harry bannink"
    code = "bluarghhh"
```
