Provide a way to show enum display text for some version diff fields

master view must explicitly declare which enums for which fields
This commit is contained in:
Lance Edgar 2023-11-30 18:23:47 -06:00
parent 2a9d5f74ce
commit 35131c8732
7 changed files with 172 additions and 26 deletions

6
docs/api/diffs.rst Normal file
View file

@ -0,0 +1,6 @@
``tailbone.diffs``
==================
.. automodule:: tailbone.diffs
:members:

View file

@ -81,6 +81,12 @@ override when defining your subclass.
override this for certain views, if so that should be done within override this for certain views, if so that should be done within
:meth:`get_help_url()`. :meth:`get_help_url()`.
.. attribute:: MasterView.version_diff_factory
Optional factory to use for version diff objects. By default
this is *not set* but a subclass is free to set it. See also
:meth:`get_version_diff_factory()`.
Methods to Override Methods to Override
------------------- -------------------
@ -100,6 +106,14 @@ subclass.
.. automethod:: MasterView.get_model_key .. automethod:: MasterView.get_model_key
.. automethod:: MasterView.get_version_diff_enums
.. automethod:: MasterView.get_version_diff_factory
.. automethod:: MasterView.make_version_diff
.. automethod:: MasterView.title_for_version
Support Methods Support Methods
--------------- ---------------

View file

@ -0,0 +1,6 @@
``tailbone.views.members``
==========================
.. automodule:: tailbone.views.members
:members:

View file

@ -44,6 +44,7 @@ Package API:
api/api/batch/core api/api/batch/core
api/api/batch/ordering api/api/batch/ordering
api/diffs
api/forms api/forms
api/grids api/grids
api/grids.core api/grids.core
@ -53,6 +54,7 @@ Package API:
api/views/batch.vendorcatalog api/views/batch.vendorcatalog
api/views/core api/views/core
api/views/master api/views/master
api/views/members
api/views/purchasing.batch api/views/purchasing.batch
api/views/purchasing.ordering api/views/purchasing.ordering

View file

@ -34,35 +34,38 @@ from webhelpers2.html import HTML
class Diff(object): class Diff(object):
""" """
Core diff class. In sore need of documentation. Core diff class. In sore need of documentation.
You must provide the old and new data sets, and the set of
relevant fields as well, if they cannot be easily introspected.
:param old_data: Dict of "old" data values.
:param new_data: Dict of "old" data values.
:param fields: Sequence of relevant field names. Note that
both data dicts are expected to have keys which match these
field names. If you do not specify the fields then they
will (hopefully) be introspected from the old or new data
sets; however this will not work if they are both empty.
:param monospace: If true, this flag will cause the value
columns to be rendered in monospace font. This is assumed
to be helpful when comparing "raw" data values which are
shown as e.g. ``repr(val)``.
:param enums: Optional dict of enums for use when displaying field
values. If specified, keys should be field names and values
should be enum dicts.
""" """
def __init__(self, old_data, new_data, columns=None, fields=None, def __init__(self, old_data, new_data, columns=None, fields=None, enums=None,
render_field=None, render_value=None, nature='dirty', render_field=None, render_value=None, nature='dirty',
monospace=False, extra_row_attrs=None): monospace=False, extra_row_attrs=None):
"""
Constructor. You must provide the old and new data sets, and
the set of relevant fields as well, if they cannot be easily
introspected.
:param old_data: Dict of "old" data values.
:param new_data: Dict of "old" data values.
:param fields: Sequence of relevant field names. Note that
both data dicts are expected to have keys which match these
field names. If you do not specify the fields then they
will (hopefully) be introspected from the old or new data
sets; however this will not work if they are both empty.
:param monospace: If true, this flag will cause the value
columns to be rendered in monospace font. This is assumed
to be helpful when comparing "raw" data values which are
shown as e.g. ``repr(val)``.
"""
self.old_data = old_data self.old_data = old_data
self.new_data = new_data self.new_data = new_data
self.columns = columns or ["field name", "old value", "new value"] self.columns = columns or ["field name", "old value", "new value"]
self.fields = fields or self.make_fields() self.fields = fields or self.make_fields()
self.enums = enums or {}
self._render_field = render_field or self.render_field_default self._render_field = render_field or self.render_field_default
self.render_value = render_value or self.render_value_default self.render_value = render_value or self.render_value_default
self.nature = nature self.nature = nature
@ -92,7 +95,7 @@ class Diff(object):
for the given field. May be an empty string, or a snippet of HTML for the given field. May be an empty string, or a snippet of HTML
attribute syntax, e.g.: attribute syntax, e.g.:
.. code-highlight:: none .. code-block:: none
class="diff" foo="bar" class="diff" foo="bar"
@ -132,7 +135,21 @@ class Diff(object):
class VersionDiff(Diff): class VersionDiff(Diff):
""" """
Special diff class, for use with version history views Special diff class, for use with version history views. Note that
while based on :class:`Diff`, this class uses a different
signature for the constructor.
:param version: Reference to a Continuum version record (object).
:param \*args: Typical usage will not require positional args
beyond the ``version`` param, in which case ``old_data`` and
``new_data`` params will be auto-determined based on the
``version``. But if you specify positional args then nothing
automatic is done, they are passed as-is to the parent
:class:`Diff` constructor.
:param \*\*kwargs: Remaining kwargs are passed as-is to the
:class:`Diff` constructor.
""" """
def __init__(self, version, *args, **kwargs): def __init__(self, version, *args, **kwargs):
@ -176,9 +193,40 @@ class VersionDiff(Diff):
if field not in unwanted] if field not in unwanted]
def render_version_value(self, field, value, version): def render_version_value(self, field, value, version):
"""
Render the cell value text for the given version/field info.
Note that this method is used to render both sides of the diff
(before and after values).
:param field: Name of the field, as string.
:param value: Raw value for the field, as obtained from ``version``.
:param version: Reference to the Continuum version object.
:returns: Rendered text as string, or ``None``.
"""
text = HTML.tag('span', c=[repr(value)], text = HTML.tag('span', c=[repr(value)],
style='font-family: monospace;') style='font-family: monospace;')
# assume the enum display is all we need, if enum exists for the field
if field in self.enums:
# but skip the enum display if None
display = self.enums[field].get(value)
if display is None and value is None:
return text
# otherwise show enum display to the right of raw value
display = self.enums[field].get(value, str(value))
return HTML.tag('span', c=[
text,
HTML.tag('span', c=[display],
style='margin-left: 2rem; font-style: italic; font-weight: bold;'),
])
# next we look for a relationship and may render the foreign object
for prop in self.mapper.relationships: for prop in self.mapper.relationships:
if prop.uselist: if prop.uselist:
continue continue

