From 949b9d64bf2d627980dcd37a8e8bb5aa32ec9e61 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 14 May 2021 12:13:23 -0500 Subject: [PATCH 0001/1336] Allow customization of rendering version diff values --- tailbone/templates/master/view_version.mako | 14 ++++++++++---- tailbone/views/master.py | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tailbone/templates/master/view_version.mako b/tailbone/templates/master/view_version.mako index 13c87ae6..5dbcd15d 100644 --- a/tailbone/templates/master/view_version.mako +++ b/tailbone/templates/master/view_version.mako @@ -11,6 +11,10 @@ overflow: auto; } + .versions-wrapper { + margin-left: 2rem; + } + @@ -45,6 +49,7 @@ +
% for version in versions:

${title_for_version(version)}

@@ -62,7 +67,7 @@ % for field in fields_for_version(version): ${field} - ${repr(getattr(version.previous, field))} + ${render_old_value(version, field)}   % endfor @@ -81,8 +86,8 @@ % for field in fields_for_version(version): ${field} - ${repr(getattr(version.previous, field))} - ${repr(getattr(version, field))} + ${render_old_value(version, field)} + ${render_new_value(version, field, 'dirty')} % endfor @@ -101,7 +106,7 @@ ${field}   - ${repr(getattr(version, field))} + ${render_new_value(version, field, 'new')} % endfor @@ -109,6 +114,7 @@ % endif % endfor +
diff --git a/tailbone/views/master.py b/tailbone/views/master.py index a0e3a3b0..b21051cb 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -1169,6 +1169,8 @@ class MasterView(View): 'title_for_version': self.title_for_version, 'fields_for_version': self.fields_for_version, 'continuum': continuum, + 'render_old_value': self.render_version_old_field_value, + 'render_new_value': self.render_version_new_field_value, }) def title_for_version(self, version): @@ -1198,6 +1200,12 @@ class MasterView(View): versions.extend(query.all()) return versions + def render_version_old_field_value(self, version, field): + return repr(getattr(version.previous, field)) + + def render_version_new_field_value(self, version, field, typ): + return repr(getattr(version, field)) + def configure_common_form(self, form): """ Configure the form in whatever way is deemed "common" - i.e. where From d1a35a4d58920f626c5cae29d074b737e34827e5 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 18 May 2021 12:36:46 -0500 Subject: [PATCH 0002/1336] Allow direct creation of new label batches now technically this is allowed on desktop, but probably makes more sense on mobile via api --- tailbone/api/batch/labels.py | 4 +++- tailbone/views/batch/labels.py | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tailbone/api/batch/labels.py b/tailbone/api/batch/labels.py index 0648a0c9..11a3d20d 100644 --- a/tailbone/api/batch/labels.py +++ b/tailbone/api/batch/labels.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -61,7 +61,9 @@ class LabelBatchRowViews(APIBatchRowView): data['item_id'] = row.item_id data['upc'] = six.text_type(row.upc) data['upc_pretty'] = row.upc.pretty() if row.upc else None + data['brand_name'] = row.brand_name data['description'] = row.description + data['size'] = row.size data['full_description'] = row.product.full_description if row.product else row.description return data diff --git a/tailbone/views/batch/labels.py b/tailbone/views/batch/labels.py index 5015ffdc..c52a5a67 100644 --- a/tailbone/views/batch/labels.py +++ b/tailbone/views/batch/labels.py @@ -48,7 +48,6 @@ class LabelBatchView(BatchMasterView): route_prefix = 'labels.batch' url_prefix = '/labels/batches' template_prefix = '/batch/labels' - creatable = False bulk_deletable = True rows_editable = True rows_bulk_deletable = True @@ -116,12 +115,15 @@ class LabelBatchView(BatchMasterView): super(LabelBatchView, self).configure_form(f) # handheld_batches - f.set_readonly('handheld_batches') - f.set_renderer('handheld_batches', self.render_handheld_batches) - if self.viewing or self.deleting: + if self.creating: + f.remove('handheld_batches') + else: batch = self.get_instance() if not batch._handhelds: f.remove_field('handheld_batches') + else: + f.set_readonly('handheld_batches') + f.set_renderer('handheld_batches', self.render_handheld_batches) # label profile if self.creating or self.editing: From 31941c00bfb8a7f101ba1e8c1a9643149e2a12f9 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 24 May 2021 16:21:08 -0500 Subject: [PATCH 0003/1336] Allow generating project which integrates w/ LOC SMS --- tailbone/templates/generate_project.mako | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailbone/templates/generate_project.mako b/tailbone/templates/generate_project.mako index dc4a2f06..51f404ee 100644 --- a/tailbone/templates/generate_project.mako +++ b/tailbone/templates/generate_project.mako @@ -142,8 +142,8 @@ - + From add4337d115c1c43f95253f2c0b19d458476ebfd Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 11 Jun 2021 13:34:40 -0500 Subject: [PATCH 0004/1336] Update changelog --- CHANGES.rst | 10 ++++++++++ tailbone/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ad872b75..622d2d91 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,16 @@ CHANGELOG ========= +0.8.133 (2021-06-11) +-------------------- + +* Allow customization of rendering version diff values. + +* Allow direct creation of new label batches. + +* Allow generating project which integrates w/ LOC SMS. + + 0.8.132 (2021-05-03) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 2c05624c..5929fdec 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.132' +__version__ = '0.8.133' From b2bda5e31d01b9db1aba2c57786b48c0623525ae Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 15 Jun 2021 15:51:11 -0500 Subject: [PATCH 0005/1336] Allow config to set favicon and header image it already could set "main" image, shown in home and login pages --- tailbone/templates/base_meta.mako | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tailbone/templates/base_meta.mako b/tailbone/templates/base_meta.mako index ec097e5d..568782b7 100644 --- a/tailbone/templates/base_meta.mako +++ b/tailbone/templates/base_meta.mako @@ -5,10 +5,12 @@ <%def name="global_title()">${"[STAGE] " if not request.rattail_config.production() else ''}${self.app_title()} <%def name="favicon()"> - + -<%def name="header_logo()"> +<%def name="header_logo()"> + ${h.image(request.rattail_config.get('tailbone', 'header_image_url', default=request.static_url('tailbone:static/img/rattail.ico')), "Header Logo", style="height: 49px;")} + <%def name="footer()">

From a1d6403b1b448d47af47507e5961123745fb2c92 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 15 Jun 2021 15:51:57 -0500 Subject: [PATCH 0006/1336] Update changelog --- CHANGES.rst | 6 ++++++ tailbone/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 622d2d91..79f84049 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ CHANGELOG ========= +0.8.134 (2021-06-15) +-------------------- + +* Allow config to set favicon and header image. + + 0.8.133 (2021-06-11) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 5929fdec..4ed5a5a8 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.133' +__version__ = '0.8.134' From 2e561f1a4af292a243f83bf5128ef756b617951e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 15 Jun 2021 21:34:22 -0500 Subject: [PATCH 0007/1336] Add 'v' prefix for release package diff links at least i think that is needed... --- tailbone/views/upgrades.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tailbone/views/upgrades.py b/tailbone/views/upgrades.py index e817d9a1..0484dabc 100644 --- a/tailbone/views/upgrades.py +++ b/tailbone/views/upgrades.py @@ -292,51 +292,51 @@ class UpgradeView(MasterView): projects = { 'rattail': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/rattail/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail/files/v{new_version}/CHANGES.rst', }, 'Tailbone': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone/files/v{new_version}/CHANGES.rst', }, 'pyCOREPOS': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/pycorepos/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/pycorepos/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/pycorepos/files/v{new_version}/CHANGES.rst', }, 'rattail_corepos': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-corepos/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-corepos/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-corepos/files/v{new_version}/CHANGES.rst', }, 'tailbone_corepos': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-corepos/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-corepos/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-corepos/files/v{new_version}/CHANGES.rst', }, 'onager': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-restricted/onager/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/onager/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/onager/files/v{new_version}/CHANGES.rst', }, 'rattail-onager': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-restricted/rattail-onager/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/rattail-onager/files/{new_version}/CHANGELOG.md', + 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/rattail-onager/files/v{new_version}/CHANGELOG.md', }, 'rattail_tempmon': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-tempmon/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-tempmon/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-tempmon/files/v{new_version}/CHANGES.rst', }, 'tailbone-onager': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-restricted/tailbone-onager/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/tailbone-onager/files/{new_version}/CHANGELOG.md', + 'release_url': 'https://kallithea.rattailproject.org/rattail-restricted/tailbone-onager/files/v{new_version}/CHANGELOG.md', }, 'rattail_woocommerce': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-woocommerce/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-woocommerce/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/rattail-woocommerce/files/v{new_version}/CHANGES.rst', }, 'tailbone_woocommerce': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-woocommerce/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-woocommerce/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/tailbone-woocommerce/files/v{new_version}/CHANGES.rst', }, 'tailbone_theo': { 'commit_url': 'https://kallithea.rattailproject.org/rattail-project/theo/changelog/{new_version}/?size=10', - 'release_url': 'https://kallithea.rattailproject.org/rattail-project/theo/files/{new_version}/CHANGES.rst', + 'release_url': 'https://kallithea.rattailproject.org/rattail-project/theo/files/v{new_version}/CHANGES.rst', }, } return projects From 5cdd09020d055221205046d7e968d2b9bd84a8cb Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 15 Jun 2021 21:35:58 -0500 Subject: [PATCH 0008/1336] Update changelog --- CHANGES.rst | 6 ++++++ tailbone/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 79f84049..b466ba2f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ CHANGELOG ========= +0.8.135 (2021-06-15) +-------------------- + +* Add 'v' prefix for release package diff links. + + 0.8.134 (2021-06-15) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 4ed5a5a8..dceab07e 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.134' +__version__ = '0.8.135' From 35aab87fdc32744730df5c0173ee18a373218770 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 18 Jun 2021 17:39:14 -0500 Subject: [PATCH 0009/1336] Include "is/not null" filters for GPC fields --- tailbone/grids/filters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py index 4ce1dc22..06c4e7db 100644 --- a/tailbone/grids/filters.py +++ b/tailbone/grids/filters.py @@ -926,7 +926,8 @@ class AlchemyGPCFilter(AlchemyGridFilter): """ GPC filter for SQLAlchemy. """ - default_verbs = ['equal', 'not_equal', 'equal_any_of'] + default_verbs = ['equal', 'not_equal', 'equal_any_of', + 'is_null', 'is_not_null'] def filter_equal(self, query, value): """ From fb156d2e290632a067f2a07fd539b587981dea75 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 18 Jun 2021 17:53:27 -0500 Subject: [PATCH 0010/1336] Update changelog --- CHANGES.rst | 6 ++++++ tailbone/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index b466ba2f..1528bf60 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ CHANGELOG ========= +0.8.136 (2021-06-18) +-------------------- + +* Include "is/not null" filters for GPC fields. + + 0.8.135 (2021-06-15) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index dceab07e..231280b2 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.135' +__version__ = '0.8.136' From 8eee4a1cf09891989bb9163fa7efa60ad340eea4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 15 Jul 2021 13:29:31 -0500 Subject: [PATCH 0011/1336] Set UPC renderer for delproduct batch row --- tailbone/views/batch/core.py | 10 ++++++++++ tailbone/views/batch/delproduct.py | 7 +++++++ tailbone/views/batch/inventory.py | 10 ---------- tailbone/views/handheld.py | 10 ---------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index 299f0a10..07b7ff68 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -615,6 +615,16 @@ class BatchMasterView(MasterView): def get_row_status_enum(self): return self.model_row_class.STATUS + def render_upc(self, row, field): + upc = row.upc + if not upc: + return "" + text = upc.pretty() + if row.product_uuid: + url = self.request.route_url('products.view', uuid=row.product_uuid) + return tags.link_to(text, url) + return text + def render_row_status(self, row, column): code = row.status_code if code is None: diff --git a/tailbone/views/batch/delproduct.py b/tailbone/views/batch/delproduct.py index 845de3db..287fb3e3 100644 --- a/tailbone/views/batch/delproduct.py +++ b/tailbone/views/batch/delproduct.py @@ -105,6 +105,13 @@ class DeleteProductBatchView(BatchMasterView): row.STATUS_PENDING_CUSTOMER_ORDERS): return 'notice' + def configure_row_form(self, f): + super(DeleteProductBatchView, self).configure_row_form(f) + row = f.model_instance + + # upc + f.set_renderer('upc', self.render_upc) + def includeme(config): DeleteProductBatchView.defaults(config) diff --git a/tailbone/views/batch/inventory.py b/tailbone/views/batch/inventory.py index adf91561..f8699725 100644 --- a/tailbone/views/batch/inventory.py +++ b/tailbone/views/batch/inventory.py @@ -471,16 +471,6 @@ class InventoryBatchView(BatchMasterView): if not self.allow_cases(row.batch): f.set_readonly('cases') - def render_upc(self, row, field): - upc = row.upc - if not upc: - return "" - text = upc.pretty() - if row.product_uuid: - url = self.request.route_url('products.view', uuid=row.product_uuid) - return tags.link_to(text, url) - return text - @classmethod def defaults(cls, config): cls._batch_defaults(config) diff --git a/tailbone/views/handheld.py b/tailbone/views/handheld.py index 66cd480c..b0392c13 100644 --- a/tailbone/views/handheld.py +++ b/tailbone/views/handheld.py @@ -187,16 +187,6 @@ class HandheldBatchView(FileBatchMasterView): # upc f.set_renderer('upc', self.render_upc) - def render_upc(self, row, field): - upc = row.upc - if not upc: - return "" - text = upc.pretty() - if row.product_uuid: - url = self.request.route_url('products.view', uuid=row.product_uuid) - return tags.link_to(text, url) - return text - def get_execute_success_url(self, batch, result, **kwargs): if kwargs['action'] == 'make_inventory_batch': return self.request.route_url('batch.inventory.view', uuid=result.uuid) From 4addedef6ece47bf6028d738a2eb850a6ba8ec97 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 15 Jul 2021 14:13:01 -0500 Subject: [PATCH 0012/1336] Expose `pack_size` for delproduct batch --- tailbone/views/batch/delproduct.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tailbone/views/batch/delproduct.py b/tailbone/views/batch/delproduct.py index 287fb3e3..60561e96 100644 --- a/tailbone/views/batch/delproduct.py +++ b/tailbone/views/batch/delproduct.py @@ -64,6 +64,7 @@ class DeleteProductBatchView(BatchMasterView): 'brand_name', 'description', 'size', + 'pack_size', 'department_name', 'subdepartment_name', 'present_in_scale', @@ -78,6 +79,7 @@ class DeleteProductBatchView(BatchMasterView): 'brand_name', 'description', 'size', + 'pack_size', 'department_number', 'department_name', 'subdepartment_number', @@ -105,6 +107,12 @@ class DeleteProductBatchView(BatchMasterView): row.STATUS_PENDING_CUSTOMER_ORDERS): return 'notice' + def configure_row_grid(self, g): + super(DeleteProductBatchView, self).configure_row_grid(g) + + # pack_size + g.set_type('pack_size', 'quantity') + def configure_row_form(self, f): super(DeleteProductBatchView, self).configure_row_form(f) row = f.model_instance @@ -112,6 +120,9 @@ class DeleteProductBatchView(BatchMasterView): # upc f.set_renderer('upc', self.render_upc) + # pack_size + f.set_type('pack_size', 'quantity') + def includeme(config): DeleteProductBatchView.defaults(config) From 8884d28306ad1fb62da516f4a049073db81ffea5 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 15 Jul 2021 14:15:19 -0500 Subject: [PATCH 0013/1336] Update changelog --- CHANGES.rst | 8 ++++++++ tailbone/_version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1528bf60..27be20df 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,14 @@ CHANGELOG ========= +0.8.137 (2021-07-15) +-------------------- + +* Set UPC renderer for delproduct batch row. + +* Expose ``pack_size`` for delproduct batch. + + 0.8.136 (2021-06-18) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 231280b2..0fe1d51b 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.136' +__version__ = '0.8.137' From 90af8f91b88c6f8e82bfafaf452e923d2ae43224 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 2 Aug 2021 18:26:15 -0500 Subject: [PATCH 0014/1336] Let feedback forms define their own email key so multiple recipient options may be presented to user, e.g. in public frontend --- tailbone/api/common.py | 3 ++- tailbone/forms/common.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tailbone/api/common.py b/tailbone/api/common.py index c2823ff9..81458c01 100644 --- a/tailbone/api/common.py +++ b/tailbone/api/common.py @@ -104,7 +104,8 @@ class CommonView(APIView): data['user_url'] = '#' # TODO: could get from config? data['client_ip'] = self.request.client_addr - send_email(self.rattail_config, self.feedback_email_key, data=data) + email_key = data['email_key'] or self.feedback_email_key + send_email(self.rattail_config, email_key, data=data) return {'ok': True} return {'error': "Form did not validate!"} diff --git a/tailbone/forms/common.py b/tailbone/forms/common.py index 9cc145dd..26934479 100644 --- a/tailbone/forms/common.py +++ b/tailbone/forms/common.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2019 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -46,6 +46,9 @@ class Feedback(colander.Schema): """ Form schema for user feedback. """ + email_key = colander.SchemaNode(colander.String(), + missing=colander.null) + referrer = colander.SchemaNode(colander.String()) user = colander.SchemaNode(colander.String(), From a10de791a1c04f9abad52db0367f2e51193c8fe8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 4 Aug 2021 13:01:09 -0500 Subject: [PATCH 0015/1336] Update changelog --- CHANGES.rst | 6 ++++++ tailbone/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 27be20df..7976b45d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ CHANGELOG ========= +0.8.138 (2021-08-04) +-------------------- + +* Let feedback forms define their own email key. + + 0.8.137 (2021-07-15) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 0fe1d51b..10c12322 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.137' +__version__ = '0.8.138' From 5836099746eec80be94eead28435b36f99e09a9c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 16 Aug 2021 19:29:48 -0500 Subject: [PATCH 0016/1336] Tweak how email preview is sent, and attempt "to" is displayed latter only have been changed for the grid view. preview now is sent "properly" via the configured mail handler, which also means that an attempt may be recorded (whereas previously it would not be) --- tailbone/views/email.py | 52 +++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/tailbone/views/email.py b/tailbone/views/email.py index 2201f8f3..58a0320b 100644 --- a/tailbone/views/email.py +++ b/tailbone/views/email.py @@ -26,6 +26,8 @@ Email Views from __future__ import unicode_literals, absolute_import +import re + import six from rattail import mail @@ -80,8 +82,8 @@ class EmailSettingView(MasterView): self.handler = self.get_handler() def get_handler(self): - # TODO: should let config override which handler we use - return mail.EmailHandler(self.rattail_config) + app = self.get_rattail_app() + return app.get_mail_handler() def get_data(self, session=None): data = [] @@ -277,8 +279,8 @@ class EmailPreview(View): self.handler = self.get_handler() def get_handler(self): - # TODO: should let config override which handler we use - return mail.EmailHandler(self.rattail_config) + app = self.get_rattail_app() + return app.get_mail_handler() def __call__(self): @@ -303,22 +305,15 @@ class EmailPreview(View): if key: email = self.handler.get_email(key) data = email.obtain_sample_data(self.request) - msg = email.make_message(data) - subject = msg['Subject'] - del msg['Subject'] - msg['Subject'] = "[preview] {0}".format(subject) + self.handler.send_message(email, data, + subject_prefix="[PREVIEW] ", + to=[recipient], + cc=None, bcc=None) - del msg['To'] - del msg['Cc'] - del msg['Bcc'] - msg['To'] = recipient - - # TODO: should refactor this to use email handler - sent = mail.deliver_message(self.rattail_config, key, msg) - - self.request.session.flash("Preview for '{}' was {}emailed to {}".format( - key, '' if sent else '(NOT) ', recipient)) + self.request.session.flash( + "Preview for '{}' was emailed to {}".format( + key, recipient)) def preview_template(self, key, type_): email = self.handler.get_email(key) @@ -385,12 +380,33 @@ class EmailAttemptView(MasterView): # status_code g.set_enum('status_code', self.enum.EMAIL_ATTEMPT) + # to + g.set_renderer('to', self.render_to_short) + # links g.set_link('key') g.set_link('sender') g.set_link('subject') g.set_link('to') + to_pattern = re.compile(r'^\{(.*)\}$') + + def render_to_short(self, attempt, column): + value = attempt.to + if not value: + return + + match = self.to_pattern.match(value) + if match: + recips = parse_list(match.group(1)) + if len(recips) > 2: + recips = recips[:2] + recips.append('...') + recips = [HTML.escape(r) for r in recips] + return ', '.join(recips) + + return value + def configure_form(self, f): super(EmailAttemptView, self).configure_form(f) From cf32d4235e7ee233ec79d6f07b79f5fd12f27815 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 18 Aug 2021 19:16:59 -0500 Subject: [PATCH 0017/1336] Move "merge 2 people" logic into People Handler view now delegates to handler, which lives in the rattail package --- tailbone/views/people.py | 66 ++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/tailbone/views/people.py b/tailbone/views/people.py index 6e72fe1f..b8e06ced 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -85,16 +85,13 @@ class PersonView(MasterView): ] mergeable = True - merge_additive_fields = [ - 'usernames', - 'member_uuids', - ] - merge_fields = merge_additive_fields + [ - 'uuid', - 'first_name', - 'last_name', - 'display_name', - ] + + def __init__(self, request): + super(PersonView, self).__init__(request) + + # always get a reference to the People Handler + app = self.get_rattail_app() + self.handler = app.get_people_handler() def configure_grid(self, g): super(PersonView, self).configure_grid(g) @@ -190,9 +187,7 @@ class PersonView(MasterView): names[key] = None # do explicit name update w/ common handler logic - app = self.get_rattail_app() - handler = app.get_people_handler() - handler.update_names(person, **names) + self.handler.update_names(person, **names) return person @@ -365,33 +360,30 @@ class PersonView(MasterView): (model.VendorContact, 'person_uuid'), ] + def get_merge_fields(self): + fields = self.handler.get_merge_preview_fields() + return [field['name'] for field in fields] + + def get_merge_additive_fields(self): + fields = self.handler.get_merge_preview_fields() + return [field['name'] for field in fields + if field.get('additive')] + + def get_merge_coalesce_fields(self): + fields = self.handler.get_merge_preview_fields() + return [field['name'] for field in fields + if field.get('coalesce')] + def get_merge_data(self, person): - return { - 'uuid': person.uuid, - 'first_name': person.first_name, - 'last_name': person.last_name, - 'display_name': person.display_name, - 'usernames': [u.username for u in person.users], - 'member_uuids': [m.uuid for m in person.members], - } + return self.handler.get_merge_preview_data(person) + + def validate_merge(self, removing, keeping): + reason = self.handler.why_not_merge(removing, keeping) + if reason: + raise Exception(reason) def merge_objects(self, removing, keeping): - """ - Execute a merge operation on the two given person records. - """ - # move Member records to final Person - for member in list(removing.members): - removing.members.remove(member) - keeping.members.append(member) - - # move User records to final Person - for user in list(removing.users): - removing.users.remove(user) - keeping.users.append(user) - - # delete unwanted Person - self.Session.delete(removing) - self.Session.flush() + self.handler.perform_merge(removing, keeping) def view_profile(self): """ From ac133ce8304585c33a2af1876c8fda33fd089626 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 19 Aug 2021 18:00:51 -0500 Subject: [PATCH 0018/1336] Expose "merge request tracking" feature for People data more to come i'm sure, but this covers the basics --- tailbone/templates/people/index.mako | 105 ++++++++++++++ .../templates/people/merge-requests/view.mako | 40 ++++++ tailbone/views/master.py | 2 +- tailbone/views/people.py | 135 +++++++++++++++++- 4 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 tailbone/templates/people/index.mako create mode 100644 tailbone/templates/people/merge-requests/view.mako diff --git a/tailbone/templates/people/index.mako b/tailbone/templates/people/index.mako new file mode 100644 index 00000000..377063b8 --- /dev/null +++ b/tailbone/templates/people/index.mako @@ -0,0 +1,105 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/index.mako" /> + +<%def name="grid_tools()"> + + % if master.mergeable and master.has_perm('request_merge'): + % if use_buefy: + + Request Merge + + +

