Add versioning display support for contact-related models

This commit is contained in:
Lance Edgar 2017-07-05 17:16:28 -05:00
parent 20ddf2687b
commit 85bdefc25b
8 changed files with 125 additions and 27 deletions

View file

@ -1,7 +1,7 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/base.mako" /> <%inherit file="/base.mako" />
<%def name="title()">${instance_title} @ ver ${transaction.id}</%def> <%def name="title()">${instance_title_normal} @ ver ${transaction.id}</%def>
## TODO: this was basically copied from Revel diff template..need to abstract ## TODO: this was basically copied from Revel diff template..need to abstract
@ -40,7 +40,7 @@
background-color: #cfc; background-color: #cfc;
} }
table.deleted td.local-value, table.deleted td.host-value,
table.diff tr.diff td.local-value { table.diff tr.diff td.local-value {
background-color: #fcc; background-color: #fcc;
} }
@ -75,7 +75,7 @@
<div class="field-wrapper"> <div class="field-wrapper">
<label>Changed by</label> <label>Changed by</label>
<div class="field">${transaction.user}</div> <div class="field">${transaction.user or ''}</div>
</div> </div>
<div class="field-wrapper"> <div class="field-wrapper">
@ -94,9 +94,28 @@
% for version in versions: % for version in versions:
<h2>${version.version_parent.get_model_title()}</h2> <h2>${title_for_version(version)}</h2>
% if version.previous: % if version.previous and version.operation_type == continuum.Operation.DELETE:
<table class="deleted">
<thead>
<tr>
<th>field name</th>
<th>old value</th>
<th>new value</th>
</tr>
</thead>
<tbody>
% for field in fields_for_version(version):
<tr>
<td class="field">${field}</td>
<td class="value local-value">${repr(getattr(version.previous, field))}</td>
<td class="value host-value">&nbsp;</td>
</tr>
% endfor
</tbody>
</table>
% elif version.previous:
<table class="diff"> <table class="diff">
<thead> <thead>
<tr> <tr>

View file

@ -1,4 +1,4 @@
## -*- coding: utf-8 -*- ## -*- coding: utf-8; -*-
<%namespace name="base" file="tailbone:templates/base.mako" /> <%namespace name="base" file="tailbone:templates/base.mako" />
<%namespace file="/menu.mako" import="main_menu_items" /> <%namespace file="/menu.mako" import="main_menu_items" />
<%namespace file="/newgrids/nav.mako" import="grid_index_nav" /> <%namespace file="/newgrids/nav.mako" import="grid_index_nav" />
@ -40,6 +40,10 @@
<span class="global">${model_title_plural}</span> <span class="global">${model_title_plural}</span>
% else: % else:
${h.link_to(index_title, index_url, class_='global')} ${h.link_to(index_title, index_url, class_='global')}
% if instance_url is not Undefined:
<span class="global">&raquo;</span>
${h.link_to(instance_title, instance_url, class_='global')}
% endif
% if master.viewing and grid_index: % if master.viewing and grid_index:
${grid_index_nav()} ${grid_index_nav()}
% endif % endif

View file

