From 85bdefc25b3ed8c3042b52fcf7eef63c173108e9 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 5 Jul 2017 17:16:28 -0500 Subject: [PATCH] Add versioning display support for contact-related models --- tailbone/templates/master/view_version.mako | 29 +++++++++-- tailbone/templates/themes/better/base.mako | 6 ++- tailbone/views/customers.py | 15 +++++- tailbone/views/employees.py | 12 ++++- tailbone/views/master.py | 57 ++++++++++++++++----- tailbone/views/people.py | 10 ++++ tailbone/views/stores.py | 11 +++- tailbone/views/vendors/core.py | 12 ++++- 8 files changed, 125 insertions(+), 27 deletions(-) diff --git a/tailbone/templates/master/view_version.mako b/tailbone/templates/master/view_version.mako index a75704a5..c58ad5d1 100644 --- a/tailbone/templates/master/view_version.mako +++ b/tailbone/templates/master/view_version.mako @@ -1,7 +1,7 @@ ## -*- coding: utf-8; -*- <%inherit file="/base.mako" /> -<%def name="title()">${instance_title} @ ver ${transaction.id} +<%def name="title()">${instance_title_normal} @ ver ${transaction.id} ## TODO: this was basically copied from Revel diff template..need to abstract @@ -40,7 +40,7 @@ background-color: #cfc; } - table.deleted td.local-value, + table.deleted td.host-value, table.diff tr.diff td.local-value { background-color: #fcc; } @@ -75,7 +75,7 @@
-
${transaction.user}
+
${transaction.user or ''}
@@ -94,9 +94,28 @@ % for version in versions: -

${version.version_parent.get_model_title()}

+

${title_for_version(version)}