+ + % endif + % endif + + ${parent.grid_tools()} + + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + + + +${parent.body()} diff --git a/tailbone/templates/people/merge-requests/view.mako b/tailbone/templates/people/merge-requests/view.mako new file mode 100644 index 00000000..5dcbea03 --- /dev/null +++ b/tailbone/templates/people/merge-requests/view.mako @@ -0,0 +1,40 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/view.mako" /> + +<%def name="page_content()"> + ${parent.page_content()} + % if not instance.merged and request.has_perm('people.merge'): + % if use_buefy: + ${h.form(url('people.merge'), **{'@submit': 'submitMergeForm'})} + ${h.csrf_token(request)} + ${h.hidden('uuids', value=','.join([instance.removing_uuid, instance.keeping_uuid]))} + + {{ mergeFormButtonText }} + + ${h.end_form()} + % endif + % endif + + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + % if not instance.merged and request.has_perm('people.merge'): + + % endif + + +${parent.body()} diff --git a/tailbone/views/master.py b/tailbone/views/master.py index b21051cb..c6f0253d 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -378,7 +378,7 @@ class MasterView(View): Return a dictionary of kwargs to be passed to the factory when creating new grid instances. """ - checkboxes = self.checkboxes + checkboxes = kwargs.get('checkboxes', self.checkboxes) if not checkboxes and self.mergeable and self.has_perm('merge'): checkboxes = True if not checkboxes and self.supports_set_enabled_toggle and self.has_perm('enable_disable_set'): diff --git a/tailbone/views/people.py b/tailbone/views/people.py index b8e06ced..5b4064b3 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -33,7 +33,7 @@ import sqlalchemy as sa from sqlalchemy import orm from rattail.db import model, api -from rattail.time import localtime +from rattail.time import localtime, make_utc from rattail.util import OrderedDict import colander @@ -68,6 +68,7 @@ class PersonView(MasterView): 'last_name', 'phone', 'email', + 'merge_requested', ] form_fields = [ @@ -93,6 +94,15 @@ class PersonView(MasterView): app = self.get_rattail_app() self.handler = app.get_people_handler() + def make_grid_kwargs(self, **kwargs): + kwargs = super(PersonView, self).make_grid_kwargs(**kwargs) + + # turn on checkboxes if user can create a merge reqeust + if self.mergeable and self.has_perm('request_merge'): + kwargs['checkboxes'] = True + + return kwargs + def configure_grid(self, g): super(PersonView, self).configure_grid(g) @@ -123,6 +133,9 @@ class PersonView(MasterView): g.sorters['email'] = lambda q, d: q.order_by(getattr(model.PersonEmailAddress.address, d)()) g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.PersonPhoneNumber.number, d)()) + g.set_label('merge_requested', "MR") + g.set_renderer('merge_requested', self.render_merge_requested) + g.set_sort_defaults('display_name') g.set_label('display_name', "Full Name") @@ -134,6 +147,23 @@ class PersonView(MasterView): g.set_link('first_name') g.set_link('last_name') + def render_merge_requested(self, person, field): + model = self.model + merge_request = self.Session.query(model.MergePeopleRequest)\ + .filter(sa.or_( + model.MergePeopleRequest.removing_uuid == person.uuid, + model.MergePeopleRequest.keeping_uuid == person.uuid))\ + .filter(model.MergePeopleRequest.merged == None)\ + .first() + if merge_request: + use_buefy = self.get_use_buefy() + if use_buefy: + return HTML.tag('span', + class_='has-text-danger has-text-weight-bold', + title="A merge has been requested for this person.", + c="MR") + return "MR" + def get_instance(self): # TODO: I don't recall why this fallback check for a vendor contact # exists here, but leaving it intact for now. @@ -383,7 +413,7 @@ class PersonView(MasterView): raise Exception(reason) def merge_objects(self, removing, keeping): - self.handler.perform_merge(removing, keeping) + self.handler.perform_merge(removing, keeping, user=self.request.user) def view_profile(self): """ @@ -696,6 +726,18 @@ class PersonView(MasterView): self.request.session.flash("User has been created: {}".format(user.username)) return self.redirect(self.request.route_url('users.view', uuid=user.uuid)) + def request_merge(self): + """ + Create a new merge request for the given 2 people. + """ + merge = self.model.MergePeopleRequest() + merge.removing_uuid = self.request.POST['removing_uuid'] + merge.keeping_uuid = self.request.POST['keeping_uuid'] + merge.requested_by = self.request.user + merge.requested = make_utc() + self.Session.add(merge) + return self.redirect(self.get_index_url()) + @classmethod def defaults(cls, config): cls._people_defaults(config) @@ -709,6 +751,7 @@ class PersonView(MasterView): instance_url_prefix = cls.get_instance_url_prefix() model_key = cls.get_model_key() model_title = cls.get_model_title() + model_title_plural = cls.get_model_title_plural() # "profile" perms # TODO: should let view class (or config) determine which of these are available @@ -777,6 +820,15 @@ class PersonView(MasterView): config.add_view(cls, attr='make_user', route_name='{}.make_user'.format(route_prefix), permission='users.create') + # merge requests + if cls.mergeable: + config.add_tailbone_permission(permission_prefix, '{}.request_merge'.format(permission_prefix), + "Request merge for 2 {}".format(model_title_plural)) + config.add_route('{}.request_merge'.format(route_prefix), '{}/request-merge'.format(url_prefix), + request_method='POST') + config.add_view(cls, attr='request_merge', route_name='{}.request_merge'.format(route_prefix), + permission='{}.request_merge'.format(permission_prefix)) + # TODO: deprecate / remove this PeopleView = PersonView @@ -888,6 +940,84 @@ class NoteSchema(colander.Schema): note_text = colander.SchemaNode(colander.String(), missing='') +class MergePeopleRequestView(MasterView): + """ + Master view for the MergePeopleRequest class. + """ + model_class = model.MergePeopleRequest + route_prefix = 'people_merge_requests' + url_prefix = '/people/merge-requests' + creatable = False + editable = False + + labels = { + 'removing_uuid': "Removing", + 'keeping_uuid': "Keeping", + } + + grid_columns = [ + 'removing_uuid', + 'keeping_uuid', + 'requested', + 'requested_by', + 'merged', + 'merged_by', + ] + + form_fields = [ + 'removing_uuid', + 'keeping_uuid', + 'requested', + 'requested_by', + 'merged', + 'merged_by', + ] + + def configure_grid(self, g): + super(MergePeopleRequestView, self).configure_grid(g) + + g.set_renderer('removing_uuid', self.render_referenced_person_name) + g.set_renderer('keeping_uuid', self.render_referenced_person_name) + + g.filters['merged'].default_active = True + g.filters['merged'].default_verb = 'is_null' + + g.set_sort_defaults('requested', 'desc') + + g.set_link('removing_uuid') + g.set_link('keeping_uuid') + + def render_referenced_person_name(self, merge_request, field): + uuid = getattr(merge_request, field) + person = self.Session.query(self.model.Person).get(uuid) + if person: + return six.text_type(person) + return "(person not found)" + + def get_instance_title(self, merge_request): + model = self.model + removing = self.Session.query(model.Person).get(merge_request.removing_uuid) + keeping = self.Session.query(model.Person).get(merge_request.keeping_uuid) + return "{} -> {}".format( + removing or "(not found)", + keeping or "(not found)") + + def configure_form(self, f): + super(MergePeopleRequestView, self).configure_form(f) + + f.set_renderer('removing_uuid', self.render_referenced_person) + f.set_renderer('keeping_uuid', self.render_referenced_person) + + def render_referenced_person(self, merge_request, field): + uuid = getattr(merge_request, field) + person = self.Session.query(self.model.Person).get(uuid) + if person: + text = six.text_type(person) + url = self.request.route_url('people.view', uuid=person.uuid) + return tags.link_to(text, url) + return "(person not found)" + + def includeme(config): # autocomplete @@ -900,3 +1030,4 @@ def includeme(config): PersonView.defaults(config) PersonNoteView.defaults(config) + MergePeopleRequestView.defaults(config) From a881b310bc96610598a91e5ea8e0e187ca4e4ada Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 23 Aug 2021 14:25:08 -0500 Subject: [PATCH 0019/1336] Allow customization of row 'view' action url --- tailbone/views/master.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index c6f0253d..6d5a3d96 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -490,9 +490,8 @@ class MasterView(View): # view action if self.rows_viewable: - view = lambda r, i: self.get_row_action_url('view', r) icon = 'eye' if use_buefy else 'zoomin' - actions.append(self.make_action('view', icon=icon, url=view)) + actions.append(self.make_action('view', icon=icon, url=self.row_view_action_url)) # edit action if self.rows_editable and self.has_perm('edit_row'): @@ -1344,6 +1343,9 @@ class MasterView(View): """ return True + def row_view_action_url(self, row, i): + return self.get_row_action_url('view', row) + def row_edit_action_url(self, row, i): if self.row_editable(row): return self.get_row_action_url('edit', row) From 3cf4c0f8e40d89cae2ec2a998cf029630006bd63 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 23 Aug 2021 19:26:50 -0500 Subject: [PATCH 0020/1336] Require explicit opt-in for "clicking grid row checks box" feature sometimes it makes sense *not* to enable that, in which case disabled probably should be the default --- tailbone/grids/core.py | 2 ++ tailbone/templates/grids/buefy.mako | 2 ++ tailbone/views/master.py | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index 5c8e1c87..f6df3375 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -75,6 +75,7 @@ class Grid(object): sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc', pageable=False, default_pagesize=20, default_page=1, checkboxes=False, checked=None, check_handler=None, check_all_handler=None, + clicking_row_checks_box=False, main_actions=[], more_actions=[], delete_speedbump=False, ajax_data_url=None, component='tailbone-grid', **kwargs): @@ -128,6 +129,7 @@ class Grid(object): self.checked = lambda item: False self.check_handler = check_handler self.check_all_handler = check_all_handler + self.clicking_row_checks_box = clicking_row_checks_box self.main_actions = main_actions or [] self.more_actions = more_actions or [] diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index 00b9ce9e..6eaff11a 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -143,8 +143,10 @@ :checkable="checkable" % if grid.checkboxes: :checked-rows.sync="checkedRows" + % if grid.clicking_row_checks_box: @click="rowClick" % endif + % endif % if grid.check_handler: @check="${grid.check_handler}" % endif diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 6d5a3d96..a1d6da79 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -73,6 +73,10 @@ class MasterView(View): pageable = True checkboxes = False + # set to True to allow user to click "anywhere" in a row in order + # to toggle its checkbox + clicking_row_checks_box = False + # set to True in order to encode search values as utf-8 use_byte_string_filters = False @@ -399,6 +403,7 @@ class MasterView(View): 'url': lambda obj: self.get_action_url('view', obj), 'checkboxes': checkboxes, 'checked': self.checked, + 'clicking_row_checks_box': self.clicking_row_checks_box, 'assume_local_times': self.has_local_times, } if 'main_actions' not in kwargs and 'more_actions' not in kwargs: From c3079fe899a5c54b77042fe7a0b7742204dd0ab8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 24 Aug 2021 09:39:45 -0500 Subject: [PATCH 0021/1336] Add `before_render_index()` customization hook for MasterView --- tailbone/views/master.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index a1d6da79..f2634328 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -335,8 +335,17 @@ class MasterView(View): context['download_results_rows_fields_available'] = available context['download_results_rows_fields_default'] = self.download_results_rows_fields_default(available) + self.before_render_index() return self.render_to_response('index', context) + def before_render_index(self): + """ + Perform any needed logic just prior to rendering the index + response. Note that this logic is invoked only when rendering + the main index page, but *not* invoked when refreshing partial + grid contents etc. + """ + def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs): """ Creates a new grid instance From 445862d48d5c07306c314faa27159c1e59f5761c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 26 Aug 2021 11:55:09 -0500 Subject: [PATCH 0022/1336] Update changelog --- CHANGES.rst | 16 ++++++++++++++++ tailbone/_version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7976b45d..069c6c94 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,22 @@ CHANGELOG ========= +0.8.139 (2021-08-26) +-------------------- + +* Tweak how email preview is sent, and attempt "to" is displayed. + +* Move "merge 2 people" logic into People Handler. + +* Expose "merge request tracking" feature for People data. + +* Allow customization of row 'view' action url. + +* Require explicit opt-in for "clicking grid row checks box" feature. + +* Add ``before_render_index()`` customization hook for MasterView. + + 0.8.138 (2021-08-04) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 10c12322..565c21e5 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.138' +__version__ = '0.8.139' From 897bb177bc649966283fda0c79dad1cd244cb32f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 28 Aug 2021 14:24:56 -0500 Subject: [PATCH 0023/1336] Make it easier to override rendering grid component in master/index was needed so i could pass extra event handlers to it --- tailbone/templates/master/index.mako | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako index d1389a47..8e855422 100644 --- a/tailbone/templates/master/index.mako +++ b/tailbone/templates/master/index.mako @@ -466,17 +466,22 @@ % endif + ${self.render_grid_component()} + + % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': + ${h.form('#', ref='deleteObjectForm')} + ${h.csrf_token(request)} + ${h.end_form()} + % endif + + +<%def name="render_grid_component()"> <${grid.component} :csrftoken="csrftoken" % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': @deleteActionClicked="deleteObject" % endif > - % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': - ${h.form('#', ref='deleteObjectForm')} - ${h.csrf_token(request)} - ${h.end_form()} - % endif <%def name="make_this_page_component()"> From fe584f193fca3fdcfaeef35623e034ec998ac5ea Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 28 Aug 2021 18:45:31 -0500 Subject: [PATCH 0024/1336] Always show all grid actions...for now we don't have a great way to accommodate too many actions; ideally could hide some in a drawer, but for now we just show them all for simplicity... --- tailbone/templates/grids/buefy.mako | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index 6eaff11a..90e8121b 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -187,7 +187,9 @@ % if grid.main_actions or grid.more_actions: - % for action in grid.main_actions: + ## TODO: we do not currently differentiate for "main vs. more" + ## here, but ideally we would tuck "more" away in a drawer etc. + % for action in grid.main_actions + grid.more_actions: Date: Sun, 29 Aug 2021 10:28:36 -0500 Subject: [PATCH 0025/1336] Allow grid columns to be *invisible* (but still present in grid) this can be useful when you need contextual data for a given row, for sake of front-end UI features, but do not want to actually show the extra data column(s) --- tailbone/grids/core.py | 29 ++++++++++++++++++++++++++++- tailbone/templates/grids/buefy.mako | 5 ++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index f6df3375..c476c426 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -69,7 +69,7 @@ class Grid(object): def __init__(self, key, data, columns=None, width='auto', request=None, model_class=None, model_title=None, model_title_plural=None, - enums={}, labels={}, assume_local_times=False, renderers={}, + enums={}, labels={}, assume_local_times=False, renderers={}, invisible=[], extra_row_class=None, linked_columns=[], url='#', joiners={}, filterable=False, filters={}, use_byte_string_filters=False, sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc', @@ -105,6 +105,7 @@ class Grid(object): self.labels = labels or {} self.assume_local_times = assume_local_times self.renderers = self.make_default_renderers(renderers or {}) + self.invisible = invisible or [] self.extra_row_class = extra_row_class self.linked_columns = linked_columns or [] self.url = url @@ -161,13 +162,38 @@ class Grid(object): return [prop.key for prop in mapper.iterate_properties] def hide_column(self, key): + """ + This *removes* a column from the grid, altogether. + + This method should really be renamed to ``remove_column()`` + instead. + """ if key in self.columns: self.columns.remove(key) def hide_columns(self, *keys): + """ + This *removes* columns from the grid, altogether. + + This method should really be renamed to ``remove_columns()`` + instead. + """ for key in keys: self.hide_column(key) + def set_invisible(self, key, invisible=True): + """ + Mark the given column as "invisible" (but do not remove it). + + Use :meth:`hide_column()` if you actually want to remove it. + """ + if invisible: + if key not in self.invisible: + self.invisible.append(key) + else: + if key in self.invisible: + self.invisible.remove(key) + def append(self, field): self.columns.append(field) @@ -1185,6 +1211,7 @@ class Grid(object): 'field': name, 'label': self.get_label(name), 'sortable': self.sortable and name in self.sorters, + 'visible': name not in self.invisible, }) return columns diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index 90e8121b..b55ff30e 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -176,7 +176,10 @@