From 6463df7224211bd1d82cfc08cbc8c8fba2c7f290 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 22 Jun 2020 14:59:17 -0500
Subject: [PATCH 0001/1545] Add dropdown, autohide magic when editing Role
permissions
only for Buefy theme though
---
tailbone/templates/deform/permissions.pt | 59 +++++++++++++++++++++++-
tailbone/templates/roles/edit.mako | 11 +++++
tailbone/views/roles.py | 5 +-
3 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/tailbone/templates/deform/permissions.pt b/tailbone/templates/deform/permissions.pt
index bafa7563..f5cbeef4 100644
--- a/tailbone/templates/deform/permissions.pt
+++ b/tailbone/templates/deform/permissions.pt
@@ -1,6 +1,10 @@
+
+
${field.start_mapping()}
@@ -29,4 +33,57 @@
${field.end_mapping()}
+
+
+
+ ${field.start_mapping()}
+
+
+
+
+ showing group:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${field.end_mapping()}
+
diff --git a/tailbone/templates/roles/edit.mako b/tailbone/templates/roles/edit.mako
index 6f89b44d..67f63013 100644
--- a/tailbone/templates/roles/edit.mako
+++ b/tailbone/templates/roles/edit.mako
@@ -6,4 +6,15 @@
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
%def>
+<%def name="modify_this_page_vars()">
+ ${parent.modify_this_page_vars()}
+
+%def>
+
${parent.body()}
diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py
index adac7fb5..d0dd8967 100644
--- a/tailbone/views/roles.py
+++ b/tailbone/views/roles.py
@@ -137,6 +137,7 @@ class RolesView(PrincipalMasterView):
def configure_form(self, f):
super(RolesView, self).configure_form(f)
role = f.model_instance
+ use_buefy = self.get_use_buefy()
# name
f.set_validator('name', self.unique_name)
@@ -148,7 +149,9 @@ class RolesView(PrincipalMasterView):
self.tailbone_permissions = self.get_available_permissions()
f.set_renderer('permissions', PermissionsRenderer(permissions=self.tailbone_permissions))
f.set_node('permissions', colander.Set())
- f.set_widget('permissions', PermissionsWidget(permissions=self.tailbone_permissions))
+ f.set_widget('permissions', PermissionsWidget(
+ permissions=self.tailbone_permissions,
+ use_buefy=use_buefy))
if self.editing:
granted = []
for groupkey in self.tailbone_permissions:
From e5f08313690526d5f6dc498dffb6c4d9bc35d5f5 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 22 Jun 2020 16:00:33 -0500
Subject: [PATCH 0002/1545] Add ability to download roles / permissions matrix
as Excel file
---
tailbone/templates/roles/index.mako | 12 +++++
tailbone/views/roles.py | 77 +++++++++++++++++++++++++++++
2 files changed, 89 insertions(+)
create mode 100644 tailbone/templates/roles/index.mako
diff --git a/tailbone/templates/roles/index.mako b/tailbone/templates/roles/index.mako
new file mode 100644
index 00000000..857e5f6a
--- /dev/null
+++ b/tailbone/templates/roles/index.mako
@@ -0,0 +1,12 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/principal/index.mako" />
+
+<%def name="context_menu_items()">
+ ${parent.context_menu_items()}
+ % if master.has_perm('download_permissions_matrix'):
+ ${h.link_to("Download Permissions Matrix", url('roles.download_permissions_matrix'))}
+ % endif
+%def>
+
+
+${parent.body()}
diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py
index d0dd8967..613576e6 100644
--- a/tailbone/views/roles.py
+++ b/tailbone/views/roles.py
@@ -26,12 +26,16 @@ Role Views
from __future__ import unicode_literals, absolute_import
+import os
+
import six
from sqlalchemy import orm
+from openpyxl.styles import Font, PatternFill
from rattail.db import model
from rattail.db.auth import (has_permission, grant_permission, revoke_permission,
administrator_role, guest_role, authenticated_role)
+from rattail.excel import ExcelWriter
import colander
from deform import widget as dfwidget
@@ -278,6 +282,69 @@ class RolesView(PrincipalMasterView):
roles.append(role)
return roles
+ def download_permissions_matrix(self):
+ """
+ View which renders the complete role / permissions matrix data into an
+ Excel spreadsheet, and returns that file.
+ """
+ roles = self.Session.query(model.Role)\
+ .order_by(model.Role.name)\
+ .all()
+
+ permissions = self.get_available_permissions()
+
+ # prep the excel writer
+ path = os.path.join(self.rattail_config.workdir(),
+ 'permissions-matrix.xlsx')
+ writer = ExcelWriter(path, None)
+ sheet = writer.sheet
+
+ # write header
+ sheet.cell(row=1, column=1, value="")
+ for i, role in enumerate(roles, 2):
+ sheet.cell(row=1, column=i, value=role.name)
+
+ # font and fill pattern for permission group rows
+ bold = Font(bold=True)
+ group_fill = PatternFill(patternType='solid',
+ fgColor='d9d9d9',
+ bgColor='d9d9d9')
+
+ # now we'll write the rows
+ writing_row = 2
+ for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
+ group = permissions[groupkey]
+
+ # group label is bold, with fill pattern
+ cell = sheet.cell(row=writing_row, column=1, value=group['label'])
+ cell.font = bold
+ cell.fill = group_fill
+
+ # continue fill pattern for rest of group row
+ for col, role in enumerate(roles, 2):
+ cell = sheet.cell(row=writing_row, column=col)
+ cell.fill = group_fill
+
+ # okay, that row is done
+ writing_row += 1
+
+ # now we list each perm in the group
+ perms = group['perms']
+ for key in sorted(perms, key=lambda p: perms[p]['label'].lower()):
+ sheet.cell(row=writing_row, column=1, value=perms[key]['label'])
+
+ # and show an 'X' for any role which has this perm
+ for col, role in enumerate(roles, 2):
+ if has_permission(self.Session(), role, key, include_guest=False):
+ sheet.cell(row=writing_row, column=col, value="X")
+
+ writing_row += 1
+
+ writer.auto_resize()
+ writer.auto_freeze()
+ writer.save()
+ return self.file_response(path)
+
@classmethod
def defaults(cls, config):
cls._principal_defaults(config)
@@ -286,6 +353,8 @@ class RolesView(PrincipalMasterView):
@classmethod
def _role_defaults(cls, config):
+ route_prefix = cls.get_route_prefix()
+ url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
# extra permissions for editing built-in roles etc.
@@ -296,6 +365,14 @@ class RolesView(PrincipalMasterView):
config.add_tailbone_permission(permission_prefix, '{}.edit_my'.format(permission_prefix),
"Edit Role(s) to which current user belongs")
+ # download permissions matrix
+ config.add_tailbone_permission(permission_prefix, '{}.download_permissions_matrix'.format(permission_prefix),
+ "Download complete Role/Permissions matrix")
+ config.add_route('{}.download_permissions_matrix'.format(route_prefix), '{}/permissions-matrix'.format(url_prefix),
+ request_method='GET')
+ config.add_view(cls, attr='download_permissions_matrix', route_name='{}.download_permissions_matrix'.format(route_prefix),
+ permission='{}.download_permissions_matrix'.format(permission_prefix))
+
class PermissionsWidget(dfwidget.Widget):
template = 'permissions'
From bb11263badddf201fa4eb75832718b518ee18c80 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 22 Jun 2020 16:21:45 -0500
Subject: [PATCH 0003/1545] Tweak how we freeze column for role/perm matrix
---
tailbone/views/roles.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py
index 613576e6..e30c38be 100644
--- a/tailbone/views/roles.py
+++ b/tailbone/views/roles.py
@@ -341,7 +341,7 @@ class RolesView(PrincipalMasterView):
writing_row += 1
writer.auto_resize()
- writer.auto_freeze()
+ writer.auto_freeze(column=2)
writer.save()
return self.file_response(path)
From c7c3dea6b23f18e0a21e23803d257311ece11df0 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 22 Jun 2020 18:26:43 -0500
Subject: [PATCH 0004/1545] Improve support for composite key in master view
---
tailbone/views/master.py | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index b445bf3e..04cd30de 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -2475,7 +2475,10 @@ class MasterView(View):
return cls.get_route_prefix()
def get_row_grid_key(self):
- return '{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
+ model_key = self.get_model_key(as_tuple=True)
+ key = '.'.join([self.get_grid_key()] +
+ [self.request.matchdict[k] for k in model_key])
+ return key
def get_grid_actions(self):
main, more = self.get_main_actions(), self.get_more_actions()
@@ -2875,10 +2878,12 @@ class MasterView(View):
query = self.Session.query(self.get_model_class())
for i, model_key in enumerate(model_keys):
key = self.request.matchdict[model_key]
+ if self.key_is_integer(model_key):
+ key = int(key)
query = query.filter(getattr(self.model_class, model_key) == key)
try:
obj = query.one()
- except NoResultFound:
+ except orm.exc.NoResultFound:
raise self.notfound()
# pretend global object doesn't exist, unless access allowed
@@ -2889,6 +2894,18 @@ class MasterView(View):
return obj
+ def key_is_integer(self, model_key):
+
+ # inspect model class to determine if model_key is numeric
+ cls = self.get_model_class(error=False)
+ if cls:
+ attr = getattr(cls, model_key)
+ if isinstance(attr.type, sa.Integer):
+ return True
+
+ # do not assume integer by default
+ return False
+
def get_instance_title(self, instance):
"""
Return a "pretty" title for the instance, to be used in the page title etc.
From c1a2bb978c3831461aeedda298cae967d304df66 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 24 Jun 2020 10:53:43 -0500
Subject: [PATCH 0005/1545] Use byte string filters for row grid too
if master view needs them at all, chances are they should apply to row grid as
well as main grid
---
tailbone/views/master.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 04cd30de..163fe91e 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -468,6 +468,7 @@ class MasterView(View):
'model_class': self.model_row_class,
'width': 'full',
'filterable': self.rows_filterable,
+ 'use_byte_string_filters': self.use_byte_string_filters,
'sortable': self.rows_sortable,
'pageable': self.rows_pageable,
'default_pagesize': self.rows_default_pagesize,
From e943a1cd44307071bef631e53af7932600d4b77e Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 24 Jun 2020 11:36:58 -0500
Subject: [PATCH 0006/1545] Convert mako directories to list, if it's a string
so we can push a new path to it, for sake of theme
---
tailbone/app.py | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/tailbone/app.py b/tailbone/app.py
index 810d502c..44d9976f 100644
--- a/tailbone/app.py
+++ b/tailbone/app.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2018 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -29,10 +29,11 @@ from __future__ import unicode_literals, absolute_import
import os
import warnings
+import six
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker, scoped_session
-from rattail.config import make_config
+from rattail.config import make_config, parse_list
from rattail.exceptions import ConfigurationError
from rattail.db.types import GPCType
@@ -161,8 +162,13 @@ def establish_theme(settings):
theme = get_effective_theme(rattail_config)
settings['tailbone.theme'] = theme
+ directories = settings['mako.directories']
+ if isinstance(directories, six.string_types):
+ directories = parse_list(directories)
+
path = get_theme_template_path(rattail_config)
- settings['mako.directories'].insert(0, path)
+ directories.insert(0, path)
+ settings['mako.directories'] = directories
def configure_postgresql(pyramid_config):
From bea671987c3bac8201ce7d8c418245d37a0da367 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 24 Jun 2020 12:07:46 -0500
Subject: [PATCH 0007/1545] Update changelog
---
CHANGES.rst | 14 ++++++++++++++
tailbone/_version.py | 2 +-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index e29156b0..e33a7134 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,20 @@
CHANGELOG
=========
+0.8.97 (2020-06-24)
+-------------------
+
+* Add dropdown, autohide magic when editing Role permissions.
+
+* Add ability to download roles / permissions matrix as Excel file.
+
+* Improve support for composite key in master view.
+
+* Use byte string filters for row grid too.
+
+* Convert mako directories to list, if it's a string.
+
+
0.8.96 (2020-06-17)
-------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index ff4d0a2f..1137fc33 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.96'
+__version__ = '0.8.97'
From aac9bad7ec46a1f1b1066352bdb135b988816039 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 29 Jun 2020 13:07:49 -0500
Subject: [PATCH 0008/1545] Freeze version for 'Chameleon' dependency
pending the fix, which should come w/ next 'deform' release
---
setup.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/setup.py b/setup.py
index 2e0315b3..5731414c 100644
--- a/setup.py
+++ b/setup.py
@@ -76,6 +76,11 @@ requires = [
# TODO: remove version cap once we can drop support for python 2.x
'cornice<5.0', # 3.4.2 4.0.1
+ # TODO: remove this cap once deform releases a new version
+ # cf. https://github.com/malthe/chameleon/issues/318
+ # and https://github.com/Pylons/deform/pull/418
+ 'Chameleon<3.8.0', # 3.7.4
+
'colander', # 1.7.0
'ColanderAlchemy', # 0.3.3
'deform', # 2.0.4
From 66bf11e89326477b5364b2db93baa5fd57725dfe Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 29 Jun 2020 16:57:05 -0500
Subject: [PATCH 0009/1545] Tweak field label for `Product.item_id`
---
tailbone/views/products.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index aaf70774..6585fb81 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -87,6 +87,7 @@ class ProductsView(MasterView):
results_downloadable_xlsx = True
labels = {
+ 'item_id': "Item ID",
'upc': "UPC",
'status_code': "Status",
'tax1': "Tax 1",
From 6577b3752fbc63d8643a95b7418d5b1cf3823139 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 2 Jul 2020 12:42:42 -0500
Subject: [PATCH 0010/1545] Avoid latest SQLAlchemy-Utils when running tests
for python2.7
---
tox.ini | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/tox.ini b/tox.ini
index 18df2205..1218fec2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,6 +12,15 @@ commands =
pip install --upgrade --upgrade-strategy eager Tailbone rattail[auth,bouncer] rattail-tempmon
nosetests {posargs}
+[testenv:py27]
+# TODO: this is only here to avoid latest SA-Utils on python2.7
+deps =
+ coverage
+ fixture
+ mock
+ nose
+ SQLAlchemy-Utils<0.36.7
+
[testenv:coverage]
basepython = python
commands =
From 4f2f192783c59ffcf218f5bc15640ed7fe37650d Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Fri, 3 Jul 2020 19:14:30 -0500
Subject: [PATCH 0011/1545] Revert "Freeze version for 'Chameleon' dependency"
This reverts commit aac9bad7ec46a1f1b1066352bdb135b988816039.
all should be good now, per new 'deform' release
---
setup.py | 5 -----
1 file changed, 5 deletions(-)
diff --git a/setup.py b/setup.py
index 5731414c..2e0315b3 100644
--- a/setup.py
+++ b/setup.py
@@ -76,11 +76,6 @@ requires = [
# TODO: remove version cap once we can drop support for python 2.x
'cornice<5.0', # 3.4.2 4.0.1
- # TODO: remove this cap once deform releases a new version
- # cf. https://github.com/malthe/chameleon/issues/318
- # and https://github.com/Pylons/deform/pull/418
- 'Chameleon<3.8.0', # 3.7.4
-
'colander', # 1.7.0
'ColanderAlchemy', # 0.3.3
'deform', # 2.0.4
From 793d80f0929843ff00c79c8b91352456cee3e279 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sat, 4 Jul 2020 11:44:09 -0500
Subject: [PATCH 0012/1545] Make field list explicit for Department views
---
tailbone/views/departments.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tailbone/views/departments.py b/tailbone/views/departments.py
index 7b31fd31..c1369543 100644
--- a/tailbone/views/departments.py
+++ b/tailbone/views/departments.py
@@ -52,6 +52,14 @@ class DepartmentsView(MasterView):
'exempt_from_gross_sales',
]
+ form_fields = [
+ 'number',
+ 'name',
+ 'product',
+ 'personnel',
+ 'exempt_from_gross_sales',
+ ]
+
def configure_grid(self, g):
super(DepartmentsView, self).configure_grid(g)
g.filters['name'].default_active = True
From ca64d52021a37dc70d002016d49295993b005606 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 5 Jul 2020 00:21:00 -0500
Subject: [PATCH 0013/1545] Make field list explicit for Store views
---
tailbone/views/stores.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tailbone/views/stores.py b/tailbone/views/stores.py
index b46fa5f4..fa94f92e 100644
--- a/tailbone/views/stores.py
+++ b/tailbone/views/stores.py
@@ -50,6 +50,14 @@ class StoresView(MasterView):
'email',
]
+ form_fields = [
+ 'id',
+ 'name',
+ 'phone',
+ 'email',
+ 'database_key',
+ ]
+
labels = {
'id': "ID",
'phone': "Phone Number",
From 0dfe52a42d26ad2816fbab78cfe1afb68dc0a37a Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Tue, 7 Jul 2020 19:23:52 -0500
Subject: [PATCH 0014/1545] Don't allow "execute results" for any batches by
default
custom app must always explicitly opt-in to that feature
---
tailbone/views/batch/inventory.py | 1 -
tailbone/views/batch/labels.py | 3 +--
tailbone/views/handheld.py | 3 +--
3 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/tailbone/views/batch/inventory.py b/tailbone/views/batch/inventory.py
index fd4b9b07..26123707 100644
--- a/tailbone/views/batch/inventory.py
+++ b/tailbone/views/batch/inventory.py
@@ -63,7 +63,6 @@ class InventoryBatchView(BatchMasterView):
index_title = "Inventory"
rows_creatable = True
bulk_deletable = True
- results_executable = True
mobile_creatable = True
mobile_rows_creatable = True
diff --git a/tailbone/views/batch/labels.py b/tailbone/views/batch/labels.py
index 7aed7b5a..8aeab62b 100644
--- a/tailbone/views/batch/labels.py
+++ b/tailbone/views/batch/labels.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2019 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -53,7 +53,6 @@ class LabelBatchView(BatchMasterView):
rows_editable = True
rows_bulk_deletable = True
cloneable = True
- results_executable = True
row_grid_columns = [
'sequence',
diff --git a/tailbone/views/handheld.py b/tailbone/views/handheld.py
index 1db59662..66cd480c 100644
--- a/tailbone/views/handheld.py
+++ b/tailbone/views/handheld.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2019 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -64,7 +64,6 @@ class HandheldBatchView(FileBatchMasterView):
url_prefix = '/batch/handheld'
execution_options_schema = ExecutionOptions
editable = False
- results_executable = True
model_row_class = model.HandheldBatchRow
rows_creatable = False
From 3819dd94698d6b6733ab3866093605b041545fab Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 15 Jul 2020 22:05:57 -0500
Subject: [PATCH 0015/1545] Fix pagination sync issue with buefy grid tables
---
tailbone/templates/grids/buefy.mako | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako
index f1bfadf1..699161c8 100644
--- a/tailbone/templates/grids/buefy.mako
+++ b/tailbone/templates/grids/buefy.mako
@@ -334,7 +334,7 @@
},
onPageChange(page) {
- this.page = page
+ this.currentPage = page
this.loadAsyncData()
},
@@ -343,7 +343,7 @@
this.sortOrder = order
// always reset to first page when changing sort options
// TODO: i mean..right? would we ever not want that?
- this.page = 1
+ this.currentPage = 1
this.loadAsyncData()
},
From 925e5e0731f87d26fe90a5d36f83641c8cabb337 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 16 Jul 2020 19:43:33 -0500
Subject: [PATCH 0016/1545] Fix permissions wiget bug when creating new role
---
tailbone/templates/roles/create.mako | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/tailbone/templates/roles/create.mako b/tailbone/templates/roles/create.mako
index 235765f9..625b2675 100644
--- a/tailbone/templates/roles/create.mako
+++ b/tailbone/templates/roles/create.mako
@@ -6,4 +6,15 @@
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
%def>
+<%def name="modify_this_page_vars()">
+ ${parent.modify_this_page_vars()}
+
+%def>
+
${parent.body()}
From 4c3112b85bdf994cadb24ce0a2b93e85b29ef879 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 19 Jul 2020 18:43:31 -0500
Subject: [PATCH 0017/1545] Fix another pagination bug with buefy grid tables
hopefully this gets it all working right...ugh
---
tailbone/templates/grids/buefy.mako | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako
index 699161c8..ac814a8a 100644
--- a/tailbone/templates/grids/buefy.mako
+++ b/tailbone/templates/grids/buefy.mako
@@ -147,15 +147,12 @@
backend-sorting
@sort="onSort"
- ## % if grid.pageable:
- ## paginated
:paginated="paginated"
:per-page="perPage"
:current-page="currentPage"
backend-pagination
:total="total"
@page-change="onPageChange"
- ## % endif
## TODO: should let grid (or master view) decide how to set these?
icon-pack="fas"
@@ -295,7 +292,7 @@
`sortkey=${'$'}{this.sortField}`,
`sortdir=${'$'}{this.sortOrder}`,
`pagesize=${'$'}{this.perPage}`,
- `page=${'$'}{this.page}`
+ `page=${'$'}{this.currentPage}`
].join('&')
}
From 0798102ba56eb69156a6dd7b9e1fb359b9357627 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 22 Jul 2020 19:53:35 -0500
Subject: [PATCH 0018/1545] Tweak "coalesce" logic for merging field data
---
tailbone/views/master.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 163fe91e..daac610b 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -2128,7 +2128,9 @@ class MasterView(View):
def get_merge_resulting_data(self, remove, keep):
result = dict(keep)
for field in self.get_merge_coalesce_fields():
- if remove[field] and not keep[field]:
+ if remove[field] is not None and keep[field] is None:
+ result[field] = remove[field]
+ elif remove[field] and not keep[field]:
result[field] = remove[field]
for field in self.get_merge_additive_fields():
if isinstance(keep[field], (list, tuple)):
From d196044d117076058bb9f707fb22950361a67fd6 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 26 Jul 2020 14:02:28 -0500
Subject: [PATCH 0019/1545] Update changelog
---
CHANGES.rst | 18 ++++++++++++++++++
tailbone/_version.py | 2 +-
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index e33a7134..39f5a0d5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,24 @@
CHANGELOG
=========
+0.8.98 (2020-07-26)
+-------------------
+
+* Tweak field label for ``Product.item_id``.
+
+* Make field list explicit for Department views.
+
+* Make field list explicit for Store views.
+
+* Don't allow "execute results" for any batches by default.
+
+* Fix pagination sync issue with buefy grid tables.
+
+* Fix permissions wiget bug when creating new role.
+
+* Tweak "coalesce" logic for merging field data.
+
+
0.8.97 (2020-06-24)
-------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index 1137fc33..ed3946e6 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.97'
+__version__ = '0.8.98'
From e0ce7e8505c29fd9ac4a4cbe54e38147f17c58a2 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Tue, 28 Jul 2020 21:19:47 -0500
Subject: [PATCH 0020/1545] Add `self.cloning` convenience indicator for master
view
---
tailbone/views/master.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index daac610b..7f52d9c4 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -123,6 +123,7 @@ class MasterView(View):
editing = False
deleting = False
executing = False
+ cloning = False
has_pk_fields = False
has_image = False
has_thumbnail = False
@@ -1129,6 +1130,7 @@ class MasterView(View):
View for cloning an object's data into a new object.
"""
self.viewing = True
+ self.cloning = True
instance = self.get_instance()
form = self.make_form(instance)
self.configure_clone_form(form)
From cf8072e402cf983b5cd4bcfcb316dd4ea30d508b Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 29 Jul 2020 21:58:31 -0500
Subject: [PATCH 0021/1545] Use handler `do_delete()` method when deleting a
batch
even though it seems we have 2 calls to `session.delete(batch)` now, but things
are still working..fingers crossed
---
tailbone/views/batch/core.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py
index fff98730..66f0c382 100644
--- a/tailbone/views/batch/core.py
+++ b/tailbone/views/batch/core.py
@@ -746,7 +746,7 @@ class BatchMasterView(MasterView):
"""
Delete all data (files etc.) for the batch.
"""
- self.handler.delete(batch)
+ self.handler.do_delete(batch)
super(BatchMasterView, self).delete_instance(batch)
def get_fallback_templates(self, template, mobile=False):
From dfeb14e7a87673815f0e88d4160135d4d67c5b87 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Wed, 29 Jul 2020 21:59:49 -0500
Subject: [PATCH 0022/1545] 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 39f5a0d5..e68bb48c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,14 @@
CHANGELOG
=========
+0.8.99 (2020-07-29)
+-------------------
+
+* Add ``self.cloning`` convenience indicator for master view.
+
+* Use handler ``do_delete()`` method when deleting a batch.
+
+
0.8.98 (2020-07-26)
-------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index ed3946e6..4218b25a 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.98'
+__version__ = '0.8.99'
From 8ea379bbff29c570b701b98bbaec74cc99d34b64 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 30 Jul 2020 16:38:03 -0500
Subject: [PATCH 0023/1545] Add more customization hooks for making grid
actions in master view
---
tailbone/views/master.py | 45 +++++++++++++++++++++++++++++-----------
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 7f52d9c4..d4850ef4 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -1139,7 +1139,7 @@ class MasterView(View):
self.request.session.flash("{} has been cloned: {}".format(
self.get_model_title(), self.get_instance_title(instance)))
self.request.session.flash("(NOTE, you are now viewing the clone!)")
- return self.redirect(self.get_action_url('view', cloned))
+ return self.redirect_after_clone(cloned)
return self.render_to_response('clone', {
'instance': instance,
'instance_title': self.get_instance_title(instance),
@@ -1167,6 +1167,9 @@ class MasterView(View):
self.Session.flush()
return cloned
+ def redirect_after_clone(self, instance, mobile=False):
+ return self.redirect(self.get_action_url('view', instance, mobile=mobile))
+
def touch(self):
"""
View for "touching" an object so as to trigger datasync logic for it.
@@ -2522,13 +2525,16 @@ class MasterView(View):
Return a list of 'main' actions for the grid.
"""
actions = []
- use_buefy = self.get_use_buefy()
if self.viewable and self.has_perm('view'):
- url = self.get_view_index_url if self.use_index_links else None
- icon = 'eye' if use_buefy else 'zoomin'
- actions.append(self.make_action('view', icon=icon, url=url))
+ actions.append(self.make_grid_action_view())
return actions
+ def make_grid_action_view(self):
+ use_buefy = self.get_use_buefy()
+ url = self.get_view_index_url if self.use_index_links else None
+ icon = 'eye' if use_buefy else 'zoomin'
+ return self.make_action('view', icon=icon, url=url)
+
def get_view_index_url(self, row, i):
route = '{}.view_index'.format(self.get_route_prefix())
return '{}?index={}'.format(self.request.route_url(route), self.first_visible_grid_index + i - 1)
@@ -2538,27 +2544,42 @@ class MasterView(View):
Return a list of 'more' actions for the grid.
"""
actions = []
- use_buefy = self.get_use_buefy()
# Edit
if self.editable and self.has_perm('edit'):
- icon = 'edit' if use_buefy else 'pencil'
- actions.append(self.make_action('edit', icon=icon, url=self.default_edit_url))
+ actions.append(self.make_grid_action_edit())
# Delete
if self.deletable and self.has_perm('delete'):
- kwargs = {}
- if use_buefy and self.delete_confirm == 'simple':
- kwargs['click_handler'] = 'deleteObject'
- actions.append(self.make_action('delete', icon='trash', url=self.default_delete_url, **kwargs))
+ actions.append(self.make_grid_action_delete())
return actions
+ def make_grid_action_edit(self):
+ use_buefy = self.get_use_buefy()
+ icon = 'edit' if use_buefy else 'pencil'
+ return self.make_action('edit', icon=icon, url=self.default_edit_url)
+
+ def make_grid_action_clone(self):
+ return self.make_action('clone', icon='object-ungroup',
+ url=self.default_clone_url)
+
+ def make_grid_action_delete(self):
+ use_buefy = self.get_use_buefy()
+ kwargs = {}
+ if use_buefy and self.delete_confirm == 'simple':
+ kwargs['click_handler'] = 'deleteObject'
+ return self.make_action('delete', icon='trash', url=self.default_delete_url, **kwargs)
+
def default_edit_url(self, row, i=None):
if self.editable_instance(row):
return self.request.route_url('{}.edit'.format(self.get_route_prefix()),
**self.get_action_route_kwargs(row))
+ def default_clone_url(self, row, i=None):
+ return self.request.route_url('{}.clone'.format(self.get_route_prefix()),
+ **self.get_action_route_kwargs(row))
+
def default_delete_url(self, row, i=None):
if self.deletable_instance(row):
return self.request.route_url('{}.delete'.format(self.get_route_prefix()),
From 6bd049e0bbbf6d93e9d43787be97b399097fe19c Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 30 Jul 2020 16:39:44 -0500
Subject: [PATCH 0024/1545] 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 e68bb48c..6457e598 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,12 @@
CHANGELOG
=========
+0.8.100 (2020-07-30)
+--------------------
+
+* Add more customization hooks for making grid actions in master view.
+
+
0.8.99 (2020-07-29)
-------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index 4218b25a..0745e4ce 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.99'
+__version__ = '0.8.100'
From 9a2a6bbc9f7ae979c0cbbc9260923b70b1d93c28 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sat, 1 Aug 2020 22:18:54 -0500
Subject: [PATCH 0025/1545] Fix missing scrollbar when version diff table is
too wide for screen
at least, this seems to fix. not sure if/why we shouldn't apply this style
globally always, but playing it safe for now
---
tailbone/templates/master/view_version.mako | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/tailbone/templates/master/view_version.mako b/tailbone/templates/master/view_version.mako
index 71b51d39..13c87ae6 100644
--- a/tailbone/templates/master/view_version.mako
+++ b/tailbone/templates/master/view_version.mako
@@ -3,6 +3,17 @@
<%def name="title()">changes @ ver ${transaction.id}%def>
+<%def name="extra_styles()">
+ ${parent.extra_styles()}
+
+%def>
+
<%def name="page_content()">
## TODO: this was basically copied from Revel diff template..need to abstract
From 493785591cc6f6b5a037216c3e8004397370ad0d Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 2 Aug 2020 15:27:10 -0500
Subject: [PATCH 0026/1545] Add basic web views for "new customer order"
batches
---
tailbone/views/custorders/__init__.py | 5 +--
tailbone/views/custorders/batch.py | 41 +++++++++++++++++++++++++
tailbone/views/custorders/creating.py | 44 +++++++++++++++++++++++++++
3 files changed, 88 insertions(+), 2 deletions(-)
create mode 100644 tailbone/views/custorders/batch.py
create mode 100644 tailbone/views/custorders/creating.py
diff --git a/tailbone/views/custorders/__init__.py b/tailbone/views/custorders/__init__.py
index d2b4c5ed..78a3d3ab 100644
--- a/tailbone/views/custorders/__init__.py
+++ b/tailbone/views/custorders/__init__.py
@@ -1,8 +1,8 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2017 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -28,5 +28,6 @@ from __future__ import unicode_literals, absolute_import
def includeme(config):
+ config.include('tailbone.views.custorders.creating')
config.include('tailbone.views.custorders.orders')
config.include('tailbone.views.custorders.items')
diff --git a/tailbone/views/custorders/batch.py b/tailbone/views/custorders/batch.py
new file mode 100644
index 00000000..a49be977
--- /dev/null
+++ b/tailbone/views/custorders/batch.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2020 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 .
+#
+################################################################################
+"""
+Base class for customer order batch views
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model
+
+from tailbone.views.batch import BatchMasterView
+
+
+class CustomerOrderBatchView(BatchMasterView):
+ """
+ Master view base class, for customer order batches. The views for the
+ various mode/workflow batches will derive from this.
+ """
+ model_class = model.CustomerOrderBatch
+ model_row_class = model.CustomerOrderBatchRow
+ default_handler_spec = 'rattail.batch.custorder:CustomerOrderBatchHandler'
diff --git a/tailbone/views/custorders/creating.py b/tailbone/views/custorders/creating.py
new file mode 100644
index 00000000..29dc5b35
--- /dev/null
+++ b/tailbone/views/custorders/creating.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2020 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 .
+#
+################################################################################
+"""
+Views for 'creating' customer order batches
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from tailbone.views.custorders.batch import CustomerOrderBatchView
+
+
+class CreateCustomerOrderBatchView(CustomerOrderBatchView):
+ """
+ Master view for "creating customer order" batches.
+ """
+ route_prefix = 'new_custorders'
+ url_prefix = '/new-customer-orders'
+ model_title = "New Customer Order"
+ model_title_plural = "New Customer Orders"
+ creatable = False
+
+
+def includeme(config):
+ CreateCustomerOrderBatchView.defaults(config)
From c32f47ba9533fe4c27625629fe6b5f27a4377873 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 2 Aug 2020 19:13:40 -0500
Subject: [PATCH 0027/1545] Tweak the buefy autocomplete component a bit
to better support staying in sync w/ data on the caller/parent side
---
.../static/js/tailbone.buefy.autocomplete.js | 35 +++++++++++++++----
tailbone/templates/autocomplete.mako | 2 +-
2 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/tailbone/static/js/tailbone.buefy.autocomplete.js b/tailbone/static/js/tailbone.buefy.autocomplete.js
index 669b3c1f..fc64a073 100644
--- a/tailbone/static/js/tailbone.buefy.autocomplete.js
+++ b/tailbone/static/js/tailbone.buefy.autocomplete.js
@@ -22,23 +22,42 @@ const TailboneAutocomplete = {
data: [],
selected: selected,
isFetching: false,
- autocompleteValue: this.value,
}
},
+ watch: {
+ value(to, from) {
+ if (from && !to) {
+ this.clearSelection(false)
+ }
+ },
+ },
+
methods: {
- clearSelection() {
+ clearSelection(focus) {
+ if (focus === undefined) {
+ focus = true
+ }
this.selected = null
- this.autocompleteValue = null
- this.$nextTick(function() {
- this.$refs.autocomplete.focus()
- })
+ this.value = null
+ if (focus) {
+ this.$nextTick(function() {
+ this.$refs.autocomplete.focus()
+ })
+ }
// TODO: should emit event for caller logic (can they cancel?)
// $('#' + oid + '-textbox').trigger('autocompletevaluecleared');
},
+ getDisplayText() {
+ if (this.selected) {
+ return this.selected.label
+ }
+ return ""
+ },
+
// TODO: should we allow custom callback? or is event enough?
// function (oid) {
// $('#' + oid + '-textbox').on('autocompletevaluecleared', function() {
@@ -62,7 +81,9 @@ const TailboneAutocomplete = {
// }
itemSelected(value) {
- this.$emit('input', value)
+ if (this.selected || !value) {
+ this.$emit('input', value)
+ }
},
// TODO: buefy example uses `debounce()` here and perhaps we should too?
diff --git a/tailbone/templates/autocomplete.mako b/tailbone/templates/autocomplete.mako
index 7ec61f4c..c9de4507 100644
--- a/tailbone/templates/autocomplete.mako
+++ b/tailbone/templates/autocomplete.mako
@@ -65,7 +65,7 @@
Date: Sun, 2 Aug 2020 20:59:16 -0500
Subject: [PATCH 0028/1545] Add basic/unfinished "new customer order"
page/feature
so far creates the order batch, and can set some customer info
---
tailbone/templates/custorders/create.mako | 426 ++++++++++++++++++++++
tailbone/views/custorders/batch.py | 81 ++++
tailbone/views/custorders/creating.py | 5 +
tailbone/views/custorders/orders.py | 129 ++++++-
4 files changed, 639 insertions(+), 2 deletions(-)
create mode 100644 tailbone/templates/custorders/create.mako
diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako
new file mode 100644
index 00000000..24245b7a
--- /dev/null
+++ b/tailbone/templates/custorders/create.mako
@@ -0,0 +1,426 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/master/create.mako" />
+
+<%def name="extra_styles()">
+ ${parent.extra_styles()}
+ % if use_buefy:
+
+ % endif
+%def>
+
+<%def name="page_content()">
+
+ % if use_buefy:
+
+ % else:
+ Sorry, but this page is not supported by your current theme configuration.
+ % endif
+%def>
+
+<%def name="order_form_buttons()">
+
+
+ Submit this Order
+
+
+ Start Over Entirely
+
+
+ Cancel this Order
+
+
+%def>
+
+<%def name="render_this_page_template()">
+ ${parent.render_this_page_template()}
+
+
+%def>
+
+<%def name="make_this_page_component()">
+ ${parent.make_this_page_component()}
+
+%def>
+
+
+${parent.body()}
diff --git a/tailbone/views/custorders/batch.py b/tailbone/views/custorders/batch.py
index a49be977..c8b6280f 100644
--- a/tailbone/views/custorders/batch.py
+++ b/tailbone/views/custorders/batch.py
@@ -26,8 +26,13 @@ Base class for customer order batch views
from __future__ import unicode_literals, absolute_import
+import six
+
from rattail.db import model
+import colander
+
+from tailbone import forms
from tailbone.views.batch import BatchMasterView
@@ -39,3 +44,79 @@ class CustomerOrderBatchView(BatchMasterView):
model_class = model.CustomerOrderBatch
model_row_class = model.CustomerOrderBatchRow
default_handler_spec = 'rattail.batch.custorder:CustomerOrderBatchHandler'
+
+ grid_columns = [
+ 'id',
+ 'customer',
+ 'rows',
+ 'created',
+ 'created_by',
+ ]
+
+ form_fields = [
+ 'id',
+ 'customer',
+ 'person',
+ 'phone_number',
+ 'email_address',
+ 'created',
+ 'created_by',
+ 'rows',
+ 'status_code',
+ ]
+
+ def configure_grid(self, g):
+ super(CustomerOrderBatchView, self).configure_grid(g)
+
+ g.set_link('customer')
+ g.set_link('created')
+ g.set_link('created_by')
+
+ def configure_form(self, f):
+ super(CustomerOrderBatchView, self).configure_form(f)
+ order = f.model_instance
+ model = self.rattail_config.get_model()
+
+ # readonly fields
+ f.set_readonly('rows')
+ f.set_readonly('status_code')
+
+ # customer
+ if 'customer' in f.fields and self.editing:
+ f.replace('customer', 'customer_uuid')
+ f.set_node('customer_uuid', colander.String(), missing=colander.null)
+ customer_display = ""
+ if self.request.method == 'POST':
+ if self.request.POST.get('customer_uuid'):
+ customer = self.Session.query(model.Customer)\
+ .get(self.request.POST['customer_uuid'])
+ if customer:
+ customer_display = six.text_type(customer)
+ elif self.editing:
+ customer_display = six.text_type(order.customer or "")
+ customers_url = self.request.route_url('customers.autocomplete')
+ f.set_widget('customer_uuid', forms.widgets.JQueryAutocompleteWidget(
+ field_display=customer_display, service_url=customers_url))
+ f.set_label('customer_uuid', "Customer")
+ else:
+ f.set_renderer('customer', self.render_customer)
+
+ # person
+ if 'person' in f.fields and self.editing:
+ f.replace('person', 'person_uuid')
+ f.set_node('person_uuid', colander.String(), missing=colander.null)
+ 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 = six.text_type(person)
+ elif self.editing:
+ person_display = six.text_type(order.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_label('person_uuid', "Person")
+ else:
+ f.set_renderer('person', self.render_person)
diff --git a/tailbone/views/custorders/creating.py b/tailbone/views/custorders/creating.py
index 29dc5b35..c14448eb 100644
--- a/tailbone/views/custorders/creating.py
+++ b/tailbone/views/custorders/creating.py
@@ -22,6 +22,11 @@
################################################################################
"""
Views for 'creating' customer order batches
+
+Note that this provides only the "direct" or "raw" table views for these
+batches. This does *not* provide a way to create a new batch; you should see
+:meth:`tailbone.views.custorders.orders.CustomerOrdersView.create()` for that
+logic.
"""
from __future__ import unicode_literals, absolute_import
diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py
index dee21f15..9ffc06c8 100644
--- a/tailbone/views/custorders/orders.py
+++ b/tailbone/views/custorders/orders.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2018 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -43,7 +43,6 @@ class CustomerOrdersView(MasterView):
"""
model_class = model.CustomerOrder
route_prefix = 'custorders'
- creatable = False
editable = False
deletable = False
@@ -59,6 +58,8 @@ class CustomerOrdersView(MasterView):
'id',
'customer',
'person',
+ 'phone_number',
+ 'email_address',
'created',
'status_code',
]
@@ -115,6 +116,130 @@ class CustomerOrdersView(MasterView):
url = self.request.route_url('people.view', uuid=person.uuid)
return tags.link_to(text, url)
+ def create(self, form=None, template='create'):
+ """
+ View for creating a new customer order. Note that it does so by way of
+ maintaining a "new customer order" batch, until the user finally
+ submits the order, at which point the batch is converted to a proper
+ order.
+ """
+ batch = self.get_current_batch()
+
+ if self.request.method == 'POST':
+
+ # first we check for traditional form post
+ action = self.request.POST.get('action')
+ post_actions = [
+ 'start_over_entirely',
+ 'delete_batch',
+ ]
+ if action in post_actions:
+ return getattr(self, action)(batch)
+
+ # okay then, we'll assume newer JSON-style post params
+ data = dict(self.request.json_body)
+ action = data.get('action')
+ json_actions = [
+ 'get_customer_info',
+ 'set_customer_data',
+ 'submit_new_order',
+ ]
+ if action in json_actions:
+ result = getattr(self, action)(batch, data)
+ return self.json_response(result)
+
+ context = {'batch': batch}
+ return self.render_to_response(template, context)
+
+ def get_current_batch(self):
+ user = self.request.user
+ if not user:
+ raise RuntimeError("this feature requires a user to be logged in")
+
+ try:
+ # there should be at most *one* new batch per user
+ batch = self.Session.query(model.CustomerOrderBatch)\
+ .filter(model.CustomerOrderBatch.mode == self.enum.CUSTORDER_BATCH_MODE_CREATING)\
+ .filter(model.CustomerOrderBatch.created_by == user)\
+ .one()
+
+ except orm.exc.NoResultFound:
+ # no batch yet for this user, so make one
+ batch = model.CustomerOrderBatch()
+ batch.mode = self.enum.CUSTORDER_BATCH_MODE_CREATING
+ batch.created_by = user
+ self.Session.add(batch)
+ self.Session.flush()
+
+ return batch
+
+ def start_over_entirely(self, batch):
+ # just delete current batch outright
+ # TODO: should use self.handler.do_delete() instead?
+ self.Session.delete(batch)
+ self.Session.flush()
+
+ # send user back to normal "create" page; a new batch will be generated
+ # for them automatically
+ route_prefix = self.get_route_prefix()
+ url = self.request.route_url('{}.create'.format(route_prefix))
+ return self.redirect(url)
+
+ def delete_batch(self, batch):
+ # just delete current batch outright
+ # TODO: should use self.handler.do_delete() instead?
+ self.Session.delete(batch)
+ self.Session.flush()
+
+ # set flash msg just to be more obvious
+ self.request.session.flash("New customer order has been deleted.")
+
+ # send user back to customer orders page, w/ no new batch generated
+ route_prefix = self.get_route_prefix()
+ url = self.request.route_url(route_prefix)
+ return self.redirect(url)
+
+ def get_customer_info(self, batch, data):
+ uuid = data.get('uuid')
+ if not uuid:
+ return {'error': "Must specify a customer UUID"}
+
+ customer = self.Session.query(model.Customer).get(uuid)
+ if not customer:
+ return {'error': "Customer not found"}
+
+ return self.info_for_customer(batch, data, customer)
+
+ def info_for_customer(self, batch, data, customer):
+ phone = customer.first_phone()
+ email = customer.first_email()
+ return {
+ 'uuid': customer.uuid,
+ 'phone_number': phone.number if phone else None,
+ 'email_address': email.address if email else None,
+ }
+
+ def set_customer_data(self, batch, data):
+ if 'customer_uuid' in data:
+ batch.customer_uuid = data['customer_uuid']
+ if 'person_uuid' in data:
+ batch.person_uuid = data['person_uuid']
+ elif batch.customer_uuid:
+ self.Session.flush()
+ batch.person = batch.customer.first_person()
+ else: # no customer set
+ batch.person_uuid = None
+ if 'phone_number' in data:
+ batch.phone_number = data['phone_number']
+ if 'email_address' in data:
+ batch.email_address = data['email_address']
+ self.Session.flush()
+ return {'success': True}
+
+ def submit_new_order(self, batch, data):
+ # TODO
+ return {'success': True}
+
def includeme(config):
CustomerOrdersView.defaults(config)
From 7d158e58b5f6420ce5c9130ed9493fa754966384 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 6 Aug 2020 02:04:17 -0500
Subject: [PATCH 0029/1545] Add `protected_usernames()` config function
---
tailbone/config.py | 3 +++
tailbone/views/users.py | 16 ++++++++--------
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/tailbone/config.py b/tailbone/config.py
index 875bc25b..ebd8899e 100644
--- a/tailbone/config.py
+++ b/tailbone/config.py
@@ -56,3 +56,6 @@ class ConfigExtension(BaseExtension):
def legacy_mobile_enabled(config):
return config.getbool('tailbone', 'legacy_mobile.enabled',
default=True)
+
+def protected_usernames(config):
+ return config.getlist('tailbone', 'protected_usernames')
diff --git a/tailbone/views/users.py b/tailbone/views/users.py
index 79e2590c..93869d5d 100644
--- a/tailbone/views/users.py
+++ b/tailbone/views/users.py
@@ -42,6 +42,7 @@ from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer
+from tailbone.config import protected_usernames
class UsersView(PrincipalMasterView):
@@ -139,9 +140,9 @@ class UsersView(PrincipalMasterView):
user is "root". But if the given user is not protected, this simply
returns ``True``.
"""
- if self.user_is_protected(user):
- return self.request.is_root
- return True
+ if self.request.is_root:
+ return True
+ return not self.user_is_protected(user)
def deletable_instance(self, user):
"""
@@ -149,9 +150,9 @@ class UsersView(PrincipalMasterView):
user is "root". But if the given user is not protected, this simply
returns ``True``.
"""
- if self.user_is_protected(user):
- return self.request.is_root
- return True
+ if self.request.is_root:
+ return True
+ return not self.user_is_protected(user)
def user_is_protected(self, user):
"""
@@ -165,8 +166,7 @@ class UsersView(PrincipalMasterView):
"root", otherwise will return ``False``.
"""
if not hasattr(self, 'protected_usernames'):
- self.protected_usernames = self.rattail_config.getlist(
- 'tailbone', 'protected_usernames')
+ self.protected_usernames = protected_usernames(self.rattail_config)
if self.protected_usernames and user.username in self.protected_usernames:
return True
return False
From 437157440308179ef6171fc26155fad2876b5a72 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 9 Aug 2020 14:03:28 -0500
Subject: [PATCH 0030/1545] Add `model` to global template context, plus
`h.maxlen()`
sometimes it's nice to just add a `maxlength="100"` or whatever to an input tag
within some random template. that should "just be possible" with no extra
effort
---
tailbone/helpers.py | 1 +
tailbone/subscribers.py | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/tailbone/helpers.py b/tailbone/helpers.py
index 3a3d8365..46a30dec 100644
--- a/tailbone/helpers.py
+++ b/tailbone/helpers.py
@@ -32,6 +32,7 @@ from decimal import Decimal
from rattail.time import localtime, make_utc
from rattail.util import (pretty_quantity, pretty_hours, hours_as_decimal,
OrderedDict)
+from rattail.db.util import maxlen
from webhelpers2.html import *
from webhelpers2.html.tags import *
diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py
index 90930e60..af88f7a7 100644
--- a/tailbone/subscribers.py
+++ b/tailbone/subscribers.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2019 Lance Edgar
+# Copyright © 2010-2020 Lance Edgar
#
# This file is part of Rattail.
#
@@ -99,6 +99,7 @@ def before_render(event):
renderer_globals['url'] = request.route_url
renderer_globals['rattail'] = rattail
renderer_globals['tailbone'] = tailbone
+ renderer_globals['model'] = request.rattail_config.get_model()
renderer_globals['enum'] = request.rattail_config.get_enum()
renderer_globals['six'] = six
renderer_globals['json'] = json
From 163134326aaab45620a71d72795ba33d5b35fec9 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 9 Aug 2020 14:32:16 -0500
Subject: [PATCH 0031/1545] Coalesce on `User.active` when merging
---
tailbone/views/users.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tailbone/views/users.py b/tailbone/views/users.py
index 93869d5d..078e99ca 100644
--- a/tailbone/views/users.py
+++ b/tailbone/views/users.py
@@ -86,6 +86,7 @@ class UsersView(PrincipalMasterView):
merge_coalesce_fields = [
'person_uuid',
'person_name',
+ 'active',
]
merge_fields = merge_additive_fields + [
'uuid',
@@ -93,7 +94,6 @@ class UsersView(PrincipalMasterView):
'person_uuid',
'person_name',
'role_count',
- 'active',
]
def query(self, session):
From ca31af196f6b456baf01914f48f687ec0d9415eb Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 9 Aug 2020 14:39:31 -0500
Subject: [PATCH 0032/1545] Expose user reference(s) for employees
---
tailbone/views/employees.py | 23 +++++++++++++++++++++--
tailbone/views/master.py | 12 ++++++++++++
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/tailbone/views/employees.py b/tailbone/views/employees.py
index 488e1f65..dac93e67 100644
--- a/tailbone/views/employees.py
+++ b/tailbone/views/employees.py
@@ -60,6 +60,7 @@ class EmployeesView(MasterView):
'phone',
'email',
'status',
+ 'username',
]
form_fields = [
@@ -73,6 +74,7 @@ class EmployeesView(MasterView):
'full_time',
'full_time_start',
'id',
+ 'users',
'stores',
'departments',
]
@@ -90,15 +92,25 @@ class EmployeesView(MasterView):
factory=grids.filters.AlchemyPhoneNumberFilter)
g.set_sorter('phone', model.EmployeePhoneNumber.number)
+ # email
g.joiners['email'] = lambda q: q.outerjoin(model.EmployeeEmailAddress, sa.and_(
model.EmployeeEmailAddress.parent_uuid == model.Employee.uuid,
model.EmployeeEmailAddress.preference == 1))
+ g.filters['email'] = g.make_filter('email', model.EmployeeEmailAddress.address,
+ label="Email Address")
+ # first/last name
g.filters['first_name'] = g.make_filter('first_name', model.Person.first_name)
g.filters['last_name'] = g.make_filter('last_name', model.Person.last_name)
- g.filters['email'] = g.make_filter('email', model.EmployeeEmailAddress.address,
- label="Email Address")
+ # username
+ if self.request.has_perm('users.view'):
+ g.set_joiner('username', lambda q: q.outerjoin(model.User))
+ g.set_filter('username', model.User.username)
+ g.set_sorter('username', model.User.username)
+ g.set_renderer('username', self.grid_render_username)
+ else:
+ g.hide_column('username')
# id
if self.request.has_perm('{}.edit'.format(route_prefix)):
@@ -142,6 +154,12 @@ class EmployeesView(MasterView):
q = q.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)
return q
+ def grid_render_username(self, employee, field):
+ person = employee.person if employee else None
+ if not person:
+ return ""
+ return ", ".join([u.username for u in person.users])
+
def grid_extra_class(self, employee, i):
if employee.status == self.enum.EMPLOYEE_STATUS_FORMER:
return 'warning'
@@ -161,6 +179,7 @@ class EmployeesView(MasterView):
employee = f.model_instance
f.set_renderer('person', self.render_person)
+ f.set_renderer('users', self.render_users)
f.set_renderer('stores', self.render_stores)
f.set_label('stores', "Stores") # TODO: should not be necessary
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index d4850ef4..10bd5c26 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -947,6 +947,18 @@ class MasterView(View):
url = self.request.route_url('users.view', uuid=user.uuid)
return tags.link_to(text, url)
+ def render_users(self, obj, field):
+ users = obj.users
+ if not users:
+ return ""
+
+ items = []
+ for user in users:
+ text = user.username
+ url = self.request.route_url('users.view', uuid=user.uuid)
+ items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
+ return HTML.tag('ul', c=items)
+
def render_customer(self, obj, field):
customer = getattr(obj, field)
if not customer:
From b4ea1489a7075b562767f41fb1faa3ef4ec999cf Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 9 Aug 2020 15:06:41 -0500
Subject: [PATCH 0033/1545] Update changelog
---
CHANGES.rst | 20 ++++++++++++++++++++
tailbone/_version.py | 2 +-
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 6457e598..1516feb7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,26 @@
CHANGELOG
=========
+0.8.101 (2020-08-09)
+--------------------
+
+* Fix missing scrollbar when version diff table is too wide for screen.
+
+* Add basic web views for "new customer order" batches.
+
+* Tweak the buefy autocomplete component a bit.
+
+* Add basic/unfinished "new customer order" page/feature.
+
+* Add ``protected_usernames()`` config function.
+
+* Add ``model`` to global template context, plus ``h.maxlen()``.
+
+* Coalesce on ``User.active`` when merging.
+
+* Expose user reference(s) for employees.
+
+
0.8.100 (2020-07-30)
--------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index 0745e4ce..c5157649 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.100'
+__version__ = '0.8.101'
From d0e7f7dda2039cc2fc2cef0c022663cb03e62b96 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 9 Aug 2020 15:50:25 -0500
Subject: [PATCH 0034/1545] Improve rendering of `true_margin` column for
pricing batch row grid
---
tailbone/views/batch/pricing.py | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/tailbone/views/batch/pricing.py b/tailbone/views/batch/pricing.py
index 34586ea1..88063d00 100644
--- a/tailbone/views/batch/pricing.py
+++ b/tailbone/views/batch/pricing.py
@@ -26,6 +26,8 @@ Views for pricing batches
from __future__ import unicode_literals, absolute_import
+import six
+
from rattail.db import model
from rattail.time import localtime
@@ -203,12 +205,26 @@ class PricingBatchView(BatchMasterView):
g.set_renderer('current_price', self.render_current_price)
+ g.set_renderer('true_margin', self.render_true_margin)
+
def render_vendor_id(self, row, field):
vendor_id = row.vendor.id if row.vendor else None
if not vendor_id:
return ""
return vendor_id
+ def render_true_margin(self, row, field):
+ margin = row.true_margin
+ if margin:
+ margin = six.text_type(margin)
+ else:
+ margin = HTML.literal(' ')
+ if row.old_true_margin is not None:
+ title = "WAS: {}".format(row.old_true_margin)
+ else:
+ title = "WAS: NULL"
+ return HTML.tag('span', title=title, c=[margin])
+
def row_grid_extra_class(self, row, i):
extra_class = None
From dca890f16900a183ca561b751d1845b4ee3c6e05 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 10 Aug 2020 19:37:29 -0500
Subject: [PATCH 0035/1545] 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 1516feb7..d238ac5a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,12 @@
CHANGELOG
=========
+0.8.102 (2020-08-10)
+--------------------
+
+* Improve rendering of ``true_margin`` column for pricing batch row grid.
+
+
0.8.101 (2020-08-09)
--------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index c5157649..9acd832b 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.101'
+__version__ = '0.8.102'
From aac0e7d35c9011b3602d2525756b96a04313f9fb Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Tue, 11 Aug 2020 18:28:03 -0500
Subject: [PATCH 0036/1545] Tweak config methods for customer master view
---
tailbone/views/customers.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py
index cdb44429..a5cf963a 100644
--- a/tailbone/views/customers.py
+++ b/tailbone/views/customers.py
@@ -385,14 +385,17 @@ class CustomersView(MasterView):
@classmethod
def defaults(cls, config):
+ cls._defaults(config)
+ cls._customer_defaults(config)
+
+ @classmethod
+ def _customer_defaults(cls, config):
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key()
model_title = cls.get_model_title()
- cls._defaults(config)
-
# detach person
if cls.people_detachable:
config.add_tailbone_permission(permission_prefix, '{}.detach_person'.format(permission_prefix),
From 7924502b657a5de7076e5a23307241e830bb0fc0 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Thu, 13 Aug 2020 12:55:17 -0500
Subject: [PATCH 0037/1545] 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 d238ac5a..728a3483 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,12 @@
CHANGELOG
=========
+0.8.103 (2020-08-13)
+--------------------
+
+* Tweak config methods for customer master view.
+
+
0.8.102 (2020-08-10)
--------------------
diff --git a/tailbone/_version.py b/tailbone/_version.py
index 9acd832b..ce6cf316 100644
--- a/tailbone/_version.py
+++ b/tailbone/_version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8; -*-
-__version__ = '0.8.102'
+__version__ = '0.8.103'
From a038f2a98dcb7f2f2263eddbe3d6cf0ee2e1d7b5 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Sun, 16 Aug 2020 16:57:06 -0500
Subject: [PATCH 0038/1545] Make "download row results" a bit more generic
to handle non-native table/rows, w/ non-uuid key
---
tailbone/templates/master/view.mako | 4 ++--
tailbone/views/master.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/tailbone/templates/master/view.mako b/tailbone/templates/master/view.mako
index d07e1cc9..94454bd9 100644
--- a/tailbone/templates/master/view.mako
+++ b/tailbone/templates/master/view.mako
@@ -72,8 +72,8 @@
% if master.has_rows and master.rows_downloadable_csv and request.has_perm('{}.row_results_csv'.format(permission_prefix)):
${h.link_to("Download row results as CSV", url('{}.row_results_csv'.format(route_prefix), uuid=instance.uuid))}
% endif
- % if master.has_rows and master.rows_downloadable_xlsx and request.has_perm('{}.row_results_xlsx'.format(permission_prefix)):
- ${h.link_to("Download row results as XLSX", url('{}.row_results_xlsx'.format(route_prefix), uuid=instance.uuid))}
+ % if master.has_rows and master.rows_downloadable_xlsx and master.has_perm('row_results_xlsx'):
+ ${h.link_to("Download row results as XLSX", master.get_action_url('row_results_xlsx', instance))}
% endif
%def>
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 10bd5c26..827e5500 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -3868,7 +3868,7 @@ class MasterView(View):
if cls.has_rows and cls.rows_downloadable_xlsx:
config.add_tailbone_permission(permission_prefix, '{}.row_results_xlsx'.format(permission_prefix),
"Download {} results as XLSX".format(row_model_title))
- config.add_route('{}.row_results_xlsx'.format(route_prefix), '{}/{{uuid}}/rows-xlsx'.format(url_prefix))
+ config.add_route('{}.row_results_xlsx'.format(route_prefix), '{}/rows-xlsx'.format(instance_url_prefix))
config.add_view(cls, attr='row_results_xlsx', route_name='{}.row_results_xlsx'.format(route_prefix),
permission='{}.row_results_xlsx'.format(permission_prefix))
From b5028ab2d0e8a240657b6b274befe9e8efc64e30 Mon Sep 17 00:00:00 2001
From: Lance Edgar
Date: Mon, 17 Aug 2020 21:38:12 -0500
Subject: [PATCH 0039/1545] Add pagination to price, cost history grids for
product view
---
tailbone/grids/core.py | 3 +++
tailbone/templates/grids/b-table.mako | 4 ++++
tailbone/templates/products/view.mako | 8 ++++----
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py
index 60934879..d475370c 100644
--- a/tailbone/grids/core.py
+++ b/tailbone/grids/core.py
@@ -985,6 +985,9 @@ class Grid(object):
context['empty_labels'] = empty_labels
if 'grid_columns' not in context:
context['grid_columns'] = self.get_buefy_columns()
+ context.setdefault('paginated', False)
+ if context['paginated']:
+ context.setdefault('per_page', 20)
# locate the 'view' action
# TODO: this should be easier, and/or moved elsewhere?
diff --git a/tailbone/templates/grids/b-table.mako b/tailbone/templates/grids/b-table.mako
index 42e82273..8608b456 100644
--- a/tailbone/templates/grids/b-table.mako
+++ b/tailbone/templates/grids/b-table.mako
@@ -5,6 +5,10 @@
striped
hoverable
narrowed
+ % if paginated:
+ paginated
+ per-page="${per_page}"
+ % endif
% if vshow is not Undefined and vshow:
v-show="${vshow}"
% endif
diff --git a/tailbone/templates/products/view.mako b/tailbone/templates/products/view.mako
index c8f2a5ec..23b6d2ca 100644
--- a/tailbone/templates/products/view.mako
+++ b/tailbone/templates/products/view.mako
@@ -380,7 +380,7 @@
- ${regular_price_history_grid.render_buefy_table_element(data_prop='regularPriceHistoryData', loading='regularPriceHistoryLoading')|n}
+ ${regular_price_history_grid.render_buefy_table_element(data_prop='regularPriceHistoryData', loading='regularPriceHistoryLoading', paginated=True, per_page=10)|n}