- % if version.previous: + % if version.previous and version.operation_type == continuum.Operation.DELETE: + + + + + + + + + + % for field in fields_for_version(version): + + + + + + % endfor + +
field nameold valuenew value
${field}${repr(getattr(version.previous, field))} 
+ % elif version.previous: diff --git a/tailbone/templates/themes/better/base.mako b/tailbone/templates/themes/better/base.mako index c0f4546c..aab71227 100644 --- a/tailbone/templates/themes/better/base.mako +++ b/tailbone/templates/themes/better/base.mako @@ -1,4 +1,4 @@ -## -*- coding: utf-8 -*- +## -*- coding: utf-8; -*- <%namespace name="base" file="tailbone:templates/base.mako" /> <%namespace file="/menu.mako" import="main_menu_items" /> <%namespace file="/newgrids/nav.mako" import="grid_index_nav" /> @@ -40,6 +40,10 @@ ${model_title_plural} % else: ${h.link_to(index_title, index_url, class_='global')} + % if instance_url is not Undefined: + » + ${h.link_to(instance_title, instance_url, class_='global')} + % endif % if master.viewing and grid_index: ${grid_index_nav()} % endif diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py index c3c9ebf7..57580d8a 100644 --- a/tailbone/views/customers.py +++ b/tailbone/views/customers.py @@ -45,6 +45,7 @@ class CustomersView(MasterView): Master view for the Customer class. """ model_class = model.Customer + has_versions = True supports_mobile = True def _preconfigure_grid(self, g): @@ -118,15 +119,17 @@ class CustomersView(MasterView): raise HTTPNotFound def _preconfigure_fieldset(self, fs): + fs.id.set(label="ID") fs.append(forms.fields.DefaultPhoneField('default_phone', label="Phone Number")) fs.append(forms.fields.DefaultEmailField('default_email', label="Email Address")) fs.email_preference.set(renderer=forms.EnumFieldRenderer(self.enum.EMAIL_PREFERENCE)) - fs.append(forms.AssociationProxyField('people', renderer=forms.renderers.PeopleFieldRenderer)) + fs.append(forms.AssociationProxyField('people', renderer=forms.renderers.PeopleFieldRenderer, + readonly=True)) def configure_fieldset(self, fs): fs.configure( include=[ - fs.id.label("ID"), + fs.id, fs.name, # fs.phone.label("Phone Number").readonly(), fs.default_phone, @@ -143,6 +146,14 @@ class CustomersView(MasterView): fs.phone, ]) + def get_version_child_classes(self): + return [ + (model.CustomerPhoneNumber, 'parent_uuid'), + (model.CustomerEmailAddress, 'parent_uuid'), + (model.CustomerMailingAddress, 'parent_uuid'), + (model.CustomerPerson, 'customer_uuid'), + ] + class CustomerNameAutocomplete(AutocompleteView): """ diff --git a/tailbone/views/employees.py b/tailbone/views/employees.py index 5c729280..a4b86713 100644 --- a/tailbone/views/employees.py +++ b/tailbone/views/employees.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2016 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -44,6 +44,7 @@ class EmployeesView(MasterView): Master view for the Employee class. """ model_class = model.Employee + has_versions = True def _preconfigure_grid(self, g): g.joiners['phone'] = lambda q: q.outerjoin(model.EmployeePhoneNumber, sa.and_( @@ -154,6 +155,13 @@ class EmployeesView(MasterView): del fs.first_name del fs.last_name + def get_version_child_classes(self): + return [ + (model.Person, 'uuid', 'person_uuid'), + (model.EmployeePhoneNumber, 'parent_uuid'), + (model.EmployeeEmailAddress, 'parent_uuid'), + ] + class StoresField(fa.Field): diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 2e2db328..16412564 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -346,8 +346,7 @@ class MasterView(View): return self.render_to_response('versions', { 'instance': instance, 'instance_title': instance_title, - 'index_title': "{}: {}".format(self.get_model_title_plural(), instance_title), - 'index_url': self.get_action_url('view', instance), + 'instance_url': self.get_action_url('view', instance), 'grid': grid, }) @@ -392,9 +391,27 @@ class MasterView(View): """ model_class = self.get_model_class() transaction_class = continuum.transaction_class(model_class) - query = model_transaction_query(self.Session(), instance.uuid, model_class) + query = model_transaction_query(self.Session(), instance, model_class, + child_classes=self.normalize_version_child_classes()) return query.order_by(transaction_class.issued_at.desc()) + def get_version_child_classes(self): + """ + If applicable, should return a list of child classes which should be + considered when querying for version history of an object. + """ + return [] + + def normalize_version_child_classes(self): + classes = [] + for cls in self.get_version_child_classes(): + if not isinstance(cls, tuple): + cls = (cls, 'uuid', 'uuid') + elif len(cls) == 2: + cls = tuple([cls[0], cls[1], 'uuid']) + classes.append(cls) + return classes + def make_version_grid_kwargs(self, **kwargs): """ Return a dictionary of kwargs to be passed to the factory when @@ -440,7 +457,8 @@ class MasterView(View): instance = self.get_instance() model_class = self.get_model_class() Transaction = continuum.transaction_class(model_class) - transactions = model_transaction_query(self.Session(), instance.uuid, model_class) + transactions = model_transaction_query(self.Session(), instance, model_class, + child_classes=self.normalize_version_child_classes()) transaction_id = self.request.matchdict['txnid'] transaction = transactions.filter(Transaction.id == transaction_id).first() if not transaction: @@ -457,32 +475,45 @@ class MasterView(View): instance_title = self.get_instance_title(instance) return self.render_to_response('view_version', { 'instance': instance, - 'instance_title': instance_title, - 'index_title': "{}: {} (History)".format(self.get_model_title_plural(), instance_title), - 'index_url': self.get_action_url('versions', instance), + 'instance_title': "{} (history)".format(instance_title), + 'instance_title_normal': instance_title, + 'instance_url': self.get_action_url('versions', instance), 'transaction': transaction, 'changed': localtime(self.rattail_config, transaction.issued_at, from_utc=True), 'versions': self.get_relevant_versions(transaction, instance), 'previous_transaction': older, 'next_transaction': newer, + 'title_for_version': self.title_for_version, 'fields_for_version': self.fields_for_version, + 'continuum': continuum, }) + def title_for_version(self, version): + cls = continuum.parent_class(version.__class__) + return cls.get_model_title() + def fields_for_version(self, version): mapper = orm.class_mapper(version.__class__) fields = sorted(mapper.columns.keys()) - fields.remove('uuid') fields.remove('transaction_id') fields.remove('end_transaction_id') fields.remove('operation_type') return fields def get_relevant_versions(self, transaction, instance): - version_class = self.get_model_version_class() - query = self.Session.query(version_class)\ - .filter(version_class.transaction == transaction)\ - .filter(version_class.uuid == instance.uuid) - return query.all() + versions = [] + version_cls = self.get_model_version_class() + query = self.Session.query(version_cls)\ + .filter(version_cls.transaction == transaction)\ + .filter(version_cls.uuid == instance.uuid) + versions.extend(query.all()) + for cls, foreign_attr, primary_attr in self.normalize_version_child_classes(): + version_cls = continuum.version_class(cls) + query = self.Session.query(version_cls)\ + .filter(version_cls.transaction == transaction)\ + .filter(getattr(version_cls, foreign_attr) == getattr(instance, primary_attr)) + versions.extend(query.all()) + return versions def mobile_view(self): """ diff --git a/tailbone/views/people.py b/tailbone/views/people.py index 1d55d422..a88bf253 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -145,6 +145,16 @@ class PeopleView(MasterView): fs._customers, ]) + def get_version_child_classes(self): + return [ + (model.PersonPhoneNumber, 'parent_uuid'), + (model.PersonEmailAddress, 'parent_uuid'), + (model.PersonMailingAddress, 'parent_uuid'), + (model.Employee, 'person_uuid'), + (model.CustomerPerson, 'person_uuid'), + (model.VendorContact, 'person_uuid'), + ] + class PeopleAutocomplete(AutocompleteView): diff --git a/tailbone/views/stores.py b/tailbone/views/stores.py index 095ba5c4..c41deeac 100644 --- a/tailbone/views/stores.py +++ b/tailbone/views/stores.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2015 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -38,6 +38,7 @@ class StoresView(MasterView): Master view for the Store class. """ model_class = model.Store + has_versions = True def configure_grid(self, g): @@ -81,6 +82,12 @@ class StoresView(MasterView): fs.email.label("Email Address").readonly(), ]) + def get_version_child_classes(self): + return [ + (model.StorePhoneNumber, 'parent_uuid'), + (model.StoreEmailAddress, 'parent_uuid'), + ] + def includeme(config): StoresView.defaults(config) diff --git a/tailbone/views/vendors/core.py b/tailbone/views/vendors/core.py index 2aa46745..63bbf912 100644 --- a/tailbone/views/vendors/core.py +++ b/tailbone/views/vendors/core.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2015 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -39,6 +39,7 @@ class VendorsView(MasterView): Master view for the Vendor class. """ model_class = model.Vendor + has_versions = True def configure_grid(self, g): g.filters['name'].default_active = True @@ -78,6 +79,13 @@ class VendorsView(MasterView): for cost in q: Session.delete(cost) + def get_version_child_classes(self): + return [ + (model.VendorPhoneNumber, 'parent_uuid'), + (model.VendorEmailAddress, 'parent_uuid'), + (model.VendorContact, 'vendor_uuid'), + ] + class VendorVersionView(VersionView): """