From 14e021db0de693b06c02ff50a4d8ea3d78be5fd4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 29 Jan 2022 19:36:17 -0600 Subject: [PATCH 01/22] Cleanup views for Harvest Projects --- tailbone_harvest/views/harvest/master.py | 19 +++++++++++++++- tailbone_harvest/views/harvest/projects.py | 26 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/tailbone_harvest/views/harvest/master.py b/tailbone_harvest/views/harvest/master.py index 5d206ac..96b0537 100644 --- a/tailbone_harvest/views/harvest/master.py +++ b/tailbone_harvest/views/harvest/master.py @@ -24,6 +24,8 @@ Harvest master view """ +from rattail_harvest.db.model import HarvestTimeEntry + from tailbone.views import MasterView @@ -33,9 +35,24 @@ class HarvestMasterView(MasterView): """ creatable = False editable = False - deletable = False has_versions = True + model_row_class = HarvestTimeEntry labels = { 'id': "ID", } + + row_labels = { + 'id': "ID", + } + + def configure_form(self, f): + super(HarvestMasterView, self).configure_form(f) + f.remove('time_entries') + + def configure_row_grid(self, g): + super(HarvestMasterView, self).configure_row_grid(g) + g.set_sort_defaults('spent_date', 'desc') + + def row_view_action_url(self, entry, i): + return self.request.route_url('harvest.time_entries.view', uuid=entry.uuid) diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index 30b8a9d..cca48a6 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -37,6 +37,8 @@ class HarvestProjectView(HarvestMasterView): url_prefix = '/harvest/projects' route_prefix = 'harvest.projects' + has_rows = True + grid_columns = [ 'id', 'client', @@ -49,6 +51,15 @@ class HarvestProjectView(HarvestMasterView): 'fee', ] + row_grid_columns = [ + 'id', + 'spent_date', + 'user', + 'client', + 'task', + 'hours', + ] + def configure_grid(self, g): super(HarvestProjectView, self).configure_grid(g) model = self.model @@ -59,6 +70,9 @@ class HarvestProjectView(HarvestMasterView): g.filters['client'].default_active = True g.filters['client'].default_verb = 'contains' + g.filters['is_active'].default_active = True + g.filters['is_active'].default_verb = 'is_true' + g.set_type('hourly_rate', 'currency') g.set_type('fee', 'currency') @@ -69,6 +83,18 @@ class HarvestProjectView(HarvestMasterView): g.set_link('name') g.set_link('code') + def grid_extra_class(self, project, i): + if not project.is_active: + return 'warning' + + def get_row_data(self, project): + model = self.model + return self.Session.query(model.HarvestTimeEntry)\ + .filter(model.HarvestTimeEntry.project == project) + + def get_parent(self, entry): + return entry.project + def includeme(config): HarvestProjectView.defaults(config) From b8fd9c302289ef7caa2a8f13b8a2d31bdf56eee4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 30 Jan 2022 12:38:15 -0600 Subject: [PATCH 02/22] Allow editing of Harvest cache data i needed it to fix some data sync.. --- tailbone_harvest/views/harvest/master.py | 6 +++++- tailbone_harvest/views/harvest/projects.py | 11 +++++++++++ tailbone_harvest/views/harvest/time_entries.py | 8 -------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/tailbone_harvest/views/harvest/master.py b/tailbone_harvest/views/harvest/master.py index 96b0537..35c2286 100644 --- a/tailbone_harvest/views/harvest/master.py +++ b/tailbone_harvest/views/harvest/master.py @@ -34,12 +34,16 @@ class HarvestMasterView(MasterView): Base class for Harvest master views """ creatable = False - editable = False has_versions = True model_row_class = HarvestTimeEntry labels = { 'id': "ID", + 'user_id': "User ID", + 'client_id': "Client ID", + 'project_id': "Project ID", + 'task_id': "Task ID", + 'invoice_id': "Invoice ID", } row_labels = { diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index cca48a6..9966801 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -87,6 +87,17 @@ class HarvestProjectView(HarvestMasterView): if not project.is_active: return 'warning' + def configure_form(self, f): + super(HarvestProjectView, self).configure_form(f) + + if self.editing: + f.remove('client') + f.set_type('over_budget_notification_date', 'date_jquery') + f.set_type('starts_on', 'date_jquery') + f.set_type('ends_on', 'date_jquery') + f.set_readonly('created_at') + f.set_readonly('updated_at') + def get_row_data(self, project): model = self.model return self.Session.query(model.HarvestTimeEntry)\ diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index 3ddfdab..8001fab 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -37,14 +37,6 @@ class HarvestTimeEntryView(HarvestMasterView): url_prefix = '/harvest/time-entries' route_prefix = 'harvest.time_entries' - labels = { - 'user_id': "User ID", - 'client_id': "Client ID", - 'project_id': "Project ID", - 'task_id': "Task ID", - 'invoice_id': "Invoice ID", - } - grid_columns = [ 'id', 'spent_date', From a270d1dcc24af0123359c9a42eac285c286005ad Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 30 Jan 2022 17:41:17 -0600 Subject: [PATCH 03/22] Expose `HarvestUser.person` for editing --- tailbone_harvest/views/harvest/users.py | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index 8f87fb0..5fcc3ff 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -26,6 +26,9 @@ Harvest User views from rattail_harvest.db.model import HarvestUser +import colander + +from tailbone import forms from .master import HarvestMasterView @@ -49,6 +52,10 @@ class HarvestUserView(HarvestMasterView): def configure_grid(self, g): super(HarvestUserView, self).configure_grid(g) + model = self.model + + g.set_joiner('person_name', lambda q: q.outerjoin(model.Person)) + g.set_filter('person_name', model.Person.display_name) g.set_sort_defaults('first_name') @@ -59,10 +66,45 @@ class HarvestUserView(HarvestMasterView): def configure_form(self, f): super(HarvestUserView, self).configure_form(f) + model = self.model + user = f.model_instance + # person + f.set_renderer('person', self.render_person) + if self.creating or self.editing: + if 'person' in f.fields: + f.remove('person_uuid') + f.replace('person', 'person_uuid') + person_display = "" + if self.request.method == 'POST': + if self.request.POST.get('person_uuid'): + person = self.Session.query(model.Person).get(self.request.POST['person_uuid']) + if person: + person_display = str(person) + elif self.editing: + person_display = str(user.person or '') + people_url = self.request.route_url('people.autocomplete') + f.set_widget('person_uuid', forms.widgets.JQueryAutocompleteWidget( + field_display=person_display, service_url=people_url)) + f.set_validator('person_uuid', self.valid_person) + f.set_label('person_uuid', "Person") + + # timestamps + if self.creating or self.editing: + f.remove('created_at') + f.remove('updated_at') + + # time_entries # TODO: should add this as child rows/grid instead f.remove('time_entries') + def valid_person(self, node, value): + model = self.model + if value: + person = self.Session.query(model.Person).get(value) + if not person: + raise colander.Invalid(node, "Person not found (you must *select* a record)") + def includeme(config): HarvestUserView.defaults(config) From 4bad4e262c80df6bbc4a135e80033173de05f494 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 9 Feb 2022 19:05:55 -0600 Subject: [PATCH 04/22] Be smarter about showing non-active Harvest Clients --- tailbone_harvest/views/harvest/clients.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tailbone_harvest/views/harvest/clients.py b/tailbone_harvest/views/harvest/clients.py index 1f7be2b..58fe842 100644 --- a/tailbone_harvest/views/harvest/clients.py +++ b/tailbone_harvest/views/harvest/clients.py @@ -51,11 +51,18 @@ class HarvestClientView(HarvestMasterView): g.filters['name'].default_active = True g.filters['name'].default_verb = 'contains' + g.filters['is_active'].default_active = True + g.filters['is_active'].default_verb = 'is_true' + g.set_sort_defaults('name') g.set_link('id') g.set_link('name') + def grid_extra_class(self, client, i): + if not client.is_active: + return 'warning' + def configure_form(self, f): super(HarvestClientView, self).configure_form(f) From d35baf15f6546d091341c36b30d6021bbf8799f8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 11 Feb 2022 19:16:47 -0600 Subject: [PATCH 05/22] Add xref helper utils template --- tailbone_harvest/templates/harvest-util.mako | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tailbone_harvest/templates/harvest-util.mako diff --git a/tailbone_harvest/templates/harvest-util.mako b/tailbone_harvest/templates/harvest-util.mako new file mode 100644 index 0000000..907cc96 --- /dev/null +++ b/tailbone_harvest/templates/harvest-util.mako @@ -0,0 +1,23 @@ +## -*- coding: utf-8; -*- + +<%def name="render_xref_buttons()"> + + View in Harvest + + + +<%def name="render_xref_helper()"> + + From cc48cf50132abe823bdd72cff88e550c60e6d7a2 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 11 Feb 2022 21:07:58 -0600 Subject: [PATCH 06/22] Improve filter sequence for Harvest Projects grid --- tailbone_harvest/views/harvest/projects.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index 9966801..6e8f5de 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -78,6 +78,12 @@ class HarvestProjectView(HarvestMasterView): g.set_sort_defaults('client') + g.set_filters_sequence([ + 'id', + 'name', + 'client', + ]) + g.set_link('id') g.set_link('client') g.set_link('name') From f3e05124c393f6bfe2a5494481c903e727d94332 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 3 Mar 2022 19:33:01 -0600 Subject: [PATCH 07/22] Use new config syntax for all Harvest views --- tailbone_harvest/views/harvest/clients.py | 9 ++++++++- tailbone_harvest/views/harvest/projects.py | 9 ++++++++- tailbone_harvest/views/harvest/tasks.py | 9 ++++++++- tailbone_harvest/views/harvest/time_entries.py | 9 ++++++++- tailbone_harvest/views/harvest/users.py | 9 ++++++++- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tailbone_harvest/views/harvest/clients.py b/tailbone_harvest/views/harvest/clients.py index 58fe842..08d39c7 100644 --- a/tailbone_harvest/views/harvest/clients.py +++ b/tailbone_harvest/views/harvest/clients.py @@ -85,5 +85,12 @@ class HarvestClientView(HarvestMasterView): return HTML.tag('ul', c=items) -def includeme(config): +def defaults(config, **kwargs): + base = globals() + + HarvestClientView = kwargs.get('HarvestClientView', base['HarvestClientView']) HarvestClientView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index 6e8f5de..bf02fcc 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -113,5 +113,12 @@ class HarvestProjectView(HarvestMasterView): return entry.project -def includeme(config): +def defaults(config, **kwargs): + base = globals() + + HarvestProjectView = kwargs.get('HarvestProjectView', base['HarvestProjectView']) HarvestProjectView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/tailbone_harvest/views/harvest/tasks.py b/tailbone_harvest/views/harvest/tasks.py index a3b61b4..0bdf7af 100644 --- a/tailbone_harvest/views/harvest/tasks.py +++ b/tailbone_harvest/views/harvest/tasks.py @@ -61,5 +61,12 @@ class HarvestTaskView(HarvestMasterView): f.remove_field('time_entries') -def includeme(config): +def defaults(config, **kwargs): + base = globals() + + HarvestTaskView = kwargs.get('HarvestTaskView', base['HarvestTaskView']) HarvestTaskView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index 8001fab..5a3a746 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -61,5 +61,12 @@ class HarvestTimeEntryView(HarvestMasterView): g.set_link('notes') -def includeme(config): +def defaults(config, **kwargs): + base = globals() + + HarvestTimeEntryView = kwargs.get('HarvestTimeEntryView', base['HarvestTimeEntryView']) HarvestTimeEntryView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index 5fcc3ff..16a0ca0 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -106,5 +106,12 @@ class HarvestUserView(HarvestMasterView): raise colander.Invalid(node, "Person not found (you must *select* a record)") -def includeme(config): +def defaults(config, **kwargs): + base = globals() + + HarvestUserView = kwargs.get('HarvestUserView', base['HarvestUserView']) HarvestUserView.defaults(config) + + +def includeme(config): + defaults(config) From 8da3f895240491effb9f3286519aab583c3ff323 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 9 Mar 2022 19:43:18 -0600 Subject: [PATCH 08/22] Misc. tweaks to improve viewing Harvest cache records --- tailbone_harvest/templates/.keepme | 0 .../templates/harvest/users/view.mako | 16 ++++++++++ tailbone_harvest/views/harvest/master.py | 30 +++++++++++++++++++ tailbone_harvest/views/harvest/projects.py | 2 ++ .../views/harvest/time_entries.py | 28 +++++++++++++++++ tailbone_harvest/views/harvest/users.py | 13 ++++++++ 6 files changed, 89 insertions(+) delete mode 100644 tailbone_harvest/templates/.keepme create mode 100644 tailbone_harvest/templates/harvest/users/view.mako diff --git a/tailbone_harvest/templates/.keepme b/tailbone_harvest/templates/.keepme deleted file mode 100644 index e69de29..0000000 diff --git a/tailbone_harvest/templates/harvest/users/view.mako b/tailbone_harvest/templates/harvest/users/view.mako new file mode 100644 index 0000000..245deff --- /dev/null +++ b/tailbone_harvest/templates/harvest/users/view.mako @@ -0,0 +1,16 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/view.mako" /> + +<%def name="page_content()"> + + % if instance.avatar_url: +
+ +
+ % endif + + ${parent.page_content()} + + + +${parent.body()} diff --git a/tailbone_harvest/views/harvest/master.py b/tailbone_harvest/views/harvest/master.py index 35c2286..a50bdba 100644 --- a/tailbone_harvest/views/harvest/master.py +++ b/tailbone_harvest/views/harvest/master.py @@ -26,6 +26,8 @@ Harvest master view from rattail_harvest.db.model import HarvestTimeEntry +from webhelpers2.html import tags + from tailbone.views import MasterView @@ -54,6 +56,34 @@ class HarvestMasterView(MasterView): super(HarvestMasterView, self).configure_form(f) f.remove('time_entries') + def render_harvest_user(self, obj, field): + user = getattr(obj, field) + if user: + text = str(user) + url = self.request.route_url('harvest.users.view', uuid=user.uuid) + return tags.link_to(text, url) + + def render_harvest_client(self, obj, field): + client = getattr(obj, field) + if client: + text = str(client) + url = self.request.route_url('harvest.clients.view', uuid=client.uuid) + return tags.link_to(text, url) + + def render_harvest_project(self, obj, field): + project = getattr(obj, field) + if project: + text = str(project) + url = self.request.route_url('harvest.projects.view', uuid=project.uuid) + return tags.link_to(text, url) + + def render_harvest_task(self, obj, field): + task = getattr(obj, field) + if task: + text = str(task) + url = self.request.route_url('harvest.tasks.view', uuid=task.uuid) + return tags.link_to(text, url) + def configure_row_grid(self, g): super(HarvestMasterView, self).configure_row_grid(g) g.set_sort_defaults('spent_date', 'desc') diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index bf02fcc..9de2010 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -96,6 +96,8 @@ class HarvestProjectView(HarvestMasterView): def configure_form(self, f): super(HarvestProjectView, self).configure_form(f) + f.set_type('hourly_rate', 'currency') + if self.editing: f.remove('client') f.set_type('over_budget_notification_date', 'date_jquery') diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index 5a3a746..28eb66b 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -60,6 +60,34 @@ class HarvestTimeEntryView(HarvestMasterView): g.set_link('client') g.set_link('notes') + def configure_form(self, f): + super(HarvestTimeEntryView, self).configure_form(f) + + # make sure id is first field + f.remove('id') + f.insert(0, 'id') + + # user + f.remove('user_id') + f.set_renderer('user', self.render_harvest_user) + + # client + f.remove('client_id') + f.set_renderer('client', self.render_harvest_client) + + # project + f.remove('project_id') + f.set_renderer('project', self.render_harvest_project) + + # task + f.remove('task_id') + f.set_renderer('task', self.render_harvest_task) + + f.set_type('notes', 'text') + + f.set_type('billable_rate', 'currency') + f.set_type('cost_rate', 'currency') + def defaults(config, **kwargs): base = globals() diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index 16a0ca0..f38ead7 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -40,6 +40,10 @@ class HarvestUserView(HarvestMasterView): url_prefix = '/harvest/users' route_prefix = 'harvest.users' + labels = { + 'avatar_url': "Avatar URL", + } + grid_columns = [ 'id', 'first_name', @@ -88,6 +92,15 @@ class HarvestUserView(HarvestMasterView): field_display=person_display, service_url=people_url)) f.set_validator('person_uuid', self.valid_person) f.set_label('person_uuid', "Person") + else: + f.remove('person_uuid') + + f.set_type('weekly_capacity', 'duration') + + f.set_type('default_hourly_rate', 'currency') + f.set_type('cost_rate', 'currency') + + f.set_renderer('avatar_url', self.render_url) # timestamps if self.creating or self.editing: From 15ea726b9b74eea7bc164c1498191af07826f26e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 18 Jan 2023 13:11:26 -0600 Subject: [PATCH 09/22] Make all Harvest models touchable --- tailbone_harvest/views/harvest/master.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tailbone_harvest/views/harvest/master.py b/tailbone_harvest/views/harvest/master.py index a50bdba..8e5fc97 100644 --- a/tailbone_harvest/views/harvest/master.py +++ b/tailbone_harvest/views/harvest/master.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -36,6 +36,7 @@ class HarvestMasterView(MasterView): Base class for Harvest master views """ creatable = False + touchable = True has_versions = True model_row_class = HarvestTimeEntry From 4c64dbc536537808f3c75a1973ff4a4d406c5710 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 11 Feb 2023 22:09:24 -0600 Subject: [PATCH 10/22] Refactor `Query.get()` => `Session.get()` per SQLAlchemy 1.4 --- tailbone_harvest/views/harvest/users.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index f38ead7..cdebc3a 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -82,7 +82,8 @@ class HarvestUserView(HarvestMasterView): person_display = "" if self.request.method == 'POST': if self.request.POST.get('person_uuid'): - person = self.Session.query(model.Person).get(self.request.POST['person_uuid']) + person = self.Session.get(model.Person, + self.request.POST['person_uuid']) if person: person_display = str(person) elif self.editing: @@ -114,7 +115,7 @@ class HarvestUserView(HarvestMasterView): def valid_person(self, node, value): model = self.model if value: - person = self.Session.query(model.Person).get(value) + person = self.Session.get(model.Person, value) if not person: raise colander.Invalid(node, "Person not found (you must *select* a record)") From fc70b3c8ae2f13b5782d5363fa65bb0aa9fb60fe Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 25 Mar 2023 13:21:02 -0500 Subject: [PATCH 11/22] Add xref buttons for all Harvest views --- tailbone_harvest/views/harvest/clients.py | 16 +++++++++++++++- tailbone_harvest/views/harvest/projects.py | 16 +++++++++++++++- tailbone_harvest/views/harvest/tasks.py | 16 +++++++++++++++- .../views/harvest/time_entries.py | 19 ++++++++++++++++++- tailbone_harvest/views/harvest/users.py | 13 +++++++++++++ 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/tailbone_harvest/views/harvest/clients.py b/tailbone_harvest/views/harvest/clients.py index 08d39c7..44a399e 100644 --- a/tailbone_harvest/views/harvest/clients.py +++ b/tailbone_harvest/views/harvest/clients.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -25,6 +25,7 @@ Harvest Client views """ from rattail_harvest.db.model import HarvestClient +from rattail_harvest.harvest.config import get_harvest_url from webhelpers2.html import HTML, tags @@ -84,6 +85,19 @@ class HarvestClientView(HarvestMasterView): items.append(HTML.tag('li', c=[tags.link_to(text, url)])) return HTML.tag('ul', c=items) + def get_xref_buttons(self, client): + buttons = super(HarvestClientView, self).get_xref_buttons(client) + model = self.model + + # harvest proper + url = get_harvest_url(self.rattail_config) + if url: + url = '{}/clients'.format(url) + buttons.append(self.make_xref_button(url=url, + text="View in Harvest")) + + return buttons + def defaults(config, **kwargs): base = globals() diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index 9de2010..ae36090 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -25,6 +25,7 @@ Harvest Project views """ from rattail_harvest.db.model import HarvestProject +from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView @@ -106,6 +107,19 @@ class HarvestProjectView(HarvestMasterView): f.set_readonly('created_at') f.set_readonly('updated_at') + def get_xref_buttons(self, project): + buttons = super(HarvestProjectView, self).get_xref_buttons(project) + model = self.model + + # harvest + url = get_harvest_url(self.rattail_config) + if url: + url = '{}/projects/{}'.format(url, project.id) + buttons.append(self.make_xref_button(url=url, + text="View in Harvest")) + + return buttons + def get_row_data(self, project): model = self.model return self.Session.query(model.HarvestTimeEntry)\ diff --git a/tailbone_harvest/views/harvest/tasks.py b/tailbone_harvest/views/harvest/tasks.py index 0bdf7af..17cf6d6 100644 --- a/tailbone_harvest/views/harvest/tasks.py +++ b/tailbone_harvest/views/harvest/tasks.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -25,6 +25,7 @@ Harvest Task views """ from rattail_harvest.db.model import HarvestTask +from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView @@ -60,6 +61,19 @@ class HarvestTaskView(HarvestMasterView): # time_entries f.remove_field('time_entries') + def get_xref_buttons(self, task): + buttons = super(HarvestTaskView, self).get_xref_buttons(task) + model = self.model + + # harvest + url = get_harvest_url(self.rattail_config) + if url: + url = '{}/tasks'.format(url) + buttons.append(self.make_xref_button(url=url, + text="View in Harvest")) + + return buttons + def defaults(config, **kwargs): base = globals() diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index 28eb66b..7c85f8e 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -25,6 +25,7 @@ Harvest Time Entry views """ from rattail_harvest.db.model import HarvestTimeEntry +from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView @@ -88,6 +89,22 @@ class HarvestTimeEntryView(HarvestMasterView): f.set_type('billable_rate', 'currency') f.set_type('cost_rate', 'currency') + def get_xref_buttons(self, entry): + buttons = super(HarvestTimeEntryView, self).get_xref_buttons(entry) + model = self.model + + # harvest + url = get_harvest_url(self.rattail_config) + if url: + url = '{}/time/day/{}/{}'.format( + url, + entry.spent_date.strftime('%Y/%m/%d'), + entry.user_id) + buttons.append(self.make_xref_button(url=url, + text="View in Harvest")) + + return buttons + def defaults(config, **kwargs): base = globals() diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index cdebc3a..54b8dd1 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -25,6 +25,7 @@ Harvest User views """ from rattail_harvest.db.model import HarvestUser +from rattail_harvest.harvest.config import get_harvest_url import colander @@ -119,6 +120,18 @@ class HarvestUserView(HarvestMasterView): if not person: raise colander.Invalid(node, "Person not found (you must *select* a record)") + def get_xref_buttons(self, user): + buttons = super(HarvestUserView, self).get_xref_buttons(user) + model = self.model + + # harvest proper + url = get_harvest_url(self.rattail_config) + if url: + url = '{}/team'.format(url) + buttons.append(self.make_xref_button(url=url, text="View in Harvest")) + + return buttons + def defaults(config, **kwargs): base = globals() From a1c9199416f4044b2d75e3b84b43460e29641983 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 May 2023 13:06:47 -0500 Subject: [PATCH 12/22] Replace `setup.py` contents with `setup.cfg` --- setup.cfg | 31 +++++++++++++++++++++++ setup.py | 73 +++---------------------------------------------------- 2 files changed, 34 insertions(+), 70 deletions(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..928158a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +# -*- coding: utf-8; -*- + +[metadata] +name = tailbone-harvest +version = attr: tailbone_harvest.__version__ +author = Lance Edgar +author_email = lance@edbob.org +url = https://rattailproject.org/ +description = Tailbone integration package for Harvest +long_description = file: README.rst +classifiers = + Development Status :: 3 - Alpha + Environment :: Web Environment + Intended Audience :: Developers + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Natural Language :: English + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Topic :: Office/Business + Topic :: Software Development :: Libraries :: Python Modules + + +[options] +install_requires = + invoke + rattail-harvest + Tailbone + +packages = find: +include_package_data = True diff --git a/setup.py b/setup.py index d333d3f..3b73b9a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -24,73 +24,6 @@ tailbone-harvest setup script """ -import os -from setuptools import setup, find_packages +from setuptools import setup - -here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'tailbone_harvest', '_version.py')).read()) -README = open(os.path.join(here, 'README.rst')).read() - - -requires = [ - # - # Version numbers within comments below have specific meanings. - # Basically the 'low' value is a "soft low," and 'high' a "soft high." - # In other words: - # - # If either a 'low' or 'high' value exists, the primary point to be - # made about the value is that it represents the most current (stable) - # version available for the package (assuming typical public access - # methods) whenever this project was started and/or documented. - # Therefore: - # - # If a 'low' version is present, you should know that attempts to use - # versions of the package significantly older than the 'low' version - # may not yield happy results. (A "hard" high limit may or may not be - # indicated by a true version requirement.) - # - # Similarly, if a 'high' version is present, and especially if this - # project has laid dormant for a while, you may need to refactor a bit - # when attempting to support a more recent version of the package. (A - # "hard" low limit should be indicated by a true version requirement - # when a 'high' version is present.) - # - # In any case, developers and other users are encouraged to play - # outside the lines with regard to these soft limits. If bugs are - # encountered then they should be filed as such. - # - # package # low high - - 'invoke', # 1.5.0 - 'rattail-harvest', # 0.1.0 - 'Tailbone', # 0.8.199 -] - - -setup( - name = "tailbone-harvest", - version = __version__, - author = "Lance Edgar", - author_email = "lance@edbob.org", - url = "https://rattailproject.org/", - description = "Tailbone integration package for Harvest", - long_description = README, - - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Office/Business', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - - install_requires = requires, - packages = find_packages(), - include_package_data = True, -) +setup() From f388f2d6cfcc8d39acf277f1b3740732e82672fe Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 4 Oct 2023 13:10:28 -0500 Subject: [PATCH 13/22] Add button to re-import Harvest cache time entry from API --- .../templates/harvest/time-entries/view.mako | 45 +++++++++++++++++ .../views/harvest/time_entries.py | 48 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tailbone_harvest/templates/harvest/time-entries/view.mako diff --git a/tailbone_harvest/templates/harvest/time-entries/view.mako b/tailbone_harvest/templates/harvest/time-entries/view.mako new file mode 100644 index 0000000..3b36ff5 --- /dev/null +++ b/tailbone_harvest/templates/harvest/time-entries/view.mako @@ -0,0 +1,45 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/view.mako" /> + +<%def name="object_helpers()"> + ${parent.object_helpers()} + ${self.render_import_helper()} + + +<%def name="render_import_helper()"> + % if master.has_perm('import_from_harvest'): + + % endif + + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + % if master.has_perm('import_from_harvest'): + + % endif + + + +${parent.body()} diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index 7c85f8e..b4b67f4 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -62,7 +62,7 @@ class HarvestTimeEntryView(HarvestMasterView): g.set_link('notes') def configure_form(self, f): - super(HarvestTimeEntryView, self).configure_form(f) + super().configure_form(f) # make sure id is first field f.remove('id') @@ -84,11 +84,20 @@ class HarvestTimeEntryView(HarvestMasterView): f.remove('task_id') f.set_renderer('task', self.render_harvest_task) + # hours + f.set_renderer('hours', self.render_hours) + f.set_type('notes', 'text') f.set_type('billable_rate', 'currency') f.set_type('cost_rate', 'currency') + def render_hours(self, entry, field): + hours = getattr(entry, field) + app = self.get_rattail_app() + duration = app.render_duration(hours=hours) + return f"{hours} ({duration})" + def get_xref_buttons(self, entry): buttons = super(HarvestTimeEntryView, self).get_xref_buttons(entry) model = self.model @@ -105,6 +114,43 @@ class HarvestTimeEntryView(HarvestMasterView): return buttons + def import_from_harvest(self): + app = self.get_rattail_app() + handler = app.get_import_handler('to_rattail.from_harvest.import', require=True) + importer = handler.get_importer('HarvestTimeEntry') + importer.session = self.Session() + importer.setup() + + cache_entry = self.get_instance() + if self.oneoff_import(importer, local_object=cache_entry): + self.request.session.flash(f"{self.get_model_title()} has been " + f"(re-)imported from Harvest: {cache_entry}") + else: + self.request.session.flash("Import failed!", 'error') + + return self.redirect(self.get_action_url('view', cache_entry)) + + @classmethod + def defaults(cls, config): + route_prefix = cls.get_route_prefix() + instance_url_prefix = cls.get_instance_url_prefix() + permission_prefix = cls.get_permission_prefix() + model_title = cls.get_model_title() + + # normal defaults + cls._defaults(config) + + # import from harvest + config.add_tailbone_permission(permission_prefix, + f'{permission_prefix}.import_from_harvest', + f"Re-Import {model_title} from Harvest") + config.add_route(f'{route_prefix}.import_from_harvest', + f'{instance_url_prefix}/import-from-harvest', + request_method='POST') + config.add_view(cls, attr='import_from_harvest', + route_name=f'{route_prefix}.import_from_harvest', + permission=f'{permission_prefix}.import_from_harvest') + def defaults(config, **kwargs): base = globals() From eb904aea3b6b4df0e4837583e79c76d625ee3b59 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 4 Oct 2023 15:56:02 -0500 Subject: [PATCH 14/22] Refactor per rename of Harvest cache models --- tailbone_harvest/views/harvest/clients.py | 16 ++++++------ tailbone_harvest/views/harvest/master.py | 4 +-- tailbone_harvest/views/harvest/projects.py | 26 +++++++++---------- tailbone_harvest/views/harvest/tasks.py | 16 ++++++------ .../views/harvest/time_entries.py | 16 ++++++------ tailbone_harvest/views/harvest/users.py | 16 ++++++------ 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tailbone_harvest/views/harvest/clients.py b/tailbone_harvest/views/harvest/clients.py index 44a399e..d80751d 100644 --- a/tailbone_harvest/views/harvest/clients.py +++ b/tailbone_harvest/views/harvest/clients.py @@ -24,7 +24,7 @@ Harvest Client views """ -from rattail_harvest.db.model import HarvestClient +from rattail_harvest.db.model import HarvestCacheClient from rattail_harvest.harvest.config import get_harvest_url from webhelpers2.html import HTML, tags @@ -32,11 +32,11 @@ from webhelpers2.html import HTML, tags from .master import HarvestMasterView -class HarvestClientView(HarvestMasterView): +class HarvestCacheClientView(HarvestMasterView): """ Master view for Harvest Clients """ - model_class = HarvestClient + model_class = HarvestCacheClient url_prefix = '/harvest/clients' route_prefix = 'harvest.clients' @@ -47,7 +47,7 @@ class HarvestClientView(HarvestMasterView): ] def configure_grid(self, g): - super(HarvestClientView, self).configure_grid(g) + super().configure_grid(g) g.filters['name'].default_active = True g.filters['name'].default_verb = 'contains' @@ -65,7 +65,7 @@ class HarvestClientView(HarvestMasterView): return 'warning' def configure_form(self, f): - super(HarvestClientView, self).configure_form(f) + super().configure_form(f) # projects f.set_renderer('projects', self.render_projects) @@ -86,7 +86,7 @@ class HarvestClientView(HarvestMasterView): return HTML.tag('ul', c=items) def get_xref_buttons(self, client): - buttons = super(HarvestClientView, self).get_xref_buttons(client) + buttons = super().get_xref_buttons(client) model = self.model # harvest proper @@ -102,8 +102,8 @@ class HarvestClientView(HarvestMasterView): def defaults(config, **kwargs): base = globals() - HarvestClientView = kwargs.get('HarvestClientView', base['HarvestClientView']) - HarvestClientView.defaults(config) + HarvestCacheClientView = kwargs.get('HarvestCacheClientView', base['HarvestCacheClientView']) + HarvestCacheClientView.defaults(config) def includeme(config): diff --git a/tailbone_harvest/views/harvest/master.py b/tailbone_harvest/views/harvest/master.py index 8e5fc97..5230987 100644 --- a/tailbone_harvest/views/harvest/master.py +++ b/tailbone_harvest/views/harvest/master.py @@ -24,7 +24,7 @@ Harvest master view """ -from rattail_harvest.db.model import HarvestTimeEntry +from rattail_harvest.db.model import HarvestCacheTimeEntry from webhelpers2.html import tags @@ -38,7 +38,7 @@ class HarvestMasterView(MasterView): creatable = False touchable = True has_versions = True - model_row_class = HarvestTimeEntry + model_row_class = HarvestCacheTimeEntry labels = { 'id': "ID", diff --git a/tailbone_harvest/views/harvest/projects.py b/tailbone_harvest/views/harvest/projects.py index ae36090..55bf236 100644 --- a/tailbone_harvest/views/harvest/projects.py +++ b/tailbone_harvest/views/harvest/projects.py @@ -24,17 +24,17 @@ Harvest Project views """ -from rattail_harvest.db.model import HarvestProject +from rattail_harvest.db.model import HarvestCacheProject from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView -class HarvestProjectView(HarvestMasterView): +class HarvestCacheProjectView(HarvestMasterView): """ Master view for Harvest Projects """ - model_class = HarvestProject + model_class = HarvestCacheProject url_prefix = '/harvest/projects' route_prefix = 'harvest.projects' @@ -62,12 +62,12 @@ class HarvestProjectView(HarvestMasterView): ] def configure_grid(self, g): - super(HarvestProjectView, self).configure_grid(g) + super().configure_grid(g) model = self.model - g.set_joiner('client', lambda q: q.outerjoin(model.HarvestClient)) - g.set_sorter('client', model.HarvestClient.name) - g.set_filter('client', model.HarvestClient.name, label="Client Name") + g.set_joiner('client', lambda q: q.outerjoin(model.HarvestCacheClient)) + g.set_sorter('client', model.HarvestCacheClient.name) + g.set_filter('client', model.HarvestCacheClient.name, label="Client Name") g.filters['client'].default_active = True g.filters['client'].default_verb = 'contains' @@ -95,7 +95,7 @@ class HarvestProjectView(HarvestMasterView): return 'warning' def configure_form(self, f): - super(HarvestProjectView, self).configure_form(f) + super().configure_form(f) f.set_type('hourly_rate', 'currency') @@ -108,7 +108,7 @@ class HarvestProjectView(HarvestMasterView): f.set_readonly('updated_at') def get_xref_buttons(self, project): - buttons = super(HarvestProjectView, self).get_xref_buttons(project) + buttons = super().get_xref_buttons(project) model = self.model # harvest @@ -122,8 +122,8 @@ class HarvestProjectView(HarvestMasterView): def get_row_data(self, project): model = self.model - return self.Session.query(model.HarvestTimeEntry)\ - .filter(model.HarvestTimeEntry.project == project) + return self.Session.query(model.HarvestCacheTimeEntry)\ + .filter(model.HarvestCacheTimeEntry.project == project) def get_parent(self, entry): return entry.project @@ -132,8 +132,8 @@ class HarvestProjectView(HarvestMasterView): def defaults(config, **kwargs): base = globals() - HarvestProjectView = kwargs.get('HarvestProjectView', base['HarvestProjectView']) - HarvestProjectView.defaults(config) + HarvestCacheProjectView = kwargs.get('HarvestCacheProjectView', base['HarvestCacheProjectView']) + HarvestCacheProjectView.defaults(config) def includeme(config): diff --git a/tailbone_harvest/views/harvest/tasks.py b/tailbone_harvest/views/harvest/tasks.py index 17cf6d6..2cf52df 100644 --- a/tailbone_harvest/views/harvest/tasks.py +++ b/tailbone_harvest/views/harvest/tasks.py @@ -24,17 +24,17 @@ Harvest Task views """ -from rattail_harvest.db.model import HarvestTask +from rattail_harvest.db.model import HarvestCacheTask from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView -class HarvestTaskView(HarvestMasterView): +class HarvestCacheTaskView(HarvestMasterView): """ Master view for Harvest Tasks """ - model_class = HarvestTask + model_class = HarvestCacheTask url_prefix = '/harvest/tasks' route_prefix = 'harvest.tasks' @@ -48,7 +48,7 @@ class HarvestTaskView(HarvestMasterView): ] def configure_grid(self, g): - super(HarvestTaskView, self).configure_grid(g) + super().configure_grid(g) g.set_sort_defaults('name') @@ -56,13 +56,13 @@ class HarvestTaskView(HarvestMasterView): g.set_link('name') def configure_form(self, f): - super(HarvestTaskView, self).configure_form(f) + super().configure_form(f) # time_entries f.remove_field('time_entries') def get_xref_buttons(self, task): - buttons = super(HarvestTaskView, self).get_xref_buttons(task) + buttons = super().get_xref_buttons(task) model = self.model # harvest @@ -78,8 +78,8 @@ class HarvestTaskView(HarvestMasterView): def defaults(config, **kwargs): base = globals() - HarvestTaskView = kwargs.get('HarvestTaskView', base['HarvestTaskView']) - HarvestTaskView.defaults(config) + HarvestCacheTaskView = kwargs.get('HarvestCacheTaskView', base['HarvestCacheTaskView']) + HarvestCacheTaskView.defaults(config) def includeme(config): diff --git a/tailbone_harvest/views/harvest/time_entries.py b/tailbone_harvest/views/harvest/time_entries.py index b4b67f4..fc04bf8 100644 --- a/tailbone_harvest/views/harvest/time_entries.py +++ b/tailbone_harvest/views/harvest/time_entries.py @@ -24,17 +24,17 @@ Harvest Time Entry views """ -from rattail_harvest.db.model import HarvestTimeEntry +from rattail_harvest.db.model import HarvestCacheTimeEntry from rattail_harvest.harvest.config import get_harvest_url from .master import HarvestMasterView -class HarvestTimeEntryView(HarvestMasterView): +class HarvestCacheTimeEntryView(HarvestMasterView): """ Master view for Harvest Time Entries """ - model_class = HarvestTimeEntry + model_class = HarvestCacheTimeEntry url_prefix = '/harvest/time-entries' route_prefix = 'harvest.time_entries' @@ -50,7 +50,7 @@ class HarvestTimeEntryView(HarvestMasterView): ] def configure_grid(self, g): - super(HarvestTimeEntryView, self).configure_grid(g) + super().configure_grid(g) g.set_type('hours', 'duration_hours') @@ -99,7 +99,7 @@ class HarvestTimeEntryView(HarvestMasterView): return f"{hours} ({duration})" def get_xref_buttons(self, entry): - buttons = super(HarvestTimeEntryView, self).get_xref_buttons(entry) + buttons = super().get_xref_buttons(entry) model = self.model # harvest @@ -117,7 +117,7 @@ class HarvestTimeEntryView(HarvestMasterView): def import_from_harvest(self): app = self.get_rattail_app() handler = app.get_import_handler('to_rattail.from_harvest.import', require=True) - importer = handler.get_importer('HarvestTimeEntry') + importer = handler.get_importer('HarvestCacheTimeEntry') importer.session = self.Session() importer.setup() @@ -155,8 +155,8 @@ class HarvestTimeEntryView(HarvestMasterView): def defaults(config, **kwargs): base = globals() - HarvestTimeEntryView = kwargs.get('HarvestTimeEntryView', base['HarvestTimeEntryView']) - HarvestTimeEntryView.defaults(config) + HarvestCacheTimeEntryView = kwargs.get('HarvestCacheTimeEntryView', base['HarvestCacheTimeEntryView']) + HarvestCacheTimeEntryView.defaults(config) def includeme(config): diff --git a/tailbone_harvest/views/harvest/users.py b/tailbone_harvest/views/harvest/users.py index 54b8dd1..d360b83 100644 --- a/tailbone_harvest/views/harvest/users.py +++ b/tailbone_harvest/views/harvest/users.py @@ -24,7 +24,7 @@ Harvest User views """ -from rattail_harvest.db.model import HarvestUser +from rattail_harvest.db.model import HarvestCacheUser from rattail_harvest.harvest.config import get_harvest_url import colander @@ -33,11 +33,11 @@ from tailbone import forms from .master import HarvestMasterView -class HarvestUserView(HarvestMasterView): +class HarvestCacheUserView(HarvestMasterView): """ Master view for Harvest Users """ - model_class = HarvestUser + model_class = HarvestCacheUser url_prefix = '/harvest/users' route_prefix = 'harvest.users' @@ -56,7 +56,7 @@ class HarvestUserView(HarvestMasterView): ] def configure_grid(self, g): - super(HarvestUserView, self).configure_grid(g) + super().configure_grid(g) model = self.model g.set_joiner('person_name', lambda q: q.outerjoin(model.Person)) @@ -70,7 +70,7 @@ class HarvestUserView(HarvestMasterView): g.set_link('email') def configure_form(self, f): - super(HarvestUserView, self).configure_form(f) + super().configure_form(f) model = self.model user = f.model_instance @@ -121,7 +121,7 @@ class HarvestUserView(HarvestMasterView): raise colander.Invalid(node, "Person not found (you must *select* a record)") def get_xref_buttons(self, user): - buttons = super(HarvestUserView, self).get_xref_buttons(user) + buttons = super().get_xref_buttons(user) model = self.model # harvest proper @@ -136,8 +136,8 @@ class HarvestUserView(HarvestMasterView): def defaults(config, **kwargs): base = globals() - HarvestUserView = kwargs.get('HarvestUserView', base['HarvestUserView']) - HarvestUserView.defaults(config) + HarvestCacheUserView = kwargs.get('HarvestCacheUserView', base['HarvestCacheUserView']) + HarvestCacheUserView.defaults(config) def includeme(config): From 3ea20c2d8c26f0474546e7933a85da04937d14dd Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 6 Jun 2024 18:25:57 -0500 Subject: [PATCH 15/22] Update changelog --- CHANGELOG.md | 5 +++++ tailbone_harvest/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbaaf9a..208ab65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to tailbone-harvest will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2024-06-06 +Catch-up release. +### Changed +- lots of things... + ## [0.1.0] - 2022-01-29 ### Added - Initial version. diff --git a/tailbone_harvest/_version.py b/tailbone_harvest/_version.py index e41b669..08b390b 100644 --- a/tailbone_harvest/_version.py +++ b/tailbone_harvest/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.0' +__version__ = '0.2.0' From 6eb483fe90c311673df482592384a952ca8e0a59 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 6 Jun 2024 18:26:47 -0500 Subject: [PATCH 16/22] Fix default dist filename for release task not sure why this fix was needed, did setuptools behavior change? --- tasks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tasks.py b/tasks.py index 88b264a..fea43cf 100644 --- a/tasks.py +++ b/tasks.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2024 Lance Edgar # # This file is part of Rattail. # @@ -35,14 +35,14 @@ exec(open(os.path.join(here, 'tailbone_harvest', '_version.py')).read()) @task -def release(ctx): +def release(c): """ Release a new version of tailbone-harvest """ # rebuild local tar.gz file for distribution shutil.rmtree('tailbone_harvest.egg-info') - ctx.run('python setup.py sdist --formats=gztar') + c.run('python setup.py sdist --formats=gztar') # upload to public PyPI - filename = 'tailbone-harvest-{}.tar.gz'.format(__version__) - ctx.run('twine upload dist/{}'.format(filename)) + filename = f'tailbone_harvest-{__version__}.tar.gz' + c.run(f'twine upload dist/{filename}') From 29c3429d2e0aad3857433c4571e558cc2a74ee77 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 10 Jun 2024 19:35:42 -0500 Subject: [PATCH 17/22] feat: switch from setup.cfg to pyproject.toml + hatchling --- .gitignore | 3 ++ pyproject.toml | 58 ++++++++++++++++++++++++++++++++++++ setup.cfg | 31 ------------------- setup.py | 29 ------------------ tailbone_harvest/_version.py | 5 +++- tasks.py | 18 +++++++++-- 6 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 6ee5074..40ca5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +*~ +*.pyc +dist/ tailbone_harvest.egg-info/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8f70171 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[project] +name = "tailbone-harvest" +version = "0.2.0" +description = "Tailbone integration package for Harvest" +readme = "README.rst" +authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] +license = {text = "GNU GPL v3+"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Office/Business", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "invoke", + "rattail-harvest", + "Tailbone", +] + + +[project.urls] +Homepage = "https://rattailproject.org" +Repository = "https://kallithea.rattailproject.org/rattail-project/tailbone-harvest" +Changelog = "https://kallithea.rattailproject.org/rattail-project/tailbone-harvest/files/master/CHANGELOG.md" + + +[project.entry-points."rattail.subcommands"] +import-harvest = "rattail_harvest.commands:ImportHarvest" + + +[project.entry-points."rattail.typer_imports"] +rattail_harvest = "rattail_harvest.commands" + + +[project.entry-points."rattail.config.extensions"] +rattail_harvest = "rattail_harvest.config:RattailHarvestExtension" + + +[project.entry-points."rattail.importing"] +"to_rattail.from_harvest.import" = "rattail_harvest.importing.harvest:FromHarvestToRattail" + + +[tool.commitizen] +version_provider = "pep621" +tag_format = "v$version" +update_changelog_on_bump = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 928158a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8; -*- - -[metadata] -name = tailbone-harvest -version = attr: tailbone_harvest.__version__ -author = Lance Edgar -author_email = lance@edbob.org -url = https://rattailproject.org/ -description = Tailbone integration package for Harvest -long_description = file: README.rst -classifiers = - Development Status :: 3 - Alpha - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) - Natural Language :: English - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Topic :: Office/Business - Topic :: Software Development :: Libraries :: Python Modules - - -[options] -install_requires = - invoke - rattail-harvest - Tailbone - -packages = find: -include_package_data = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 3b73b9a..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -tailbone-harvest setup script -""" - -from setuptools import setup - -setup() diff --git a/tailbone_harvest/_version.py b/tailbone_harvest/_version.py index 08b390b..0e7a01b 100644 --- a/tailbone_harvest/_version.py +++ b/tailbone_harvest/_version.py @@ -1,3 +1,6 @@ # -*- coding: utf-8; -*- -__version__ = '0.2.0' +from importlib.metadata import version + + +__version__ = version('tailbone-harvest') diff --git a/tasks.py b/tasks.py index fea43cf..c4ed484 100644 --- a/tasks.py +++ b/tasks.py @@ -25,13 +25,24 @@ Tasks for tailbone-harvest """ import os +import re import shutil from invoke import task here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'tailbone_harvest', '_version.py')).read()) +__version__ = None +pattern = re.compile(r'^version = "(\d+\.\d+\.\d+)"$') +with open(os.path.join(here, 'pyproject.toml'), 'rt') as f: + for line in f: + line = line.rstrip('\n') + match = pattern.match(line) + if match: + __version__ = match.group(1) + break +if not __version__: + raise RuntimeError("could not parse version!") @task @@ -40,8 +51,9 @@ def release(c): Release a new version of tailbone-harvest """ # rebuild local tar.gz file for distribution - shutil.rmtree('tailbone_harvest.egg-info') - c.run('python setup.py sdist --formats=gztar') + if os.path.exists('tailbone_harvest.egg-info'): + shutil.rmtree('tailbone_harvest.egg-info') + c.run('python -m build --sdist') # upload to public PyPI filename = f'tailbone_harvest-{__version__}.tar.gz' From 945d595329afb7ca01b8b4e67cdc4a76f690d085 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 10 Jun 2024 19:35:55 -0500 Subject: [PATCH 18/22] =?UTF-8?q?bump:=20version=200.2.0=20=E2=86=92=200.3?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 208ab65..27c246c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to tailbone-harvest will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.0 (2024-06-10) + +### Feat + +- switch from setup.cfg to pyproject.toml + hatchling + ## [0.2.0] - 2024-06-06 Catch-up release. ### Changed diff --git a/pyproject.toml b/pyproject.toml index 8f70171..5537117 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "tailbone-harvest" -version = "0.2.0" +version = "0.3.0" description = "Tailbone integration package for Harvest" readme = "README.rst" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 52a1d15f7ab8054ad6fe737985e4a6ac8cfae0cb Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 Jul 2024 12:33:05 -0500 Subject: [PATCH 19/22] fix: remove incorrect entry points missed these when copy-pasting apparently --- pyproject.toml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5537117..7c359a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,22 +36,6 @@ Repository = "https://kallithea.rattailproject.org/rattail-project/tailbone-harv Changelog = "https://kallithea.rattailproject.org/rattail-project/tailbone-harvest/files/master/CHANGELOG.md" -[project.entry-points."rattail.subcommands"] -import-harvest = "rattail_harvest.commands:ImportHarvest" - - -[project.entry-points."rattail.typer_imports"] -rattail_harvest = "rattail_harvest.commands" - - -[project.entry-points."rattail.config.extensions"] -rattail_harvest = "rattail_harvest.config:RattailHarvestExtension" - - -[project.entry-points."rattail.importing"] -"to_rattail.from_harvest.import" = "rattail_harvest.importing.harvest:FromHarvestToRattail" - - [tool.commitizen] version_provider = "pep621" tag_format = "v$version" From 5884901d75834cb57efd4c45130ec1c521be1b71 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 Jul 2024 14:02:13 -0500 Subject: [PATCH 20/22] =?UTF-8?q?bump:=20version=200.3.0=20=E2=86=92=200.3?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c246c..0728aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to tailbone-harvest will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.1 (2024-07-01) + +### Fix + +- remove incorrect entry points + ## v0.3.0 (2024-06-10) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 7c359a0..830cad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "tailbone-harvest" -version = "0.3.0" +version = "0.3.1" description = "Tailbone integration package for Harvest" readme = "README.rst" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 65b9d059e8161d3ef20d94c1e1c690a030cd0d81 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 13 Sep 2024 18:11:51 -0500 Subject: [PATCH 21/22] docs: use markdown for readme file --- README.md | 11 +++++++++++ README.rst | 14 -------------- pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec86877 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ + +# tailbone-harvest + +Rattail is a retail software framework, released under the GNU General +Public License. + +This package contains software interfaces for +[Harvest](https://www.getharvest.com/). + +Please see the [Rattail Project](https://rattailproject.org/) for more +information. diff --git a/README.rst b/README.rst deleted file mode 100644 index 4866f29..0000000 --- a/README.rst +++ /dev/null @@ -1,14 +0,0 @@ - -tailbone-harvest -================ - -Rattail is a retail software framework, released under the GNU General -Public License. - -This package contains software interfaces for `Harvest`_. - -.. _`Harvest`: https://www.getharvest.com/ - -Please see the `Rattail Project`_ for more information. - -.. _`Rattail Project`: https://rattailproject.org/ diff --git a/pyproject.toml b/pyproject.toml index 830cad9..70192c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "tailbone-harvest" version = "0.3.1" description = "Tailbone integration package for Harvest" -readme = "README.rst" +readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] license = {text = "GNU GPL v3+"} classifiers = [ From 212e6ffc920b0551d1da5bcd6911b1ed36e02819 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 14 Sep 2024 12:13:24 -0500 Subject: [PATCH 22/22] docs: update project links, kallithea -> forgejo --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70192c2..e9ffcd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,8 @@ dependencies = [ [project.urls] Homepage = "https://rattailproject.org" -Repository = "https://kallithea.rattailproject.org/rattail-project/tailbone-harvest" -Changelog = "https://kallithea.rattailproject.org/rattail-project/tailbone-harvest/files/master/CHANGELOG.md" +Repository = "https://forgejo.wuttaproject.org/rattail/tailbone-harvest" +Changelog = "https://forgejo.wuttaproject.org/rattail/tailbone-harvest/src/branch/master/CHANGELOG.md" [tool.commitizen]