From 40317d3ad0d93bbf7af07ed0786e4b669faff5c3 Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Wed, 11 Dec 2024 14:02:02 +0100
Subject: [PATCH] add new task base objects

---
 scipost_django/tasks/tasks/task.py        | 89 +++++++++++++++++++++++
 scipost_django/tasks/tasks/task_action.py | 60 +++++++++++++++
 2 files changed, 149 insertions(+)
 create mode 100644 scipost_django/tasks/tasks/task.py
 create mode 100644 scipost_django/tasks/tasks/task_action.py

diff --git a/scipost_django/tasks/tasks/task.py b/scipost_django/tasks/tasks/task.py
new file mode 100644
index 000000000..1b40988d0
--- /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 000000000..cff3f1fc7
--- /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
-- 
GitLab