diff --git a/docs/api/diffs.rst b/docs/api/diffs.rst new file mode 100644 index 00000000..fb1bba71 --- /dev/null +++ b/docs/api/diffs.rst @@ -0,0 +1,6 @@ + +``tailbone.diffs`` +================== + +.. automodule:: tailbone.diffs + :members: diff --git a/docs/api/views/master.rst b/docs/api/views/master.rst index 44278e0a..e7de7170 100644 --- a/docs/api/views/master.rst +++ b/docs/api/views/master.rst @@ -81,6 +81,12 @@ override when defining your subclass. override this for certain views, if so that should be done within :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 ------------------- @@ -100,6 +106,14 @@ subclass. .. 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 --------------- diff --git a/docs/api/views/members.rst b/docs/api/views/members.rst new file mode 100644 index 00000000..6a9e9168 --- /dev/null +++ b/docs/api/views/members.rst @@ -0,0 +1,6 @@ + +``tailbone.views.members`` +========================== + +.. automodule:: tailbone.views.members + :members: diff --git a/docs/index.rst b/docs/index.rst index b19d859f..4aa22f3e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ Package API: api/api/batch/core api/api/batch/ordering + api/diffs api/forms api/grids api/grids.core @@ -53,6 +54,7 @@ Package API: api/views/batch.vendorcatalog api/views/core api/views/master + api/views/members api/views/purchasing.batch api/views/purchasing.ordering diff --git a/tailbone/diffs.py b/tailbone/diffs.py index cdf35830..98253c57 100644 --- a/tailbone/diffs.py +++ b/tailbone/diffs.py @@ -34,35 +34,38 @@ from webhelpers2.html import HTML class Diff(object): """ 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', 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.new_data = new_data self.columns = columns or ["field name", "old value", "new value"] self.fields = fields or self.make_fields() + self.enums = enums or {} self._render_field = render_field or self.render_field_default self.render_value = render_value or self.render_value_default self.nature = nature @@ -92,7 +95,7 @@ class Diff(object): for the given field. May be an empty string, or a snippet of HTML attribute syntax, e.g.: - .. code-highlight:: none + .. code-block:: none class="diff" foo="bar" @@ -132,7 +135,21 @@ class Diff(object): 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): @@ -176,9 +193,40 @@ class VersionDiff(Diff): if field not in unwanted] 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)], 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: if prop.uselist: continue diff --git a/tailbone/views/master.py b/tailbone/views/master.py index cf001c36..cc2adcaf 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -597,7 +597,6 @@ class MasterView(View): return defaults def configure_row_grid(self, grid): - # super(MasterView, self).configure_row_grid(grid) self.set_row_labels(grid) self.configure_column_customer_key(grid) @@ -1528,6 +1527,15 @@ class MasterView(View): }) 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__) return cls.get_model_title() @@ -4962,13 +4970,52 @@ class MasterView(View): return diffs.Diff(old_data, new_data, **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'): return self.version_diff_factory 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): + """ + 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: 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() return factory(version, *args, **kwargs) diff --git a/tailbone/views/members.py b/tailbone/views/members.py index b1bb2a0d..de844eb7 100644 --- a/tailbone/views/members.py +++ b/tailbone/views/members.py @@ -27,6 +27,7 @@ Member Views from collections import OrderedDict import sqlalchemy as sa +import sqlalchemy_continuum as continuum from rattail.db import model from rattail.db.model import MembershipType, Member, MemberEquityPayment @@ -71,6 +72,7 @@ class MembershipTypeView(MasterView): ] def configure_grid(self, g): + """ """ super().configure_grid(g) g.set_sort_defaults('number') @@ -79,6 +81,7 @@ class MembershipTypeView(MasterView): g.set_link('name') def get_row_data(self, memtype): + """ """ model = self.model return self.Session.query(model.Member)\ .filter(model.Member.membership_type == memtype) @@ -102,7 +105,7 @@ class MemberView(MasterView): """ Master view for the Member class. """ - model_class = model.Member + model_class = Member is_contact = True touchable = True has_versions = True @@ -169,6 +172,7 @@ class MemberView(MasterView): return app.get_people_handler().get_quickie_search_placeholder() def configure_grid(self, g): + """ """ super().configure_grid(g) route_prefix = self.get_route_prefix() model = self.model @@ -263,13 +267,16 @@ class MemberView(MasterView): default=False) def grid_extra_class(self, member, i): + """ """ if not member.active: return 'warning' if member.equity_current is False: return 'notice' def configure_form(self, f): + """ """ super().configure_form(f) + model = self.model member = f.model_instance # date fields @@ -342,6 +349,7 @@ class MemberView(MasterView): return app.render_currency(total) def template_kwargs_view(self, **kwargs): + """ """ kwargs = super().template_kwargs_view(**kwargs) app = self.get_rattail_app() member = kwargs['instance'] @@ -360,10 +368,12 @@ class MemberView(MasterView): return kwargs def render_default_email(self, member, field): + """ """ if member.emails: return member.emails[0].address def render_default_phone(self, member, field): + """ """ if member.phones: return member.phones[0].number @@ -376,6 +386,7 @@ class MemberView(MasterView): return tags.link_to(text, url) def get_row_data(self, member): + """ """ model = self.model return self.Session.query(model.MemberEquityPayment)\ .filter(model.MemberEquityPayment.member == member) @@ -395,6 +406,7 @@ class MemberView(MasterView): uuid=payment.uuid) def configure_get_simple_settings(self): + """ """ return [ # General @@ -417,7 +429,7 @@ class MemberEquityPaymentView(MasterView): """ Master view for the MemberEquityPayment class. """ - model_class = model.MemberEquityPayment + model_class = MemberEquityPayment route_prefix = 'member_equity_payments' url_prefix = '/member-equity-payments' supports_grid_totals = True @@ -450,6 +462,7 @@ class MemberEquityPaymentView(MasterView): ] def query(self, session): + """ """ query = super().query(session) model = self.model @@ -458,6 +471,7 @@ class MemberEquityPaymentView(MasterView): return query def configure_grid(self, g): + """ """ super().configure_grid(g) model = self.model @@ -502,6 +516,7 @@ class MemberEquityPaymentView(MasterView): return {'totals_display': app.render_currency(total)} def configure_form(self, f): + """ """ super().configure_form(f) model = self.model payment = f.model_instance @@ -543,6 +558,14 @@ class MemberEquityPaymentView(MasterView): # status_code 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): base = globals()