From 0d9c5a078be54558796c79e79c290a0969f33314 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 18:21:59 -0500 Subject: [PATCH 001/320] Improve form support for view supplements this seems a bit hacky yet but works for now.. cf. field logic for Vendor -> Quickbooks Bank Accounts, which requires this --- tailbone/forms/core.py | 28 +++++++++++++++++++++++++++- tailbone/templates/master/form.mako | 17 +++++++++++++---- tailbone/views/master.py | 17 +++++++++++++++-- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index a5ab3355..9624f6fb 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -338,7 +338,7 @@ class Form(object): assume_local_times=False, renderers=None, renderer_kwargs={}, hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None, action_url=None, cancel_url=None, component='tailbone-form', - vuejs_component_kwargs=None, vuejs_field_converters={}, + vuejs_component_kwargs=None, vuejs_field_converters={}, json_data={}, included_templates={}, # TODO: ugh this is getting out hand! can_edit_help=False, edit_help_url=None, route_prefix=None, ): @@ -381,6 +381,8 @@ class Form(object): self.component = component self.vuejs_component_kwargs = vuejs_component_kwargs or {} self.vuejs_field_converters = vuejs_field_converters or {} + self.json_data = json_data or {} + self.included_templates = included_templates or {} self.can_edit_help = can_edit_help self.edit_help_url = edit_help_url self.route_prefix = route_prefix @@ -966,6 +968,30 @@ class Form(object): kwargs.setdefault(':configure-fields-help', 'configureFieldsHelp') return HTML.tag(self.component, **kwargs) + def set_json_data(self, key, value): + """ + Establish a data value for use in client-side JS. This value + will be JSON-encoded and made available to the + `` component within the client page. + """ + self.json_data[key] = value + + def include_template(self, template, context): + """ + Declare a JS template as required by the current form. This + template will then be included in the final page, so all + widgets behave correctly. + """ + self.included_templates[template] = context + + def render_included_templates(self): + templates = [] + for template, context in self.included_templates.items(): + context = dict(context) + context['form'] = self + templates.append(HTML.literal(render(template, context))) + return HTML.literal('\n').join(templates) + def render_field_complete(self, fieldname, bfield_attrs={}): """ Render the given field completely, i.e. with ```` diff --git a/tailbone/templates/master/form.mako b/tailbone/templates/master/form.mako index c142d8ef..1339bd91 100644 --- a/tailbone/templates/master/form.mako +++ b/tailbone/templates/master/form.mako @@ -3,8 +3,14 @@ <%def name="modify_this_page_vars()"> ${parent.modify_this_page_vars()} - % if master.deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': - - % endif + % endif + + + ${form.render_included_templates()} + diff --git a/tailbone/views/master.py b/tailbone/views/master.py index c6ce44e0..20dc0dcf 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -2695,8 +2695,15 @@ class MasterView(View): context.update(data) context.update(self.template_kwargs(**context)) - if hasattr(self, 'template_kwargs_{}'.format(template)): - context.update(getattr(self, 'template_kwargs_{}'.format(template))(**context)) + + method_name = f'template_kwargs_{template}' + if hasattr(self, method_name): + context.update(getattr(self, method_name)(**context)) + for supp in self.iter_view_supplements(): + if hasattr(supp, 'template_kwargs'): + context.update(getattr(supp, 'template_kwargs')(**context)) + if hasattr(supp, method_name): + context.update(getattr(supp, method_name)(**context)) # First try the template path most specific to the view. mako_path = '{}/{}.mako'.format(self.get_template_prefix(), template) @@ -4441,6 +4448,9 @@ class MasterView(View): if not self.has_perm('view_global'): obj.local_only = True + for supp in self.iter_view_supplements(): + obj = supp.objectify(obj, form, data) + return obj def objectify_contact(self, contact, data): @@ -5892,6 +5902,9 @@ class ViewSupplement(object): renderers, default values etc. for them. """ + def objectify(self, obj, form, data): + return obj + def get_xref_buttons(self, obj): return [] From b37981e83f1e39499729da468a2e00a129fc60de Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 20:09:39 -0500 Subject: [PATCH 002/320] Prevent multi-click for grid filters "Save Defaults" button --- tailbone/templates/grids/complete.mako | 6 ++++++ tailbone/templates/grids/filters.mako | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tailbone/templates/grids/complete.mako b/tailbone/templates/grids/complete.mako index f9e665dc..205012be 100644 --- a/tailbone/templates/grids/complete.mako +++ b/tailbone/templates/grids/complete.mako @@ -357,6 +357,8 @@ loading: false, ajaxDataUrl: ${json.dumps(grid.ajax_data_url)|n}, + savingDefaults: false, + data: ${grid.component_studly}CurrentData, rowStatusMap: ${json.dumps(grid_data['row_status_map'])|n}, @@ -589,6 +591,7 @@ this.firstItem = data.first_item this.lastItem = data.last_item this.loading = false + this.savingDefaults = false this.checkedRows = this.locateCheckedRows(data.checked_rows) if (success) { success() @@ -600,6 +603,7 @@ duration: 2000, // 4 seconds }) this.loading = false + this.savingDefaults = false if (failure) { failure() } @@ -609,6 +613,7 @@ this.data = [] this.total = 0 this.loading = false + this.savingDefaults = false if (failure) { failure() } @@ -805,6 +810,7 @@ }, saveDefaults() { + this.savingDefaults = true // apply current filters as normal, but add special directive this.applyFilters({'save-current-filters-as-defaults': true}) diff --git a/tailbone/templates/grids/filters.mako b/tailbone/templates/grids/filters.mako index 5e1fef9b..4c584883 100644 --- a/tailbone/templates/grids/filters.mako +++ b/tailbone/templates/grids/filters.mako @@ -60,8 +60,9 @@ - Save Defaults + class="control" + :disabled="savingDefaults"> + {{ savingDefaults ? "Working, please wait..." : "Save Defaults" }} % endif From 9065f42195f1d64bb02452727fb6acf44b280623 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 20:10:10 -0500 Subject: [PATCH 003/320] Fix typo when getting app instance --- tailbone/forms/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index 9624f6fb..496d59ee 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -1156,7 +1156,7 @@ class Form(object): value = self.obtain_value(record, field_name) if value is None: return "" - app = self.get_rattail_app() + app = self.request.rattail_config.get_app() value = app.localtime(value) return raw_datetime(self.request.rattail_config, value) @@ -1186,7 +1186,7 @@ class Form(object): value = self.obtain_value(obj, field) if value is None: return "" - app = self.get_rattail_app() + app = self.request.rattail_config.get_app() return app.render_quantity(value) def render_percent(self, obj, field): From 5a7deadba221ad45b764d9eedba14a6e3b07cb69 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 20:11:15 -0500 Subject: [PATCH 004/320] Update changelog --- CHANGES.rst | 11 +++++++++++ tailbone/_version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 99ae8ed9..e028452c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,17 @@ CHANGELOG Unreleased ---------- + +0.9.93 (2024-04-16) +------------------- + +* Improve form support for view supplements. + +* Prevent multi-click for grid filters "Save Defaults" button. + +* Fix typo when getting app instance. + + 0.9.92 (2024-04-16) ------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 701a9305..109cbcbd 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.9.92' +__version__ = '0.9.93' From e7b8b6e818015ecff1604e71c558d36d5095f34e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 21:13:53 -0500 Subject: [PATCH 005/320] Fix master template bug when no form in context --- tailbone/templates/master/form.mako | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tailbone/templates/master/form.mako b/tailbone/templates/master/form.mako index 1339bd91..dfe56fa8 100644 --- a/tailbone/templates/master/form.mako +++ b/tailbone/templates/master/form.mako @@ -6,9 +6,11 @@ - ${form.render_included_templates()} + % if form is not Undefined: + ${form.render_included_templates()} + % endif From a95cc2b9e80bc5f3eab39352dbe1674d521ad8d2 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 21:14:23 -0500 Subject: [PATCH 006/320] Update changelog --- CHANGES.rst | 5 +++++ tailbone/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index e028452c..ba3f2b97 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,11 @@ CHANGELOG Unreleased ---------- +0.9.94 (2024-04-16) +------------------- + +* Fix master template bug when no form in context. + 0.9.93 (2024-04-16) ------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 109cbcbd..665e5baa 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.9.93' +__version__ = '0.9.94' From 7fa39d42e2caf156022dc55187338936e9145db8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 23:26:46 -0500 Subject: [PATCH 007/320] Fix ASGI websockets when serving on sub-path under site root --- CHANGES.rst | 3 +++ tailbone/asgi.py | 16 +++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ba3f2b97..7321ee2c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ CHANGELOG Unreleased ---------- +* Fix ASGI websockets when serving on sub-path under site root. + + 0.9.94 (2024-04-16) ------------------- diff --git a/tailbone/asgi.py b/tailbone/asgi.py index f2146577..1afbe12a 100644 --- a/tailbone/asgi.py +++ b/tailbone/asgi.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. # @@ -24,14 +24,10 @@ ASGI App Utilities """ -from __future__ import unicode_literals, absolute_import - import os +import configparser import logging -import six -from six.moves import configparser - from rattail.util import load_object from asgiref.wsgi import WsgiToAsgi @@ -49,6 +45,12 @@ class TailboneWsgiToAsgi(WsgiToAsgi): protocol = scope['type'] path = scope['path'] + # strip off the root path, if non-empty. needed for serving + # under /poser or anything other than true site root + root_path = scope['root_path'] + if root_path and path.startswith(root_path): + path = path[len(root_path):] + if protocol == 'websocket': websockets = self.wsgi_application.registry.get( 'tailbone_websockets', {}) @@ -85,7 +87,7 @@ def make_asgi_app(main_app=None): # parse the settings needed for pyramid app settings = dict(parser.items('app:main')) - if isinstance(main_app, six.string_types): + if isinstance(main_app, str): make_wsgi_app = load_object(main_app) elif callable(main_app): make_wsgi_app = main_app From e82f0f37d860c1c63e924f4f042f6b95c29cac3a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 Apr 2024 23:29:56 -0500 Subject: [PATCH 008/320] Fix raw query to avoid SQLAlchemy 2.x warnings --- CHANGES.rst | 2 ++ tailbone/views/datasync.py | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7321ee2c..0ae23410 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,8 @@ Unreleased * Fix ASGI websockets when serving on sub-path under site root. +* Fix raw query to avoid SQLAlchemy 2.x warnings. + 0.9.94 (2024-04-16) ------------------- diff --git a/tailbone/views/datasync.py b/tailbone/views/datasync.py index ac0fec52..b734325f 100644 --- a/tailbone/views/datasync.py +++ b/tailbone/views/datasync.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar +# Copyright © 2010-2024 Lance Edgar # # This file is part of Rattail. # @@ -30,7 +30,7 @@ import logging import sqlalchemy as sa -from rattail.db import model +from rattail.db.model import DataSyncChange from rattail.datasync.util import purge_datasync_settings from rattail.util import simple_error @@ -71,7 +71,7 @@ class DataSyncThreadView(MasterView): ] def __init__(self, request, context=None): - super(DataSyncThreadView, self).__init__(request, context=context) + super().__init__(request, context=context) app = self.get_rattail_app() self.datasync_handler = app.get_datasync_handler() @@ -106,7 +106,7 @@ class DataSyncThreadView(MasterView): from datasync_change group by source, consumer """ - result = self.Session.execute(sql) + result = self.Session.execute(sa.text(sql)) all_changes = {} for row in result: all_changes[(row.source, row.consumer)] = row.changes @@ -368,7 +368,7 @@ class DataSyncChangeView(MasterView): """ Master view for the DataSyncChange model. """ - model_class = model.DataSyncChange + model_class = DataSyncChange url_prefix = '/datasync/changes' permission_prefix = 'datasync_changes' creatable = False @@ -390,7 +390,7 @@ class DataSyncChangeView(MasterView): ] def configure_grid(self, g): - super(DataSyncChangeView, self).configure_grid(g) + super().configure_grid(g) # batch_sequence g.set_label('batch_sequence', "Batch Seq.") @@ -404,7 +404,7 @@ class DataSyncChangeView(MasterView): return kwargs def configure_form(self, f): - super(DataSyncChangeView, self).configure_form(f) + super().configure_form(f) f.set_readonly('obtained') From 1fa6e35663b6a144d64d3fe4799564475719d1b4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 19 Apr 2024 17:45:58 -0500 Subject: [PATCH 009/320] Remove config "style" from appinfo page there is only one style now (finally) --- tailbone/templates/appinfo/index.mako | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailbone/templates/appinfo/index.mako b/tailbone/templates/appinfo/index.mako index 62a911ee..ac67e582 100644 --- a/tailbone/templates/appinfo/index.mako +++ b/tailbone/templates/appinfo/index.mako @@ -51,7 +51,7 @@ - Configuration Files (style: ${request.rattail_config._style}) + Configuration Files From 5cb643a32ad1b7196eddfe2e254dfe1b6d37850e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 19 Apr 2024 19:47:41 -0500 Subject: [PATCH 010/320] Update changelog --- CHANGES.rst | 5 +++++ tailbone/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0ae23410..53bc179f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,10 +5,15 @@ CHANGELOG Unreleased ---------- +0.9.95 (2024-04-19) +------------------- + * Fix ASGI websockets when serving on sub-path under site root. * Fix raw query to avoid SQLAlchemy 2.x warnings. +* Remove config "style" from appinfo page. + 0.9.94 (2024-04-16) ------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 665e5baa..016440ba 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.9.94' +__version__ = '0.9.95' From 36b9e00dc9ba21bef9b0ab699d088635dfd720d8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 19 Apr 2024 20:15:44 -0500 Subject: [PATCH 011/320] Remove unused code for `webhelpers2_grid` --- setup.cfg | 6 ---- tailbone/grids/core.py | 64 ------------------------------------------ 2 files changed, 70 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2195aee9..514b77ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,12 +40,6 @@ classifiers = [options] install_requires = - # TODO: apparently they jumped from 0.1 to 0.9 and that broke us... - # (0.1 was released on 2014-09-14 and then 0.9 came out on 2018-09-27) - # (i've cached 0.1 at pypi.rattailproject.org just in case it disappears) - # (still, probably a better idea is to refactor so we can use 0.9) - webhelpers2_grid==0.1 - # TODO: remove once their bug is fixed? idk what this is about yet... deform<2.0.15 diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index f905659e..41d75fc2 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -34,7 +34,6 @@ from sqlalchemy import orm from rattail.db.types import GPCType from rattail.util import prettify, pretty_boolean, pretty_quantity -import webhelpers2_grid from pyramid.renderers import render from webhelpers2.html import HTML, tags from paginate_sqlalchemy import SqlalchemyOrmPage @@ -1721,69 +1720,6 @@ class Grid(object): return False -class CustomWebhelpersGrid(webhelpers2_grid.Grid): - """ - Implement column sorting links etc. for webhelpers2_grid - """ - - def __init__(self, itemlist, columns, **kwargs): - self.renderers = kwargs.pop('renderers', {}) - self.linked_columns = kwargs.pop('linked_columns', []) - self.extra_record_class = kwargs.pop('extra_record_class', None) - super().__init__(itemlist, columns, **kwargs) - - def generate_header_link(self, column_number, column, label_text): - - # display column header as simple no-op link; client-side JS takes care - # of the rest for us - label_text = tags.link_to(label_text, '#', data_sortkey=column) - - # Is the current column the one we're ordering on? - if (column == self.order_column): - return self.default_header_ordered_column_format(column_number, - column, - label_text) - else: - return self.default_header_column_format(column_number, column, - label_text) - - def default_record_format(self, i, record, columns): - kwargs = { - 'class_': self.get_record_class(i, record, columns), - } - if hasattr(record, 'uuid'): - kwargs['data_uuid'] = record.uuid - return HTML.tag('tr', columns, **kwargs) - - def get_record_class(self, i, record, columns): - if i % 2 == 0: - cls = 'even r{}'.format(i) - else: - cls = 'odd r{}'.format(i) - if self.extra_record_class: - extra = self.extra_record_class(record, i) - if extra: - cls = '{} {}'.format(cls, extra) - return cls - - def get_column_value(self, column_number, i, record, column_name): - if self.renderers and column_name in self.renderers: - return self.renderers[column_name](record, column_name) - try: - return record[column_name] - except TypeError: - return getattr(record, column_name) - - def default_column_format(self, column_number, i, record, column_name): - value = self.get_column_value(column_number, i, record, column_name) - if self.linked_columns and column_name in self.linked_columns and ( - value is not None and value != ''): - url = self.url_generator(record, i) - value = tags.link_to(value, url) - class_name = 'c{} {}'.format(column_number, column_name) - return HTML.tag('td', value, class_=class_name) - - class GridAction(object): """ Represents an action available to a grid. This is used to construct the From 49da9776e72f68483e67f6af2769169f92284cca Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 19 Apr 2024 20:25:07 -0500 Subject: [PATCH 012/320] Remove unused test fixtures --- setup.cfg | 4 ++-- tests/fixtures.py | 28 ---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 tests/fixtures.py diff --git a/setup.cfg b/setup.cfg index 514b77ab..7fcce722 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,7 +73,7 @@ install_requires = zope.sqlalchemy tests_require = Tailbone[tests] -test_suite = nose.collector +test_suite = tests packages = find: include_package_data = True zip_safe = False @@ -87,7 +87,7 @@ exclude = [options.extras_require] docs = Sphinx; sphinx-rtd-theme -tests = coverage; fixture; mock; nose; pytest; pytest-cov +tests = coverage; mock; pytest; pytest-cov [options.entry_points] diff --git a/tests/fixtures.py b/tests/fixtures.py deleted file mode 100644 index a07825fd..00000000 --- a/tests/fixtures.py +++ /dev/null @@ -1,28 +0,0 @@ - -import fixture - -from rattail.db import model - - -class DepartmentData(fixture.DataSet): - - class grocery: - number = 1 - name = 'Grocery' - - class supplements: - number = 2 - name = 'Supplements' - - -def load_fixtures(engine): - - dbfixture = fixture.SQLAlchemyFixture( - env={ - 'DepartmentData': model.Department, - }, - engine=engine) - - data = dbfixture.data(DepartmentData) - - data.setup() From 8781e34c98dd5215f346af8efe1bcb98ac412640 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 19 Apr 2024 21:18:57 -0500 Subject: [PATCH 013/320] Rename setting for custom user css (remove "buefy") but have to keep support for older setting name for now --- tailbone/subscribers.py | 10 ++++++++-- tailbone/templates/users/preferences.mako | 4 ++-- tailbone/views/users.py | 13 +++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py index 33a9d749..1dc0592a 100644 --- a/tailbone/subscribers.py +++ b/tailbone/subscribers.py @@ -27,6 +27,7 @@ Event Subscribers import six import json import datetime +import warnings import rattail @@ -181,8 +182,13 @@ def before_render(event): # maybe set custom stylesheet css = None if request.user: - css = request.rattail_config.get('tailbone.{}'.format(request.user.uuid), - 'buefy_css') + css = rattail_config.get(f'tailbone.{request.user.uuid}', 'user_css') + if not css: + css = rattail_config.get(f'tailbone.{request.user.uuid}', 'buefy_css') + if css: + warnings.warn(f"setting 'tailbone.{request.user.uuid}.buefy_css' should be" + f"changed to 'tailbone.{request.user.uuid}.user_css'", + DeprecationWarning) renderer_globals['user_css'] = css # add global search data for quick access diff --git a/tailbone/templates/users/preferences.mako b/tailbone/templates/users/preferences.mako index f1432676..c2e17396 100644 --- a/tailbone/templates/users/preferences.mako +++ b/tailbone/templates/users/preferences.mako @@ -27,8 +27,8 @@
-