@ -45,6 +45,7 @@ class CustomersView(MasterView):
Master view for the Customer class. Master view for the Customer class.
""" """
model_class = model.Customer model_class = model.Customer
has_versions = True
supports_mobile = True supports_mobile = True
def _preconfigure_grid(self, g): def _preconfigure_grid(self, g):
@ -118,15 +119,17 @@ class CustomersView(MasterView):
raise HTTPNotFound raise HTTPNotFound
def _preconfigure_fieldset(self, fs): def _preconfigure_fieldset(self, fs):
fs.id.set(label="ID")
fs.append(forms.fields.DefaultPhoneField('default_phone', label="Phone Number")) fs.append(forms.fields.DefaultPhoneField('default_phone', label="Phone Number"))
fs.append(forms.fields.DefaultEmailField('default_email', label="Email Address")) fs.append(forms.fields.DefaultEmailField('default_email', label="Email Address"))
fs.email_preference.set(renderer=forms.EnumFieldRenderer(self.enum.EMAIL_PREFERENCE)) 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): def configure_fieldset(self, fs):
fs.configure( fs.configure(
include=[ include=[
fs.id.label("ID"), fs.id,
fs.name, fs.name,
# fs.phone.label("Phone Number").readonly(), # fs.phone.label("Phone Number").readonly(),
fs.default_phone, fs.default_phone,
@ -143,6 +146,14 @@ class CustomersView(MasterView):
fs.phone, 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): class CustomerNameAutocomplete(AutocompleteView):
""" """

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8; -*-
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2016 Lance Edgar # Copyright © 2010-2017 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -44,6 +44,7 @@ class EmployeesView(MasterView):
Master view for the Employee class. Master view for the Employee class.
""" """
model_class = model.Employee model_class = model.Employee
has_versions = True
def _preconfigure_grid(self, g): def _preconfigure_grid(self, g):
g.joiners['phone'] = lambda q: q.outerjoin(model.EmployeePhoneNumber, sa.and_( g.joiners['phone'] = lambda q: q.outerjoin(model.EmployeePhoneNumber, sa.and_(
@ -154,6 +155,13 @@ class EmployeesView(MasterView):
del fs.first_name del fs.first_name
del fs.last_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): class StoresField(fa.Field):

View file

@ -346,8 +346,7 @@ class MasterView(View):
return self.render_to_response('versions', { return self.render_to_response('versions', {
'instance': instance, 'instance': instance,
'instance_title': instance_title, 'instance_title': instance_title,
'index_title': "{}: {}".format(self.get_model_title_plural(), instance_title), 'instance_url': self.get_action_url('view', instance),
'index_url': self.get_action_url('view', instance),
'grid': grid, 'grid': grid,
}) })
@ -392,9 +391,27 @@ class MasterView(View):
""" """
model_class = self.get_model_class() model_class = self.get_model_class()
transaction_class = continuum.transaction_class(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()) 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): def make_version_grid_kwargs(self, **kwargs):
""" """
Return a dictionary of kwargs to be passed to the factory when Return a dictionary of kwargs to be passed to the factory when
@ -440,7 +457,8 @@ class MasterView(View):
instance = self.get_instance() instance = self.get_instance()
model_class = self.get_model_class() model_class = self.get_model_class()
Transaction = continuum.transaction_class(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_id = self.request.matchdict['txnid']
transaction = transactions.filter(Transaction.id == transaction_id).first() transaction = transactions.filter(Transaction.id == transaction_id).first()
if not transaction: if not transaction:
@ -457,32 +475,45 @@ class MasterView(View):
instance_title = self.get_instance_title(instance) instance_title = self.get_instance_title(instance)
return self.render_to_response('view_version', { return self.render_to_response('view_version', {
'instance': instance, 'instance': instance,
'instance_title': instance_title, 'instance_title': "{} (history)".format(instance_title),
'index_title': "{}: {} (History)".format(self.get_model_title_plural(), instance_title), 'instance_title_normal': instance_title,
'index_url': self.get_action_url('versions', instance), 'instance_url': self.get_action_url('versions', instance),
'transaction': transaction, 'transaction': transaction,
'changed': localtime(self.rattail_config, transaction.issued_at, from_utc=True), 'changed': localtime(self.rattail_config, transaction.issued_at, from_utc=True),
'versions': self.get_relevant_versions(transaction, instance), 'versions': self.get_relevant_versions(transaction, instance),
'previous_transaction': older, 'previous_transaction': older,
'next_transaction': newer, 'next_transaction': newer,
'title_for_version': self.title_for_version,
'fields_for_version': self.fields_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): def fields_for_version(self, version):
mapper = orm.class_mapper(version.__class__) mapper = orm.class_mapper(version.__class__)
fields = sorted(mapper.columns.keys()) fields = sorted(mapper.columns.keys())
fields.remove('uuid')
fields.remove('transaction_id') fields.remove('transaction_id')
fields.remove('end_transaction_id') fields.remove('end_transaction_id')
fields.remove('operation_type') fields.remove('operation_type')
return fields return fields
def get_relevant_versions(self, transaction, instance): def get_relevant_versions(self, transaction, instance):
version_class = self.get_model_version_class() versions = []
query = self.Session.query(version_class)\ version_cls = self.get_model_version_class()
.filter(version_class.transaction == transaction)\ query = self.Session.query(version_cls)\
.filter(version_class.uuid == instance.uuid) .filter(version_cls.transaction == transaction)\
return query.all() .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): def mobile_view(self):
""" """

View file

@ -145,6 +145,16 @@ class PeopleView(MasterView):
fs._customers, 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): class PeopleAutocomplete(AutocompleteView):

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8; -*-
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2017 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -38,6 +38,7 @@ class StoresView(MasterView):
Master view for the Store class. Master view for the Store class.
""" """
model_class = model.Store model_class = model.Store
has_versions = True
def configure_grid(self, g): def configure_grid(self, g):
@ -81,6 +82,12 @@ class StoresView(MasterView):
fs.email.label("Email Address").readonly(), fs.email.label("Email Address").readonly(),
]) ])
def get_version_child_classes(self):
return [
(model.StorePhoneNumber, 'parent_uuid'),
(model.StoreEmailAddress, 'parent_uuid'),
]
def includeme(config): def includeme(config):
StoresView.defaults(config) StoresView.defaults(config)

View file

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8; -*-
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2017 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -39,6 +39,7 @@ class VendorsView(MasterView):
Master view for the Vendor class. Master view for the Vendor class.
""" """
model_class = model.Vendor model_class = model.Vendor
has_versions = True
def configure_grid(self, g): def configure_grid(self, g):
g.filters['name'].default_active = True g.filters['name'].default_active = True
@ -78,6 +79,13 @@ class VendorsView(MasterView):
for cost in q: for cost in q:
Session.delete(cost) 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): class VendorVersionView(VersionView):
""" """