diff --git a/scipost_django/tasks/tasks/task.py b/scipost_django/tasks/tasks/task.py new file mode 100644 index 0000000000000000000000000000000000000000..1b40988d0b88f6f7aae37211217ea8b537c79fd0 --- /dev/null +++ b/scipost_django/tasks/tasks/task.py @@ -0,0 +1,89 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from abc import abstractmethod +from collections.abc import Callable, Collection +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from django.db.models import Q, QuerySet +from django.template import Template, loader +from django.utils import timezone + + +if TYPE_CHECKING: + from django.contrib.auth.models import User + from tasks.tasks.task_action import TaskAction + + +@dataclass +class Task: + user: int + kind: "type[TaskKind]" + data: dict = field(default_factory=dict) + + @property + def actions(self) -> Collection["TaskAction"]: + return [action(self) for action in self.kind.actions] + + @property + def as_html(self) -> str: + + return self.kind.template().render({"task": self}) + + @property + def title(self) -> str: + return self.kind.task_title.format(**self.data) + + @property + def due_date(self) -> timezone.datetime: + return self.data.get("due_date", self.kind.get_default_due_date()) + + +class TaskKind: + name: str + task_title: str + description: str = "" + actions: Collection[Callable[[Task], "TaskAction"]] + template_name: str = "tasks/task.html" + + @staticmethod + @abstractmethod + def get_queryset() -> "QuerySet": + """Return a queryset of task data from which Task instances are created.""" + pass + + @staticmethod + @abstractmethod + def search_query(text: str) -> Q: + """Create a filter query for a given text.""" + return Q() + + @classmethod + def search(cls, text: str) -> "QuerySet": + """Return a queryset of tasks that match the search text.""" + search_query = cls.search_query(text) + return cls.get_queryset().filter(search_query) + + @classmethod + def get_tasks(cls) -> Collection[Task]: + return [Task(user=1, kind=cls, data=data) for data in cls.get_task_data()] + + @classmethod + def get_task_data(cls) -> Collection[dict]: + """Maps the queryset to a collection of dictionaries to be used as task data.""" + return [{"object": obj} for obj in cls.get_queryset()] + + @classmethod + def template(cls) -> Template: + return loader.get_template(cls.template_name) + + @staticmethod + @abstractmethod + def is_user_eligible(user: "User") -> bool: + return user.is_staff + + @staticmethod + def get_default_due_date() -> timezone.datetime: + return timezone.now() diff --git a/scipost_django/tasks/tasks/task_action.py b/scipost_django/tasks/tasks/task_action.py new file mode 100644 index 0000000000000000000000000000000000000000..cff3f1fc725026eba2fd489b1d91d0e11e1360f8 --- /dev/null +++ b/scipost_django/tasks/tasks/task_action.py @@ -0,0 +1,60 @@ +__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from collections.abc import Callable +from dataclasses import dataclass, field +from typing import Literal + +from django.urls import reverse_lazy + +from .task import Task + + +@dataclass +class TaskAction: + tag: Literal["a", "button"] + css_class: str = "btn" + content: str = "" + kwargs: dict = field(default_factory=dict) + + @staticmethod + def attrs_str(attrs: dict) -> str: + return " ".join(f'{k}="{v}"' for k, v in attrs.items()) + + @property + def attrs(self) -> dict: + return self.kwargs.get("attrs", {}) + + @property + def as_html(self) -> str: + element = '<{tag} {attrs} class="{css_class}">{content}</{tag}>' + return element.format( + tag=self.tag, + attrs=self.attrs_str(self.attrs), + css_class=self.css_class, + content=self.content, + ) + + +class ViewAction(TaskAction): + def __init__(self, url: str, content: str = "View"): + self.url = url + + return super().__init__( + tag="a", + content=content, + kwargs={"attrs": {"href": self.url}}, + ) + + @staticmethod + def default_builder( + reverse_url_str: str, content: str = "View" + ) -> Callable[[Task], TaskAction]: + def action(task: Task) -> TaskAction: + return ViewAction( + reverse_lazy(reverse_url_str, kwargs={"pk": task.data["object"].pk}), + content, + ) + + return action