feat: repeat tasks after completion
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
@@ -29,6 +30,20 @@ class UserOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: UUID
|
||||
username: str
|
||||
timezone: str
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
timezone: str = Field(min_length=1, max_length=64)
|
||||
|
||||
@field_validator("timezone")
|
||||
@classmethod
|
||||
def validate_timezone(cls, value: str) -> str:
|
||||
try:
|
||||
ZoneInfo(value)
|
||||
except (ZoneInfoNotFoundError, ValueError) as exc:
|
||||
raise ValueError("timezone must be a valid IANA timezone") from exc
|
||||
return value
|
||||
|
||||
|
||||
class SessionOut(BaseModel):
|
||||
@@ -108,6 +123,8 @@ class TaskCreate(BaseModel):
|
||||
due_has_time: bool = False
|
||||
parent_id: UUID | None = None
|
||||
rrule: str | None = Field(default=None, min_length=5, max_length=1000)
|
||||
trigger_mode: str | None = Field(default=None, pattern="^(scheduled|after_completion)$")
|
||||
after_completion_days: int | None = Field(default=None, ge=1, le=3650)
|
||||
|
||||
@field_validator("title")
|
||||
@classmethod
|
||||
@@ -117,6 +134,22 @@ class TaskCreate(BaseModel):
|
||||
raise ValueError("title cannot be blank")
|
||||
return value
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_recurrence(self):
|
||||
has_recurrence = self.rrule is not None or self.trigger_mode is not None or self.after_completion_days is not None
|
||||
if has_recurrence and self.due_at is None:
|
||||
raise ValueError("recurrence requires due_at")
|
||||
if has_recurrence and self.parent_id is not None:
|
||||
raise ValueError("only top-level tasks can recur")
|
||||
if self.trigger_mode == "after_completion":
|
||||
if self.after_completion_days is None or self.rrule is not None:
|
||||
raise ValueError("after_completion requires days and no rrule")
|
||||
elif self.trigger_mode == "scheduled" and self.rrule is None:
|
||||
raise ValueError("scheduled recurrence requires rrule")
|
||||
elif self.trigger_mode is None and self.after_completion_days is not None:
|
||||
raise ValueError("after_completion_days requires after_completion mode")
|
||||
return self
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
title: str | None = Field(default=None, min_length=1, max_length=500)
|
||||
@@ -188,6 +221,7 @@ class BatchTaskUpdate(BaseModel):
|
||||
list_id: UUID | None = None
|
||||
due_at: datetime | None = None
|
||||
soft_delete: bool | None = None
|
||||
versions: dict[UUID, int] | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_operation(self):
|
||||
@@ -196,6 +230,13 @@ class BatchTaskUpdate(BaseModel):
|
||||
raise ValueError("at least one batch operation is required")
|
||||
if self.soft_delete is False:
|
||||
raise ValueError("soft_delete can only be true")
|
||||
if self.completed is True and self.versions is None:
|
||||
raise ValueError("completion requires versions")
|
||||
if self.versions is not None:
|
||||
if set(self.versions) != set(self.task_ids):
|
||||
raise ValueError("versions must cover every task")
|
||||
if any(version < 1 for version in self.versions.values()):
|
||||
raise ValueError("versions must be positive")
|
||||
return self
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user