diff --git a/hamstertools/__init__.py b/hamstertools/__init__.py index 17f77dd..bcaa4b0 100755 --- a/hamstertools/__init__.py +++ b/hamstertools/__init__.py @@ -18,7 +18,7 @@ from .db import ( KimaiProject, KimaiActivity, HamsterActivityKimaiMapping, - HamsterFactKimaiImport + HamsterFactKimaiImport, ) HAMSTER_DIR = Path.home() / ".local/share/hamster" @@ -631,8 +631,15 @@ def db_(): @db_.command() def init(): - db.create_tables([KimaiCustomer, KimaiProject, KimaiActivity, - HamsterActivityKimaiMapping, HamsterFactKimaiImport]) + db.create_tables( + [ + KimaiCustomer, + KimaiProject, + KimaiActivity, + HamsterActivityKimaiMapping, + HamsterFactKimaiImport, + ] + ) @db_.command() @@ -665,10 +672,7 @@ def mapping2db(mapping_path=None, global_=False): try: kimai_activity = KimaiActivity.get(name=row[4], project_id=kimai_project.id) except KimaiActivity.DoesNotExist: - kimai_activity = KimaiActivity.get( - name=row[4], - project_id=None - ) + kimai_activity = KimaiActivity.get(name=row[4], project_id=None) HamsterActivityKimaiMapping.create( hamster_activity=hamster_activity, diff --git a/hamstertools/app.py b/hamstertools/app.py index b7d3b30..7c956c6 100644 --- a/hamstertools/app.py +++ b/hamstertools/app.py @@ -38,9 +38,15 @@ class ListScreen(Screen): with Vertical(): yield DataTable() with Horizontal(id="filter"): - yield Input(id="search", placeholder="Category/activity name contains text") - yield Input(id="date", - placeholder='After date, in {0} format'.format(datetime.now().strftime('%Y-%m-%d'))) + yield Input( + id="search", placeholder="Category/activity name contains text" + ) + yield Input( + id="date", + placeholder="After date, in {0} format".format( + datetime.now().strftime("%Y-%m-%d") + ), + ) yield Footer() def action_refresh(self) -> None: @@ -69,7 +75,7 @@ class ListScreen(Screen): self.table.focus() self._refresh() - @on(Input.Changed, '#filter Input') + @on(Input.Changed, "#filter Input") def filter(self, event): self._refresh() @@ -81,7 +87,7 @@ class ActivityEditScreen(ModalScreen): ] category_id = None - category_name = '' + category_name = "" def _get_categories(self, input_state): categories = [DropdownItem(c.name, str(c.id)) for c in HamsterCategory.select()] @@ -96,15 +102,20 @@ class ActivityEditScreen(ModalScreen): def compose(self) -> ComposeResult: yield Vertical( - Horizontal(Label("Category:"), + Horizontal( + Label("Category:"), AutoComplete( - Input(placeholder="Type to search...", id="category", - value=self.category_name), + Input( + placeholder="Type to search...", + id="category", + value=self.category_name, + ), Dropdown(items=self._get_categories), ), ), - Horizontal(Label("Activity:"), Input(value=self.activity_name, - id='activity')), + Horizontal( + Label("Activity:"), Input(value=self.activity_name, id="activity") + ), ) @on(Input.Submitted, "#category") @@ -129,7 +140,7 @@ class ActivityEditScreen(ModalScreen): self.dismiss( { "category": self.category_id, - "activity": self.query_one('#activity').value, + "activity": self.query_one("#activity").value, } ) @@ -340,18 +351,18 @@ class ActivityListScreen(ListScreen): .group_by(HamsterActivity) ) - filter_search = self.query_one('#filter #search').value + filter_search = self.query_one("#filter #search").value if filter_search is not None: activities = activities.where( HamsterActivity.name.contains(filter_search) | HamsterCategory.name.contains(filter_search) ) - filter_date = self.query_one('#filter #date').value + filter_date = self.query_one("#filter #date").value if filter_date is not None: try: activities = activities.where( - HamsterFact.start_time > datetime.strptime(filter_date, '%Y-%m-%d') + HamsterFact.start_time > datetime.strptime(filter_date, "%Y-%m-%d") ) except ValueError: pass @@ -431,15 +442,14 @@ class ActivityListScreen(ListScreen): def handle_edit(properties): if properties is None: return - activity.name = properties['activity'] - activity.category_id = properties['category'] + activity.name = properties["activity"] + activity.category_id = properties["category"] activity.save() self._refresh() - self.app.push_screen(ActivityEditScreen( - category=category, - activity=activity - ), handle_edit) + self.app.push_screen( + ActivityEditScreen(category=category, activity=activity), handle_edit + ) def action_mapping(self): selected_activity = ( @@ -490,26 +500,24 @@ class CategoryListScreen(ListScreen): categories = ( HamsterCategory.select( - HamsterCategory, + HamsterCategory, fn.Count(HamsterActivity.id).alias("activities_count"), - HamsterFact.start_time + HamsterFact.start_time, ) .join(HamsterActivity, JOIN.LEFT_OUTER) .join(HamsterFact, JOIN.LEFT_OUTER) .group_by(HamsterCategory) ) - filter_search = self.query_one('#filter #search').value + filter_search = self.query_one("#filter #search").value if filter_search is not None: - categories = categories.where( - HamsterCategory.name.contains(filter_search) - ) + categories = categories.where(HamsterCategory.name.contains(filter_search)) - filter_date = self.query_one('#filter #date').value + filter_date = self.query_one("#filter #date").value if filter_date is not None: try: categories = categories.where( - HamsterFact.start_time > datetime.strptime(filter_date, '%Y-%m-%d') + HamsterFact.start_time > datetime.strptime(filter_date, "%Y-%m-%d") ) except ValueError: pass @@ -613,7 +621,7 @@ class KimaiProjectListScreen(ListScreen): "id": project.id, "name": project.name, "customer_id": project.customer.id, - "allow_global_activities": project.allow_global_activities + "allow_global_activities": project.allow_global_activities, } for project in projects ] diff --git a/hamstertools/db.py b/hamstertools/db.py index a47d8c2..67ed5d6 100644 --- a/hamstertools/db.py +++ b/hamstertools/db.py @@ -1,12 +1,20 @@ from datetime import datetime import logging -from peewee import SqliteDatabase, Model, CharField, ForeignKeyField, DateTimeField, SmallIntegerField, BooleanField +from peewee import ( + SqliteDatabase, + Model, + CharField, + ForeignKeyField, + DateTimeField, + SmallIntegerField, + BooleanField, +) from textual.logging import TextualHandler -logger = logging.getLogger("peewee") -logger.addHandler(TextualHandler()) -logger.setLevel(logging.DEBUG) +# logger = logging.getLogger("peewee") +# logger.addHandler(TextualHandler()) +# logger.setLevel(logging.DEBUG) db = SqliteDatabase(None) diff --git a/hamstertools/kimai.py b/hamstertools/kimai.py index caa9640..48d0758 100644 --- a/hamstertools/kimai.py +++ b/hamstertools/kimai.py @@ -28,12 +28,14 @@ class KimaiAPI(object): self.user_json = self.get("users/me") def get(self, endpoint, params=None): - return requests.get(f"{self.KIMAI_API_URL}/{endpoint}", - params=params, headers=self.auth_headers).json() + return requests.get( + f"{self.KIMAI_API_URL}/{endpoint}", params=params, headers=self.auth_headers + ).json() def post(self, endpoint, data): - return requests.post(f"{self.KIMAI_API_URL}/{endpoint}", - json=data, headers=self.auth_headers) + return requests.post( + f"{self.KIMAI_API_URL}/{endpoint}", json=data, headers=self.auth_headers + ) class BaseAPI(object): @@ -72,9 +74,13 @@ class Project(BaseAPI): @staticmethod def list(api): return [ - Project(api, p["id"], p["name"], Customer.get_by_id(api, - p["customer"]), - p["globalActivities"]) + Project( + api, + p["id"], + p["name"], + Customer.get_by_id(api, p["customer"]), + p["globalActivities"], + ) for p in api.projects_json ] @@ -87,7 +93,7 @@ class Project(BaseAPI): value["id"], value["name"], Customer.get_by_id(api, value["customer"]), - value["globalActivities"] + value["globalActivities"], ) if not none: raise NotFound() @@ -143,19 +149,19 @@ class Timesheet(BaseAPI): Project.get_by_id(api, t["project"], none=True), t["begin"], t["end"], - t["id"], - t["description"], - t["tags"], + t["id"], + t["description"], + t["tags"], ) for t in api.get( - 'timesheets', + "timesheets", ) ] @staticmethod def get_by_id(api, id, none=False): t = api.get( - f'timesheets/{id}', + f"timesheets/{id}", ) return Timesheet( api, @@ -163,19 +169,22 @@ class Timesheet(BaseAPI): Project.get_by_id(api, t["project"], none=True), t["begin"], t["end"], - t["id"], - t["description"], - t["tags"], + t["id"], + t["description"], + t["tags"], ) def upload(self): - return self.api.post('timesheets', { - "begin": self.begin.isoformat(), - "end": self.end.isoformat(), - "project": self.project.id, - "activity": self.activity.id, - "description": self.description, - # FIXME: support multiple users - # "user": self., - "tags": self.tags, - }) + return self.api.post( + "timesheets", + { + "begin": self.begin.isoformat(), + "end": self.end.isoformat(), + "project": self.project.id, + "activity": self.activity.id, + "description": self.description, + # FIXME: support multiple users + # "user": self., + "tags": self.tags, + }, + ) diff --git a/scripts/apitest.py b/scripts/apitest.py index 1ffd641..266409a 100644 --- a/scripts/apitest.py +++ b/scripts/apitest.py @@ -1,5 +1,6 @@ import os import sys + sys.path.append(os.path.join(os.path.dirname(__file__), "..")) from datetime import datetime, timedelta @@ -17,7 +18,8 @@ api = KimaiAPI() # print(Timesheet.list(api)) -t = Timesheet(api, +t = Timesheet( + api, activity=Activity.get_by_id(api, 613), project=Project.get_by_id(api, 233), begin=datetime.now() - timedelta(minutes=10), diff --git a/scripts/upload.py b/scripts/upload.py index 4431632..fdeb772 100644 --- a/scripts/upload.py +++ b/scripts/upload.py @@ -1,5 +1,6 @@ import os import sys + sys.path.append(os.path.join(os.path.dirname(__file__), "..")) from datetime import datetime @@ -11,22 +12,19 @@ from hamstertools.kimai import * api = KimaiAPI() -DATE_FROM='2023-10-01' -DATE_TO='2023-11-01' -SEARCH='auto' +DATE_FROM = "2023-10-01" +DATE_TO = "2023-11-01" +SEARCH = "auto" -facts = HamsterFact.select( - HamsterFact, - HamsterActivity, - HamsterCategory -).join( - HamsterActivity, JOIN.LEFT_OUTER -).join( - HamsterCategory, JOIN.LEFT_OUTER -).where( - (HamsterFact.start_time > datetime.strptime(DATE_FROM, "%Y-%m-%d")) - & (HamsterFact.start_time < datetime.strptime(DATE_TO, "%Y-%m-%d")) - & HamsterCategory.name.contains(SEARCH) +facts = ( + HamsterFact.select(HamsterFact, HamsterActivity, HamsterCategory) + .join(HamsterActivity, JOIN.LEFT_OUTER) + .join(HamsterCategory, JOIN.LEFT_OUTER) + .where( + (HamsterFact.start_time > datetime.strptime(DATE_FROM, "%Y-%m-%d")) + & (HamsterFact.start_time < datetime.strptime(DATE_TO, "%Y-%m-%d")) + & HamsterCategory.name.contains(SEARCH) + ) ) has_errors = False @@ -35,15 +33,24 @@ has_errors = False for f in facts: mappings = f.activity.mappings if len(mappings) == 0: - print(f'fact {f.id}: @{f.activity.category.id} {f.activity.category.name} » @{f.activity.id} {f.activity.name} has no mapping') + print( + f"fact {f.id}: @{f.activity.category.id} {f.activity.category.name} » @{f.activity.id} {f.activity.name} has no mapping" + ) has_errors = True continue if len(mappings) > 1: - print(f'fact {f.id}: activity @{f.activity.id} {f.activity.name} has multiple mappings') + print( + f"fact {f.id}: activity @{f.activity.id} {f.activity.name} has multiple mappings" + ) has_errors = True continue - if mappings[0].kimai_activity.project is None and not mappings[0].kimai_project.allow_global_activities: - print(f'fact {f.id}: project @{mappings[0].kimai_project.id} {mappings[0].kimai_project.name} does not allow global activity {mappings[0].kimai_activity}') + if ( + mappings[0].kimai_activity.project is None + and not mappings[0].kimai_project.allow_global_activities + ): + print( + f"fact {f.id}: project @{mappings[0].kimai_project.id} {mappings[0].kimai_project.name} does not allow global activity {mappings[0].kimai_activity}" + ) has_errors = True continue @@ -55,25 +62,27 @@ for f in facts: try: mapping = f.activity.mappings[0] except IndexError: - print(f"no mapping, skipping {f.id} ({f.activity.category.name} » {f.activity.name})") + print( + f"no mapping, skipping {f.id} ({f.activity.category.name} » {f.activity.name})" + ) continue - t = Timesheet(api, + t = Timesheet( + api, activity=mapping.kimai_activity, project=mapping.kimai_project, begin=f.start_time, end=f.end_time, - description=f.description if f.description != '' else mapping.kimai_description, + description=f.description if f.description != "" else mapping.kimai_description, # tags=f.tags if f.tags != '' else mapping.kimai_tags ) r = t.upload().json() if r.get("code", 200) != 200: print(r) print(f"{f.id} ({f.activity.category.name} » {f.activity.name})") - from pdb import set_trace; set_trace() + from pdb import set_trace + + set_trace() else: - HamsterFactKimaiImport.create( - hamster_fact=f, - kimai_id=r['id'] - ).save() + HamsterFactKimaiImport.create(hamster_fact=f, kimai_id=r["id"]).save() print(f'Created Kimai timesheet {r["id"]}')