View file

@ -597,7 +597,6 @@ class MasterView(View):
return defaults return defaults
def configure_row_grid(self, grid): def configure_row_grid(self, grid):
# super(MasterView, self).configure_row_grid(grid)
self.set_row_labels(grid) self.set_row_labels(grid)
self.configure_column_customer_key(grid) self.configure_column_customer_key(grid)
@ -1528,6 +1527,15 @@ class MasterView(View):
}) })
def title_for_version(self, version): def title_for_version(self, version):
"""
Must return the title text for the given version. By default
this will be the :term:`rattail:model title` for the version's
data class.
:param version: Reference to a Continuum version object.
:returns: Title text for the version, as string.
"""
cls = continuum.parent_class(version.__class__) cls = continuum.parent_class(version.__class__)
return cls.get_model_title() return cls.get_model_title()
@ -4962,13 +4970,52 @@ class MasterView(View):
return diffs.Diff(old_data, new_data, **kwargs) return diffs.Diff(old_data, new_data, **kwargs)
def get_version_diff_factory(self, **kwargs): def get_version_diff_factory(self, **kwargs):
"""
Must return the factory to be used when creating version diff
objects.
By default this returns the
:class:`tailbone.diffs.VersionDiff` class, unless
:attr:`version_diff_factory` is set, in which case that is
returned as-is.
:returns: A factory which can produce
:class:`~tailbone.diffs.VersionDiff` objects.
"""
if hasattr(self, 'version_diff_factory'): if hasattr(self, 'version_diff_factory'):
return self.version_diff_factory return self.version_diff_factory
return diffs.VersionDiff return diffs.VersionDiff
def get_version_diff_enums(self, version):
"""
This can optionally return a dict of field enums, to be passed
to the version diff factory. This method is called as part of
:meth:`make_version_diff()`.
"""
def make_version_diff(self, version, *args, **kwargs): def make_version_diff(self, version, *args, **kwargs):
"""
Make a version diff object, using the factory returned by
:meth:`get_version_diff_factory()`.
:param version: Reference to a Continuum version object.
:param title: If specified, must be as a kwarg. Optional
override for the version title text. If not specified,
:meth:`title_for_version()` is called for the title.
:param \*args: Additional args to pass to the factory.
:param \*\*kwargs: Additional kwargs to pass to the factory.
:returns: A :class:`~tailbone.diffs.VersionDiff` object.
"""
if 'title' not in kwargs: if 'title' not in kwargs:
kwargs['title'] = self.title_for_version(version) kwargs['title'] = self.title_for_version(version)
if 'enums' not in kwargs:
kwargs['enums'] = self.get_version_diff_enums(version)
factory = self.get_version_diff_factory() factory = self.get_version_diff_factory()
return factory(version, *args, **kwargs) return factory(version, *args, **kwargs)

View file

