Compare commits

..

No commits in common. "ccd0178de1e9c8188ee7b0745a29e1fc7ab8fb2d" and "4980b2e7e522cdca56564d2545604687df5dd9d2" have entirely different histories.

4 changed files with 49 additions and 92 deletions

View File

@ -1,7 +1,5 @@
import json
from datetime import date, timedelta
from ..db import db, TaskGenerator, GeneratorType from ..db import db, TaskGenerator, GeneratorType
from .tasks import add_task import json
def get_generator(item_id: int) -> TaskGenerator: def get_generator(item_id: int) -> TaskGenerator:
@ -29,36 +27,12 @@ def add_generator(
return task return task
def generate_needed_tasks():
to_create = []
for g in get_generators():
next = g.next_at()
if not next:
continue
# TODO: make configurable
if date.today() - next > timedelta(days=14):
to_create.append(
{
"text": g.template.format(next=next),
"category": "recurring",
"due": next,
}
)
for c in to_create:
add_task(**c)
return to_create
def update_generator( def update_generator(
item_id: int, item_id: int,
**kwargs, **kwargs,
) -> TaskGenerator: ) -> TaskGenerator:
# replace "val" with JSON config = {"val": kwargs.pop("val")}
if "val" in kwargs: kwargs["config"] = json.dumps(config)
config = {"val": kwargs.pop("val")}
kwargs["config"] = json.dumps(config)
with db.atomic(): with db.atomic():
query = TaskGenerator.update(kwargs).where(TaskGenerator.id == item_id) query = TaskGenerator.update(kwargs).where(TaskGenerator.id == item_id)
query.execute() query.execute()

View File

@ -1,5 +1,4 @@
import json from datetime import datetime, timedelta
from datetime import date, timedelta, datetime
from enum import Enum from enum import Enum
from peewee import ( from peewee import (
BooleanField, BooleanField,
@ -47,6 +46,7 @@ class Category(BaseModel):
class Task(BaseModel): class Task(BaseModel):
# TODO: rename name
text = TextField() text = TextField()
status = CharField( status = CharField(
choices=[(status.value, status.name) for status in TaskStatus], choices=[(status.value, status.name) for status in TaskStatus],
@ -81,48 +81,32 @@ class TaskGenerator(BaseModel):
last_generated_at = DateTimeField(null=True) last_generated_at = DateTimeField(null=True)
created_at = DateTimeField(default=datetime.now) created_at = DateTimeField(default=datetime.now)
def next_at(self) -> datetime.date: def should_generate(self) -> bool:
if self.deleted: if self.deleted:
return None return False
if not self.last_generated_at:
return True
today = date.today() now = datetime.now()
val = int(json.loads(self.config)["val"]) if self.type == GeneratorType.DAYS_BETWEEN:
days_between = self.config["val"]
days_since = (now - self.last_generated_at).days
return days_since >= days_between
if self.type == GeneratorType.DAYS_BETWEEN.value: elif self.type == GeneratorType.MONTHLY:
if not self.last_generated_at: day_of_month = self.config["val"]
return today
return self.last_generated_at + timedelta(days=val)
elif self.type == GeneratorType.MONTHLY.value:
year, month, day = today.year, today.month, today.day
day_of_month = val
# if we need to go forward a month # check each day until now to see if target day occurred
if day_of_month < day: one_day = timedelta(days=1)
month += 1 check_date = self.last_generated_at + one_day
if month == 13: while check_date <= now:
month = 1 if check_date.day == day_of_month:
year += 1 return True
check_date += one_day
# recurring tasks on 29-31 in Feb will just happen on 28th return False
if day_of_month >= 29 and month == 2:
maybe_next = date(year, month, 28)
else:
maybe_next = date(year, month, day_of_month)
if not self.last_generated_at or self.last_generated_at < maybe_next: return False
return maybe_next
# TODO: this doesn't handle if a month was missed somehow, just advances one
# same logic as above, if we're stepping another month forward
month += 1
if month == 13:
month = 1
year += 1
if day_of_month >= 29 and month == 2:
maybe_next = date(year, month, 28)
else:
maybe_next = date(year, month, day_of_month)
return maybe_next
def initialize_db(): def initialize_db():
@ -131,3 +115,7 @@ def initialize_db():
if not Category.select().exists(): if not Category.select().exists():
Category.create(name="default") Category.create(name="default")
db.close() db.close()
if __name__ == "__main__":
initialize_db()

View File

@ -42,19 +42,17 @@ class TableColumnConfig:
display_name: str, display_name: str,
*, *,
default=None, default=None,
enum=None,
preprocessor=None,
enable_editor=False, enable_editor=False,
enum=None,
filterable=True, filterable=True,
read_only=False, preprocessor=None,
): ):
self.field = field self.field = field
self.display_name = display_name self.display_name = display_name
self.default = default self.default = default
self.enum = enum
self.enable_editor = enable_editor self.enable_editor = enable_editor
self.enum = enum
self.filterable = filterable self.filterable = filterable
self.read_only = read_only
if preprocessor: if preprocessor:
self.preprocessor = preprocessor self.preprocessor = preprocessor
elif self.enum: elif self.enum:
@ -225,8 +223,6 @@ class TableEditor(App):
# prepopulate with either the field default or the current filter # prepopulate with either the field default or the current filter
prepopulated = {} prepopulated = {}
for fc in self.TABLE_CONFIG: for fc in self.TABLE_CONFIG:
if fc.read_only:
continue
val = self.filters.get(fc.field, fc.default) val = self.filters.get(fc.field, fc.default)
if val is not None: if val is not None:
# enums use comma separated filters # enums use comma separated filters
@ -324,21 +320,16 @@ class TableEditor(App):
self.saved_cursor_pos = (self.table.cursor_row, self.table.cursor_column) self.saved_cursor_pos = (self.table.cursor_row, self.table.cursor_column)
def action_start_change(self): def action_start_change(self):
# invalid position
if self.table.cursor_row is None or self.table.cursor_column == 0: if self.table.cursor_row is None or self.table.cursor_column == 0:
return return
# check if editable
cconf = self._active_column_config()
if cconf.read_only:
return
self._save_cursor() self._save_cursor()
current_value = self.table.get_cell_at( current_value = self.table.get_cell_at(
(self.table.cursor_row, self.table.cursor_column) (self.table.cursor_row, self.table.cursor_column)
) )
if current_value.endswith(ELLIPSIS): if current_value.endswith(ELLIPSIS):
self.notify("multi-line text, use (e)dit") # TODO: flash message?
# need to edit with e
return return
current_value = remove_rich_tag(current_value) current_value = remove_rich_tag(current_value)
self._show_input("edit", current_value) self._show_input("edit", current_value)

View File

@ -1,19 +1,27 @@
import json import json
from datetime import datetime
from ..controller.generators import ( from ..controller.generators import (
get_generator, get_generator,
get_generators, get_generators,
add_generator, add_generator,
update_generator, update_generator,
generate_needed_tasks,
) )
from ..db import GeneratorType from ..db import GeneratorType
from .editor import ( from .editor import (
TableEditor, TableEditor,
TableColumnConfig, TableColumnConfig,
NotifyValidationError,
) )
def due_preprocessor(val):
try:
return datetime.strptime(val, "%Y-%m-%d")
except ValueError:
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
class TaskGenEditor(TableEditor): class TaskGenEditor(TableEditor):
TABLE_CONFIG = ( TABLE_CONFIG = (
TableColumnConfig("id", "ID"), TableColumnConfig("id", "ID"),
@ -24,8 +32,7 @@ class TaskGenEditor(TableEditor):
default=GeneratorType.DAYS_BETWEEN.value, default=GeneratorType.DAYS_BETWEEN.value,
enum=GeneratorType, enum=GeneratorType,
), ),
TableColumnConfig("val", "Value", default="1"), TableColumnConfig("val", "Value", default=""),
TableColumnConfig("next_at", "Next @", default="", read_only=True),
) )
def __init__(self): def __init__(self):
@ -35,20 +42,17 @@ class TaskGenEditor(TableEditor):
self.get_item_callback = get_generator self.get_item_callback = get_generator
def refresh_items(self): def refresh_items(self):
generated = generate_needed_tasks()
if num := len(generated):
self.notify(f"created {num} tasks")
items = get_generators() items = get_generators()
for item in items: for item in items:
self.table.add_row( self.table.add_row(
str(item.id), str(item.id), item.template, item.type, json.loads(item.config)["val"]
item.template,
item.type,
json.loads(item.config)["val"],
str(item.next_at()),
) )
def run(): def run():
app = TaskGenEditor() app = TaskGenEditor()
app.run() app.run()
if __name__ == "__main__":
run()