Compare commits

..

No commits in common. "master" and "v0.22.2" have entirely different histories.

10 changed files with 48 additions and 93 deletions

View file

@ -5,41 +5,6 @@ All notable changes to Tailbone will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.22.7 (2025-02-19)
### Fix
- stop using old config for logo image url on login page
- fix warning msg for deprecated Grid param
## v0.22.6 (2025-02-01)
### Fix
- register vue3 form component for products -> make batch
## v0.22.5 (2024-12-16)
### Fix
- whoops this is latest rattail
- require newer rattail lib
- require newer wuttaweb
- let caller request safe HTML literal for rendered grid table
## v0.22.4 (2024-11-22)
### Fix
- avoid error in product search for duplicated key
- use vmodel for confirm password widget input
## v0.22.3 (2024-11-19)
### Fix
- avoid error for trainwreck query when not a customer
## v0.22.2 (2024-11-18) ## v0.22.2 (2024-11-18)
### Fix ### Fix

View file

@ -27,10 +27,10 @@ templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
intersphinx_mapping = { intersphinx_mapping = {
'rattail': ('https://docs.wuttaproject.org/rattail/', None), 'rattail': ('https://rattailproject.org/docs/rattail/', None),
'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None), 'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
'wuttaweb': ('https://docs.wuttaproject.org/wuttaweb/', None), 'wuttaweb': ('https://rattailproject.org/docs/wuttaweb/', None),
'wuttjamaican': ('https://docs.wuttaproject.org/wuttjamaican/', None), 'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
} }
# allow todo entries to show up # allow todo entries to show up

View file

@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "Tailbone" name = "Tailbone"
version = "0.22.7" version = "0.22.2"
description = "Backoffice Web Application for Rattail" description = "Backoffice Web Application for Rattail"
readme = "README.md" readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
@ -53,13 +53,13 @@ dependencies = [
"pyramid_mako", "pyramid_mako",
"pyramid_retry", "pyramid_retry",
"pyramid_tm", "pyramid_tm",
"rattail[db,bouncer]>=0.20.1", "rattail[db,bouncer]>=0.18.5",
"sa-filters", "sa-filters",
"simplejson", "simplejson",
"transaction", "transaction",
"waitress", "waitress",
"WebHelpers2", "WebHelpers2",
"WuttaWeb>=0.21.0", "WuttaWeb>=0.14.0",
"zope.sqlalchemy>=1.5", "zope.sqlalchemy>=1.5",
] ]

View file

@ -62,17 +62,6 @@ def make_rattail_config(settings):
# nb. this is for compaibility with wuttaweb # nb. this is for compaibility with wuttaweb
settings['wutta_config'] = rattail_config settings['wutta_config'] = rattail_config
# must import all sqlalchemy models before things get rolling,
# otherwise can have errors about continuum TransactionMeta class
# not yet mapped, when relevant pages are first requested...
# cf. https://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/database/sqlalchemy.html#importing-all-sqlalchemy-models
# hat tip to https://stackoverflow.com/a/59241485
if getattr(rattail_config, 'tempmon_engine', None):
from rattail_tempmon.db import model as tempmon_model, Session as TempmonSession
tempmon_session = TempmonSession()
tempmon_session.query(tempmon_model.Appliance).first()
tempmon_session.close()
# configure database sessions # configure database sessions
if hasattr(rattail_config, 'appdb_engine'): if hasattr(rattail_config, 'appdb_engine'):
tailbone.db.Session.configure(bind=rattail_config.appdb_engine) tailbone.db.Session.configure(bind=rattail_config.appdb_engine)

View file

