[gen] Allow to show the cumulative differences performed on historized String fields with format=String.XHTML.
This commit is contained in:
parent
6caeeb1761
commit
ab00917df6
|
@ -12,6 +12,7 @@ import appy.pod
|
|||
from appy.pod.renderer import Renderer
|
||||
from appy.shared.data import countries
|
||||
from appy.shared.xml_parser import XhtmlCleaner
|
||||
from appy.shared.diff import HtmlDiff
|
||||
from appy.shared.utils import Traceback, getOsTempFolder, formatNumber, \
|
||||
FileWrapper, sequenceTypes
|
||||
|
||||
|
@ -678,8 +679,9 @@ class Type:
|
|||
layouts['view'].addCssClasses('focus')
|
||||
layouts['edit'].addCssClasses('focus')
|
||||
# If layouts are the default ones, set width=None instead of width=100%
|
||||
# for the field if it is not in a group.
|
||||
if areDefault and not self.group:
|
||||
# for the field if it is not in a group (excepted for rich texts).
|
||||
if areDefault and not self.group and \
|
||||
not ((self.type == 'String') and (self.format == String.XHTML)):
|
||||
for layoutType in layouts.iterkeys():
|
||||
layouts[layoutType].width = ''
|
||||
# Remove letters "r" from the layouts if the field is not required.
|
||||
|
@ -765,10 +767,12 @@ class Type:
|
|||
return self.default
|
||||
return value
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
'''p_value is a real p_obj(ect) value from a field from this type. This
|
||||
method returns a pretty, string-formatted version, for displaying
|
||||
purposes. Needs to be overridden by some child classes.'''
|
||||
purposes. Needs to be overridden by some child classes. If
|
||||
p_showChanges is True, the result must also include the changes that
|
||||
occurred on p_value across the ages.'''
|
||||
if self.isEmptyValue(value): return ''
|
||||
return value
|
||||
|
||||
|
@ -971,7 +975,7 @@ class Integer(Type):
|
|||
def getStorableValue(self, value):
|
||||
if not self.isEmptyValue(value): return self.pythonType(value)
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if self.isEmptyValue(value): return ''
|
||||
return str(value)
|
||||
|
||||
|
@ -1009,7 +1013,7 @@ class Float(Type):
|
|||
label, sdefault, scolspan)
|
||||
self.pythonType = float
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
return formatNumber(value, sep=self.sep[0], precision=self.precision,
|
||||
tsep=self.tsep)
|
||||
|
||||
|
@ -1214,7 +1218,7 @@ class String(Type):
|
|||
view = 'lc-f'
|
||||
else:
|
||||
view = 'l-f'
|
||||
return {'view': view, 'edit': 'lrv-d-f'}
|
||||
return {'view': Table(view, width='100%'), 'edit': 'lrv-d-f'}
|
||||
elif self.isMultiValued():
|
||||
return {'view': 'l-f', 'edit': 'lrv-f'}
|
||||
|
||||
|
@ -1246,7 +1250,31 @@ class String(Type):
|
|||
type='warning')
|
||||
Type.store(self, obj, value)
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getDiffValue(self, obj, value):
|
||||
'''Returns a version of p_value that includes the cumulative diffs
|
||||
between successive versions.'''
|
||||
res = None
|
||||
lastEvent = None
|
||||
for event in obj.workflow_history.values()[0]:
|
||||
if event['action'] != '_datachange_': continue
|
||||
if self.name not in event['changes']: continue
|
||||
if res == None:
|
||||
# We have found the first version of the field
|
||||
res = event['changes'][self.name][0] or ''
|
||||
else:
|
||||
# We need to produce the difference between current result and
|
||||
# this version.
|
||||
iMsg, dMsg = obj.getHistoryTexts(lastEvent)
|
||||
thisVersion = event['changes'][self.name][0] or ''
|
||||
comparator = HtmlDiff(res, thisVersion, iMsg, dMsg)
|
||||
res = comparator.get()
|
||||
lastEvent = event
|
||||
# Now we need to compare the result with the current version.
|
||||
iMsg, dMsg = obj.getHistoryTexts(lastEvent)
|
||||
comparator = HtmlDiff(res, value or '', iMsg, dMsg)
|
||||
return comparator.get()
|
||||
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if self.isEmptyValue(value): return ''
|
||||
res = value
|
||||
if self.isSelect:
|
||||
|
@ -1265,6 +1293,9 @@ class String(Type):
|
|||
res = [t('%s_list_%s' % (self.labelId, v)) for v in value]
|
||||
else:
|
||||
res = t('%s_list_%s' % (self.labelId, value))
|
||||
elif (self.format == String.XHTML) and showChanges:
|
||||
# Compute the successive changes that occurred on p_value.
|
||||
res = self.getDiffValue(obj, res)
|
||||
# If value starts with a carriage return, add a space; else, it will
|
||||
# be ignored.
|
||||
if isinstance(res, basestring) and \
|
||||
|
@ -1477,7 +1508,7 @@ class Boolean(Type):
|
|||
if value == None: return False
|
||||
return value
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if value: res = obj.translate('yes')
|
||||
else: res = obj.translate('no')
|
||||
return res
|
||||
|
@ -1542,7 +1573,7 @@ class Date(Type):
|
|||
except DateTime.DateError, ValueError:
|
||||
return obj.translate('bad_date')
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if self.isEmptyValue(value): return ''
|
||||
tool = obj.getTool().appy()
|
||||
# A problem may occur with some extreme year values. Replace the "year"
|
||||
|
@ -1621,7 +1652,7 @@ class File(Type):
|
|||
if value: value = FileWrapper(value)
|
||||
return value
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if not value: return value
|
||||
return value._zopeFile
|
||||
|
||||
|
@ -1901,7 +1932,7 @@ class Ref(Type):
|
|||
if someObjects: return res
|
||||
return res.objects
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
return value
|
||||
|
||||
def getIndexType(self): return 'ListIndex'
|
||||
|
@ -2142,7 +2173,7 @@ class Computed(Type):
|
|||
# self.method is a method that will return the field value
|
||||
return self.callMethod(obj, self.method, raiseOnError=True)
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
def getFormattedValue(self, obj, value, showChanges=False):
|
||||
if not isinstance(value, basestring): return str(value)
|
||||
return value
|
||||
|
||||
|
|
|
@ -514,10 +514,10 @@ class BaseMixin:
|
|||
listType = self.getAppyType(listName)
|
||||
return listType.getInnerValue(outerValue, name, int(i))
|
||||
|
||||
def getFormattedFieldValue(self, name, value):
|
||||
def getFormattedFieldValue(self, name, value, showChanges):
|
||||
'''Gets a nice, string representation of p_value which is a value from
|
||||
field named p_name.'''
|
||||
return self.getAppyType(name).getFormattedValue(self, value)
|
||||
return self.getAppyType(name).getFormattedValue(self,value,showChanges)
|
||||
|
||||
def getRequestFieldValue(self, name):
|
||||
'''Gets the value of field p_name as may be present in the request.'''
|
||||
|
@ -991,15 +991,6 @@ class BaseMixin:
|
|||
return 1
|
||||
return 0
|
||||
|
||||
def hasHistory(self):
|
||||
'''Has this object an history?'''
|
||||
if hasattr(self.aq_base, 'workflow_history') and self.workflow_history:
|
||||
key = self.workflow_history.keys()[0]
|
||||
for event in self.workflow_history[key]:
|
||||
if event['action'] and (event['comments'] != '_invisible_'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def findNewValue(self, field, history, stopIndex):
|
||||
'''This function tries to find a more recent version of value of p_field
|
||||
on p_self. It first tries to find it in history[:stopIndex+1]. If
|
||||
|
@ -1027,6 +1018,20 @@ class BaseMixin:
|
|||
res.append(msg.encode('utf-8'))
|
||||
return res
|
||||
|
||||
def hasHistory(self, fieldName=None):
|
||||
'''Has this object an history? If p_fieldName is specified, the question
|
||||
becomes: has this object an history for field p_fieldName?'''
|
||||
if hasattr(self.aq_base, 'workflow_history') and self.workflow_history:
|
||||
history = self.workflow_history.values()[0]
|
||||
if not fieldName:
|
||||
for event in history:
|
||||
if event['action'] and (event['comments'] != '_invisible_'):
|
||||
return True
|
||||
else:
|
||||
for event in history:
|
||||
if (event['action'] == '_datachange_') and \
|
||||
(fieldName in event['changes']): return True
|
||||
|
||||
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False,
|
||||
batchSize=5):
|
||||
'''Returns the history for this object, sorted in reverse order (most
|
||||
|
|
|
@ -198,6 +198,8 @@ appyLabels = [
|
|||
('del_next_events', 'Also delete successive events of the same type.'),
|
||||
('history_insert', 'Inserted by ${userName}'),
|
||||
('history_delete', 'Deleted by ${userName}'),
|
||||
('changes_show', 'Show changes'),
|
||||
('changes_hide', 'Hide changes'),
|
||||
]
|
||||
|
||||
# Some default values for labels whose ids are not fixed (so they can't be
|
||||
|
|
|
@ -192,6 +192,14 @@ function askComputedField(hookId, objectUrl, fieldName) {
|
|||
askAjaxChunk(hookId, 'GET', objectUrl, 'widgets/computed', 'viewContent', params);
|
||||
}
|
||||
|
||||
function askField(hookId, objectUrl, layoutType, showChanges){
|
||||
// Sends an Ajax request for getting the content of any field.
|
||||
var fieldName = hookId.split('_')[1];
|
||||
var params = {'fieldName': fieldName, 'layoutType': layoutType,
|
||||
'showChanges': showChanges};
|
||||
askAjaxChunk(hookId, 'GET', objectUrl, 'widgets/show', 'fieldAjax', params);
|
||||
}
|
||||
|
||||
// Function used by checkbox widgets for having radio-button-like behaviour
|
||||
function toggleCheckbox(visibleCheckbox, hiddenBoolean) {
|
||||
vis = document.getElementById(visibleCheckbox);
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<metal:history define-macro="objectHistory"
|
||||
tal:define="startNumber request/startNumber|python:0;
|
||||
startNumber python: int(startNumber);
|
||||
batchSize python: int(request.get('maxPerPage'));
|
||||
batchSize python: int(request.get('maxPerPage', 5));
|
||||
historyInfo python: contextObj.getHistory(startNumber, batchSize=batchSize);
|
||||
objs historyInfo/events;
|
||||
totalNumber historyInfo/totalNumber;
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
layoutType The kind of layout: "view"? "edit"? "cell"?
|
||||
layout The layout object that will dictate how object content
|
||||
will be rendered.
|
||||
tagId The ID of the main tag for this layout.
|
||||
|
||||
Options:
|
||||
contextMacro The base folder containing the macros to call for
|
||||
rendering the elements within the layout.
|
||||
Defaults to app.ui
|
||||
tagId The ID of the main tag for this layout.
|
||||
tagName The name of the main tag for this layout (used
|
||||
a.o. for master/slave relationships).
|
||||
tagCss Some additional CSS class for the main tag
|
||||
|
@ -59,15 +60,19 @@
|
|||
Optionally:
|
||||
widgetName If the field to show is within a List, we cheat and
|
||||
include, within the widgetName, the row index.
|
||||
showChanges Do we need to show a variant of the field showing
|
||||
successive changes made to it?
|
||||
|
||||
</tal:comment>
|
||||
<metal:field define-macro="field"
|
||||
tal:define="contextMacro python: app.ui.widgets;
|
||||
showChanges showChanges| python:False;
|
||||
layout python: widget['layouts'][layoutType];
|
||||
name widgetName| widget/name;
|
||||
sync python: widget['sync'][layoutType];
|
||||
outerValue value|nothing;
|
||||
rawValue python: contextObj.getFieldValue(name,onlyIfSync=True,layoutType=layoutType,outerValue=outerValue);
|
||||
value python: contextObj.getFormattedFieldValue(name, rawValue);
|
||||
value python: contextObj.getFormattedFieldValue(name, rawValue, showChanges);
|
||||
requestValue python: contextObj.getRequestFieldValue(name);
|
||||
inRequest python: request.has_key(name);
|
||||
errors errors | python: ();
|
||||
|
@ -78,10 +83,20 @@
|
|||
tagCss tagCss | python:'';
|
||||
tagCss python: ('%s %s' % (slaveCss, tagCss)).strip();
|
||||
tagId python: '%s_%s' % (contextObj.UID(), name);
|
||||
tagName python: widget['master'] and 'slave' or ''">
|
||||
tagName python: widget['master'] and 'slave' or '';">
|
||||
<metal:layout use-macro="context/ui/widgets/show/macros/layout"/>
|
||||
</metal:field>
|
||||
|
||||
<tal:comment replace="nothing">Call the previous macro, but from Ajax.</tal:comment>
|
||||
<metal:afield define-macro="fieldAjax"
|
||||
tal:define="widgetName request/fieldName;
|
||||
layoutType request/layoutType;
|
||||
showChanges python: request.get('showChanges', 'False') == 'True';
|
||||
widget python: contextObj.getAppyType(widgetName, asDict=True);
|
||||
page widget/pageName">
|
||||
<metal:field use-macro="context/ui/widgets/show/macros/field"/>
|
||||
</metal:afield>
|
||||
|
||||
<tal:comment replace="nothing">
|
||||
This macro displays the widget corresponding to a group of widgets.
|
||||
It requires:
|
||||
|
@ -223,4 +238,4 @@
|
|||
<tal:required metal:define-macro="required"><img tal:attributes="src string: $appUrl/ui/required.gif"/></tal:required>
|
||||
|
||||
<tal:comment replace="nothing">Button for showing changes to the field.</tal:comment>
|
||||
<tal:changes metal:define-macro="changes"><img tal:attributes="src string: $appUrl/ui/changes.png"/></tal:changes>
|
||||
<tal:changes metal:define-macro="changes" tal:condition="python: contextObj.hasHistory(name)"><img style="cursor:pointer" tal:condition="not: showChanges" tal:attributes="src string: $appUrl/ui/changes.png; onclick python: 'askField(\'%s\',\'%s\',\'view\',\'True\')' % (tagId, contextObj.absolute_url()); title python: _('changes_show')"/><img style="cursor:pointer" tal:condition="showChanges" tal:attributes="src string: $appUrl/ui/changesNo.png; onclick python: 'askField(\'%s\',\'%s\',\'view\',\'False\')' % (tagId, contextObj.absolute_url()); title python: _('changes_hide')"/></tal:changes>
|
||||
|
|
Loading…
Reference in a new issue