@ -27,6 +27,7 @@ Member Views
from collections import OrderedDict from collections import OrderedDict
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy_continuum as continuum
from rattail.db import model from rattail.db import model
from rattail.db.model import MembershipType, Member, MemberEquityPayment from rattail.db.model import MembershipType, Member, MemberEquityPayment
@ -71,6 +72,7 @@ class MembershipTypeView(MasterView):
] ]
def configure_grid(self, g): def configure_grid(self, g):
""" """
super().configure_grid(g) super().configure_grid(g)
g.set_sort_defaults('number') g.set_sort_defaults('number')
@ -79,6 +81,7 @@ class MembershipTypeView(MasterView):
g.set_link('name') g.set_link('name')
def get_row_data(self, memtype): def get_row_data(self, memtype):
""" """
model = self.model model = self.model
return self.Session.query(model.Member)\ return self.Session.query(model.Member)\
.filter(model.Member.membership_type == memtype) .filter(model.Member.membership_type == memtype)
@ -102,7 +105,7 @@ class MemberView(MasterView):
""" """
Master view for the Member class. Master view for the Member class.
""" """
model_class = model.Member model_class = Member
is_contact = True is_contact = True
touchable = True touchable = True
has_versions = True has_versions = True
@ -169,6 +172,7 @@ class MemberView(MasterView):
return app.get_people_handler().get_quickie_search_placeholder() return app.get_people_handler().get_quickie_search_placeholder()
def configure_grid(self, g): def configure_grid(self, g):
""" """
super().configure_grid(g) super().configure_grid(g)
route_prefix = self.get_route_prefix() route_prefix = self.get_route_prefix()
model = self.model model = self.model
@ -263,13 +267,16 @@ class MemberView(MasterView):
default=False) default=False)
def grid_extra_class(self, member, i): def grid_extra_class(self, member, i):
""" """
if not member.active: if not member.active:
return 'warning' return 'warning'
if member.equity_current is False: if member.equity_current is False:
return 'notice' return 'notice'
def configure_form(self, f): def configure_form(self, f):
""" """
super().configure_form(f) super().configure_form(f)
model = self.model
member = f.model_instance member = f.model_instance
# date fields # date fields
@ -342,6 +349,7 @@ class MemberView(MasterView):
return app.render_currency(total) return app.render_currency(total)
def template_kwargs_view(self, **kwargs): def template_kwargs_view(self, **kwargs):
""" """
kwargs = super().template_kwargs_view(**kwargs) kwargs = super().template_kwargs_view(**kwargs)
app = self.get_rattail_app() app = self.get_rattail_app()
member = kwargs['instance'] member = kwargs['instance']
@ -360,10 +368,12 @@ class MemberView(MasterView):
return kwargs return kwargs
def render_default_email(self, member, field): def render_default_email(self, member, field):
""" """
if member.emails: if member.emails:
return member.emails[0].address return member.emails[0].address
def render_default_phone(self, member, field): def render_default_phone(self, member, field):
""" """
if member.phones: if member.phones:
return member.phones[0].number return member.phones[0].number
@ -376,6 +386,7 @@ class MemberView(MasterView):
return tags.link_to(text, url) return tags.link_to(text, url)
def get_row_data(self, member): def get_row_data(self, member):
""" """
model = self.model model = self.model
return self.Session.query(model.MemberEquityPayment)\ return self.Session.query(model.MemberEquityPayment)\
.filter(model.MemberEquityPayment.member == member) .filter(model.MemberEquityPayment.member == member)
@ -395,6 +406,7 @@ class MemberView(MasterView):
uuid=payment.uuid) uuid=payment.uuid)
def configure_get_simple_settings(self): def configure_get_simple_settings(self):
""" """
return [ return [
# General # General
@ -417,7 +429,7 @@ class MemberEquityPaymentView(MasterView):
""" """
Master view for the MemberEquityPayment class. Master view for the MemberEquityPayment class.
""" """
model_class = model.MemberEquityPayment model_class = MemberEquityPayment
route_prefix = 'member_equity_payments' route_prefix = 'member_equity_payments'
url_prefix = '/member-equity-payments' url_prefix = '/member-equity-payments'
supports_grid_totals = True supports_grid_totals = True
@ -450,6 +462,7 @@ class MemberEquityPaymentView(MasterView):
] ]
def query(self, session): def query(self, session):
""" """
query = super().query(session) query = super().query(session)
model = self.model model = self.model
@ -458,6 +471,7 @@ class MemberEquityPaymentView(MasterView):
return query return query
def configure_grid(self, g): def configure_grid(self, g):
""" """
super().configure_grid(g) super().configure_grid(g)
model = self.model model = self.model
@ -502,6 +516,7 @@ class MemberEquityPaymentView(MasterView):
return {'totals_display': app.render_currency(total)} return {'totals_display': app.render_currency(total)}
def configure_form(self, f): def configure_form(self, f):
""" """
super().configure_form(f) super().configure_form(f)
model = self.model model = self.model
payment = f.model_instance payment = f.model_instance
@ -543,6 +558,14 @@ class MemberEquityPaymentView(MasterView):
# status_code # status_code
f.set_enum('status_code', model.MemberEquityPayment.STATUS) f.set_enum('status_code', model.MemberEquityPayment.STATUS)
def get_version_diff_enums(self, version):
""" """
model = self.model
cls = continuum.parent_class(version.__class__)
if cls is model.MemberEquityPayment:
return {'status_code': model.MemberEquityPayment.STATUS}
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()