@ -235,7 +235,7 @@ class Grid(WuttaGrid):
if 'pageable' in kwargs: if 'pageable' in kwargs:
warnings.warn("pageable param is deprecated for Grid(); " warnings.warn("pageable param is deprecated for Grid(); "
"please use paginated param instead", "please use vue_tagname param instead",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
kwargs.setdefault('paginated', kwargs.pop('pageable')) kwargs.setdefault('paginated', kwargs.pop('pageable'))
@ -1223,7 +1223,6 @@ class Grid(WuttaGrid):
def render_table_element(self, template='/grids/b-table.mako', def render_table_element(self, template='/grids/b-table.mako',
data_prop='gridData', empty_labels=False, data_prop='gridData', empty_labels=False,
literal=False,
**kwargs): **kwargs):
""" """
This is intended for ad-hoc "small" grids with static data. Renders This is intended for ad-hoc "small" grids with static data. Renders
@ -1240,10 +1239,7 @@ class Grid(WuttaGrid):
if context['paginated']: if context['paginated']:
context.setdefault('per_page', 20) context.setdefault('per_page', 20)
context['view_click_handler'] = self.get_view_click_handler() context['view_click_handler'] = self.get_view_click_handler()
result = render(template, context) return render(template, context)
if literal:
result = HTML.literal(result)
return result
def get_view_click_handler(self): def get_view_click_handler(self):
""" """ """ """

View file

@ -1,7 +1,6 @@
<div i18n:domain="deform" tal:omit-tag="" <div i18n:domain="deform" tal:omit-tag=""
tal:define="oid oid|field.oid; tal:define="oid oid|field.oid;
name name|field.name; name name|field.name;
vmodel vmodel|'field_model_' + name;
css_class css_class|field.widget.css_class; css_class css_class|field.widget.css_class;
style style|field.widget.style;"> style style|field.widget.style;">
@ -9,7 +8,7 @@
${field.start_mapping()} ${field.start_mapping()}
<b-input type="password" <b-input type="password"
name="${name}" name="${name}"
v-model="${vmodel}" value="${field.widget.redisplay and cstruct or ''}"
tal:attributes="class string: form-control ${css_class or ''}; tal:attributes="class string: form-control ${css_class or ''};
style style; style style;
attributes|field.widget.attributes|{};" attributes|field.widget.attributes|{};"
@ -19,6 +18,7 @@
</b-input> </b-input>
<b-input type="password" <b-input type="password"
name="${name}-confirm" name="${name}-confirm"
value="${field.widget.redisplay and confirm or ''}"
tal:attributes="class string: form-control ${css_class or ''}; tal:attributes="class string: form-control ${css_class or ''};
style style; style style;
confirm_attributes|field.widget.confirm_attributes|{};" confirm_attributes|field.widget.confirm_attributes|{};"

View file

@ -55,20 +55,19 @@
</%def> </%def>
<%def name="render_form_template()"> <%def name="render_form_template()">
<script type="text/x-template" id="${form.vue_tagname}-template"> <script type="text/x-template" id="${form.component}-template">
${self.render_form_innards()} ${self.render_form_innards()}
</script> </script>
</%def> </%def>
<%def name="modify_vue_vars()"> <%def name="modify_vue_vars()">
${parent.modify_vue_vars()} ${parent.modify_vue_vars()}
<% request.register_component(form.vue_tagname, form.vue_component) %>
<script> <script>
## TODO: ugh, an awful lot of duplicated code here (from /forms/deform.mako) ## TODO: ugh, an awful lot of duplicated code here (from /forms/deform.mako)
let ${form.vue_component} = { let ${form.vue_component} = {
template: '#${form.vue_tagname}-template', template: '#${form.component}-template',
methods: { methods: {
## TODO: deprecate / remove the latter option here ## TODO: deprecate / remove the latter option here

View file

@ -44,6 +44,28 @@ class UserLogin(colander.MappingSchema):
widget=dfwidget.PasswordWidget()) widget=dfwidget.PasswordWidget())
@colander.deferred
def current_password_correct(node, kw):
request = kw['request']
app = request.rattail_config.get_app()
auth = app.get_auth_handler()
user = kw['user']
def validate(node, value):
if not auth.authenticate_user(Session(), user.username, value):
raise colander.Invalid(node, "The password is incorrect")
return validate
class ChangePassword(colander.MappingSchema):
current_password = colander.SchemaNode(colander.String(),
widget=dfwidget.PasswordWidget(),
validator=current_password_correct)
new_password = colander.SchemaNode(colander.String(),
widget=dfwidget.CheckedPasswordWidget())
class AuthenticationView(View): class AuthenticationView(View):
def forbidden(self): def forbidden(self):
@ -94,6 +116,10 @@ class AuthenticationView(View):
else: else:
self.request.session.flash("Invalid username or password", 'error') self.request.session.flash("Invalid username or password", 'error')
image_url = self.rattail_config.get(
'tailbone', 'main_image_url',
default=self.request.static_url('tailbone:static/img/home_logo.png'))
# nb. hacky..but necessary, to add the refs, for autofocus # nb. hacky..but necessary, to add the refs, for autofocus
# (also add key handler, so ENTER acts like TAB) # (also add key handler, so ENTER acts like TAB)
dform = form.make_deform_form() dform = form.make_deform_form()
@ -106,6 +132,7 @@ class AuthenticationView(View):
return { return {
'form': form, 'form': form,
'referrer': referrer, 'referrer': referrer,
'image_url': image_url,
'index_title': app.get_node_title(), 'index_title': app.get_node_title(),
'help_url': global_help_url(self.rattail_config), 'help_url': global_help_url(self.rattail_config),
} }
@ -154,23 +181,7 @@ class AuthenticationView(View):
self.request.user)) self.request.user))
return self.redirect(self.request.get_referrer()) return self.redirect(self.request.get_referrer())
def check_user_password(node, value): schema = ChangePassword().bind(user=self.request.user, request=self.request)
auth = self.app.get_auth_handler()
user = self.request.user
if not auth.check_user_password(user, value):
node.raise_invalid("The password is incorrect")
schema = colander.Schema()
schema.add(colander.SchemaNode(colander.String(),
name='current_password',
widget=dfwidget.PasswordWidget(),
validator=check_user_password))
schema.add(colander.SchemaNode(colander.String(),
name='new_password',
widget=dfwidget.CheckedPasswordWidget()))
form = forms.Form(schema=schema, request=self.request) form = forms.Form(schema=schema, request=self.request)
if form.validate(): if form.validate():
auth = self.app.get_auth_handler() auth = self.app.get_auth_handler()

View file

@ -564,19 +564,15 @@ class PersonView(MasterView):
Method which must return the base query for the profile's POS Method which must return the base query for the profile's POS
Transactions grid data. Transactions grid data.
""" """
customer = self.app.get_customer(person) app = self.get_rattail_app()
customer = app.get_customer(person)
if customer: key_field = app.get_customer_key_field()
key_field = self.app.get_customer_key_field() customer_key = getattr(customer, key_field)
customer_key = getattr(customer, key_field) if customer_key is not None:
if customer_key is not None: customer_key = str(customer_key)
customer_key = str(customer_key)
else:
# nb. this should *not* match anything, so query returns
# no results..
customer_key = person.uuid
trainwreck = self.app.get_trainwreck_handler() trainwreck = app.get_trainwreck_handler()
model = trainwreck.get_model() model = trainwreck.get_model()
query = TrainwreckSession.query(model.Transaction)\ query = TrainwreckSession.query(model.Transaction)\
.filter(model.Transaction.customer_id == customer_key) .filter(model.Transaction.customer_id == customer_key)

View file

@ -1857,8 +1857,7 @@ class ProductView(MasterView):
lookup_fields.append('alt_code') lookup_fields.append('alt_code')
if lookup_fields: if lookup_fields:
product = self.products_handler.locate_product_for_entry( product = self.products_handler.locate_product_for_entry(
session, term, lookup_fields=lookup_fields, session, term, lookup_fields=lookup_fields)
first_if_multiple=True)
if product: if product:
final_results.append(self.search_normalize_result(product)) final_results.append(self.search_normalize_result(product))