[gen] Historization of multilingual fields.
This commit is contained in:
parent
7889277328
commit
c8cf3911fa
|
@ -51,7 +51,7 @@ class Field:
|
||||||
# * showChanges If True, a variant of the field showing successive changes
|
# * showChanges If True, a variant of the field showing successive changes
|
||||||
# made to it is shown.
|
# made to it is shown.
|
||||||
pxRender = Px('''
|
pxRender = Px('''
|
||||||
<x var="showChanges=showChanges|req.get('showChanges',False);
|
<x var="showChanges=showChanges|req.get('showChanges') == 'True';
|
||||||
layoutType=layoutType|req.get('layoutType');
|
layoutType=layoutType|req.get('layoutType');
|
||||||
isSearch = layoutType == 'search';
|
isSearch = layoutType == 'search';
|
||||||
layout=field.layouts[layoutType];
|
layout=field.layouts[layoutType];
|
||||||
|
@ -117,14 +117,24 @@ class Field:
|
||||||
pxRequired = Px('''<img src=":url('required.gif')"/>''')
|
pxRequired = Px('''<img src=":url('required.gif')"/>''')
|
||||||
|
|
||||||
# Button for showing changes to the field.
|
# Button for showing changes to the field.
|
||||||
pxChanges = Px('''<x if=":zobj.hasHistory(name)"><img class="clickable"
|
pxChanges = Px('''
|
||||||
if="not showChanges" src=":url('changes')" title="_('changes_show')"
|
<x if="zobj.hasHistory(name)">
|
||||||
onclick=":'askField(%s,%s,%s,null,%s)' % \
|
<!-- Button for showing the field version containing changes -->
|
||||||
(q(tagId), q(zobj.absolute_url()), q('view'), q('True'))"/><img
|
<input type="button" class="button" if="not showChanges"
|
||||||
class="clickable" if="showChanges" src=":url('changesNo')"
|
var="label=_('changes_show')" value=":label"
|
||||||
onclick=":'askField(%s,%s,%s,null,%s)' % \
|
style=":'%s; %s' % (url('changes', bg=True), \
|
||||||
(q(tagId), q(zobj.absolute_url(), q('view'), q('True'))"
|
ztool.getButtonWidth(label))"
|
||||||
title=":_('changes_hide')"/></x>''')
|
onclick=":'askField(%s,%s,%s,null,%s)' % \
|
||||||
|
(q(tagId), q(obj.url), q('view'), q('True'))"/>
|
||||||
|
|
||||||
|
<!-- Button for showing the field version without changes -->
|
||||||
|
<input type="button" class="button" if="showChanges"
|
||||||
|
var="label=_('changes_hide')" value=":label"
|
||||||
|
style=":'%s; %s' % (url('changesNo', bg=True), \
|
||||||
|
ztool.getButtonWidth(label))"
|
||||||
|
onclick=":'askField(%s,%s,%s,null,%s)' % \
|
||||||
|
(q(tagId), q(obj.url), q('view'), q('False'))"/>
|
||||||
|
</x>''')
|
||||||
|
|
||||||
def __init__(self, validator, multiplicity, default, show, page, group,
|
def __init__(self, validator, multiplicity, default, show, page, group,
|
||||||
layouts, move, indexed, searchable, specificReadPermission,
|
layouts, move, indexed, searchable, specificReadPermission,
|
||||||
|
|
|
@ -549,22 +549,25 @@ class String(Field):
|
||||||
if Field.isEmptyValue(self, v, obj): return
|
if Field.isEmptyValue(self, v, obj): return
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getDiffValue(self, obj, value):
|
def getDiffValue(self, obj, value, language):
|
||||||
'''Returns a version of p_value that includes the cumulative diffs
|
'''Returns a version of p_value that includes the cumulative diffs
|
||||||
between successive versions.'''
|
between successive versions. If the field is non-multilingual, it
|
||||||
|
must be called with p_language being None. Else, p_language
|
||||||
|
identifies the language-specific part we will work on.'''
|
||||||
res = None
|
res = None
|
||||||
lastEvent = None
|
lastEvent = None
|
||||||
for event in obj.workflow_history.values()[0]:
|
name = language and ('%s-%s' % (self.name, language)) or self.name
|
||||||
|
for event in obj.workflow_history['appy']:
|
||||||
if event['action'] != '_datachange_': continue
|
if event['action'] != '_datachange_': continue
|
||||||
if self.name not in event['changes']: continue
|
if name not in event['changes']: continue
|
||||||
if res == None:
|
if res == None:
|
||||||
# We have found the first version of the field
|
# We have found the first version of the field
|
||||||
res = event['changes'][self.name][0] or ''
|
res = event['changes'][name][0] or ''
|
||||||
else:
|
else:
|
||||||
# We need to produce the difference between current result and
|
# We need to produce the difference between current result and
|
||||||
# this version.
|
# this version.
|
||||||
iMsg, dMsg = obj.getHistoryTexts(lastEvent)
|
iMsg, dMsg = obj.getHistoryTexts(lastEvent)
|
||||||
thisVersion = event['changes'][self.name][0] or ''
|
thisVersion = event['changes'][name][0] or ''
|
||||||
comparator = HtmlDiff(res, thisVersion, iMsg, dMsg)
|
comparator = HtmlDiff(res, thisVersion, iMsg, dMsg)
|
||||||
res = comparator.get()
|
res = comparator.get()
|
||||||
lastEvent = event
|
lastEvent = event
|
||||||
|
@ -573,7 +576,12 @@ class String(Field):
|
||||||
comparator = HtmlDiff(res, value or '', iMsg, dMsg)
|
comparator = HtmlDiff(res, value or '', iMsg, dMsg)
|
||||||
return comparator.get()
|
return comparator.get()
|
||||||
|
|
||||||
def getUnilingualFormattedValue(self, obj, value, showChanges=False):
|
def getUnilingualFormattedValue(self, obj, value, showChanges=False,
|
||||||
|
language=None):
|
||||||
|
'''If no p_language is specified, this method is called by
|
||||||
|
m_getFormattedValue for getting a non-multilingual value (ie, in
|
||||||
|
most cases). Else, this method returns a formatted value for the
|
||||||
|
p_language-specific part of a multilingual value.'''
|
||||||
if Field.isEmptyValue(self, value): return ''
|
if Field.isEmptyValue(self, value): return ''
|
||||||
res = value
|
res = value
|
||||||
if self.isSelect:
|
if self.isSelect:
|
||||||
|
@ -594,7 +602,7 @@ class String(Field):
|
||||||
res = t('%s_list_%s' % (self.labelId, value))
|
res = t('%s_list_%s' % (self.labelId, value))
|
||||||
elif (self.format == String.XHTML) and showChanges:
|
elif (self.format == String.XHTML) and showChanges:
|
||||||
# Compute the successive changes that occurred on p_value.
|
# Compute the successive changes that occurred on p_value.
|
||||||
res = self.getDiffValue(obj, res)
|
res = self.getDiffValue(obj, res, language)
|
||||||
# If value starts with a carriage return, add a space; else, it will
|
# If value starts with a carriage return, add a space; else, it will
|
||||||
# be ignored.
|
# be ignored.
|
||||||
if isinstance(res, basestring) and \
|
if isinstance(res, basestring) and \
|
||||||
|
@ -609,7 +617,8 @@ class String(Field):
|
||||||
if not value: return value
|
if not value: return value
|
||||||
res = {}
|
res = {}
|
||||||
for lg in self.languages:
|
for lg in self.languages:
|
||||||
res[lg]=self.getUnilingualFormattedValue(obj,value[lg],showChanges)
|
res[lg] = self.getUnilingualFormattedValue(obj, value[lg],
|
||||||
|
showChanges, lg)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def extractText(self, value):
|
def extractText(self, value):
|
||||||
|
@ -773,8 +782,8 @@ class String(Field):
|
||||||
# Apply transform if required
|
# Apply transform if required
|
||||||
if isString and not isEmpty and (self.transform != 'none'):
|
if isString and not isEmpty and (self.transform != 'none'):
|
||||||
value = self.applyTransform(value)
|
value = self.applyTransform(value)
|
||||||
# Clean XHTML if format is XHTML
|
# Clean XHTML strings
|
||||||
if (self.format == String.XHTML) and not isEmpty:
|
if not isEmpty and (self.format == String.XHTML):
|
||||||
# When image upload is allowed, ckeditor inserts some "style" attrs
|
# When image upload is allowed, ckeditor inserts some "style" attrs
|
||||||
# (ie for image size when images are resized). So in this case we
|
# (ie for image size when images are resized). So in this case we
|
||||||
# can't remove style-related information.
|
# can't remove style-related information.
|
||||||
|
@ -784,6 +793,9 @@ class String(Field):
|
||||||
# Errors while parsing p_value can't prevent the user from
|
# Errors while parsing p_value can't prevent the user from
|
||||||
# storing it.
|
# storing it.
|
||||||
pass
|
pass
|
||||||
|
# Clean TEXT strings
|
||||||
|
if not isEmpty and (self.format == String.TEXT):
|
||||||
|
value = value.replace('\r', '')
|
||||||
# Truncate the result if longer than self.maxChars
|
# Truncate the result if longer than self.maxChars
|
||||||
if isString and self.maxChars and (len(value) > self.maxChars):
|
if isString and self.maxChars and (len(value) > self.maxChars):
|
||||||
value = value[:self.maxChars]
|
value = value[:self.maxChars]
|
||||||
|
@ -819,6 +831,11 @@ class String(Field):
|
||||||
rq = obj.REQUEST
|
rq = obj.REQUEST
|
||||||
if rq.get('cancel') == 'True': return
|
if rq.get('cancel') == 'True': return
|
||||||
requestValue = rq['fieldContent']
|
requestValue = rq['fieldContent']
|
||||||
|
# Remember previous value if the field is historized.
|
||||||
|
previousData = obj.rememberPreviousData([self])
|
||||||
|
# We take a copy because the data is mutable (ie, a dict).
|
||||||
|
if previousData:
|
||||||
|
previousData[self.name] = previousData[self.name].copy()
|
||||||
if self.isMultilingual():
|
if self.isMultilingual():
|
||||||
# We get a partial value, for one language only.
|
# We get a partial value, for one language only.
|
||||||
language = rq['languageOnly']
|
language = rq['languageOnly']
|
||||||
|
@ -828,6 +845,12 @@ class String(Field):
|
||||||
else:
|
else:
|
||||||
self.store(obj, self.getStorableValue(requestValue))
|
self.store(obj, self.getStorableValue(requestValue))
|
||||||
part = ''
|
part = ''
|
||||||
|
# Update the object history when relevant
|
||||||
|
if previousData: obj.historizeData(previousData)
|
||||||
|
# Update obj's last modification date
|
||||||
|
from DateTime import DateTime
|
||||||
|
obj.modified = DateTime()
|
||||||
|
obj.reindex()
|
||||||
obj.log('Ajax-edited %s%s on %s.' % (self.name, part, obj.id))
|
obj.log('Ajax-edited %s%s on %s.' % (self.name, part, obj.id))
|
||||||
|
|
||||||
def getIndexType(self):
|
def getIndexType(self):
|
||||||
|
|
|
@ -152,10 +152,12 @@ class ToolMixin(BaseMixin):
|
||||||
'''Returns the supported languages. First one is the default.'''
|
'''Returns the supported languages. First one is the default.'''
|
||||||
return self.getProductConfig(True).languages
|
return self.getProductConfig(True).languages
|
||||||
|
|
||||||
def getLanguageName(self, code):
|
def getLanguageName(self, code, lowerize=False):
|
||||||
'''Gets the language name (in this language) from a 2-chars language
|
'''Gets the language name (in this language) from a 2-chars language
|
||||||
p_code.'''
|
p_code.'''
|
||||||
return languages.get(code)[2]
|
res = languages.get(code)[2]
|
||||||
|
if not lowerize: return res
|
||||||
|
return res.lower()
|
||||||
|
|
||||||
def changeLanguage(self):
|
def changeLanguage(self):
|
||||||
'''Sets the language cookie with the new desired language code that is
|
'''Sets the language cookie with the new desired language code that is
|
||||||
|
|
|
@ -336,7 +336,7 @@ class BaseMixin:
|
||||||
value.'''
|
value.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
for field in self.getAppyTypes('edit', rq.form.get('page')):
|
for field in self.getAppyTypes('edit', rq.form.get('page')):
|
||||||
if not field.validable: continue
|
if not field.validable or not field.isClientVisible(self): continue
|
||||||
value = field.getRequestValue(rq)
|
value = field.getRequestValue(rq)
|
||||||
message = field.validate(self, value)
|
message = field.validate(self, value)
|
||||||
if message:
|
if message:
|
||||||
|
@ -592,14 +592,8 @@ class BaseMixin:
|
||||||
~{s_fieldName: previousFieldValue}~'''
|
~{s_fieldName: previousFieldValue}~'''
|
||||||
res = {}
|
res = {}
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if not field.historized: continue
|
if not field.getAttribute(self, 'historized'): continue
|
||||||
# appyType.historized can be a method or a boolean.
|
res[field.name] = field.getValue(self)
|
||||||
if callable(field.historized):
|
|
||||||
historized = field.callMethod(self, field.historized)
|
|
||||||
else:
|
|
||||||
historized = field.historized
|
|
||||||
if historized:
|
|
||||||
res[field.name] = field.getValue(self)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def addHistoryEvent(self, action, **kw):
|
def addHistoryEvent(self, action, **kw):
|
||||||
|
@ -622,15 +616,29 @@ class BaseMixin:
|
||||||
a field. The method is also called by m_historizeData below, that
|
a field. The method is also called by m_historizeData below, that
|
||||||
performs "automatic" recording when a HTTP form is uploaded. Field
|
performs "automatic" recording when a HTTP form is uploaded. Field
|
||||||
changes for which the previous value was empty are not recorded into
|
changes for which the previous value was empty are not recorded into
|
||||||
the history if p_notForPreviouslyEmptyValues is True.'''
|
the history if p_notForPreviouslyEmptyValues is True.
|
||||||
|
|
||||||
|
For a multilingual string field, p_changes can contain a key for
|
||||||
|
every language, of the form <field name>-<language>.'''
|
||||||
# Add to the p_changes dict the field labels
|
# Add to the p_changes dict the field labels
|
||||||
for fieldName in changes.keys():
|
for name in changes.keys():
|
||||||
appyType = self.getAppyType(fieldName)
|
# "name" can contain the language for multilingual fields.
|
||||||
if notForPreviouslyEmptyValues and \
|
if '-' in name:
|
||||||
appyType.isEmptyValue(changes[fieldName], self):
|
fieldName, lg = name.split('-')
|
||||||
del changes[fieldName]
|
|
||||||
else:
|
else:
|
||||||
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
fieldName = name
|
||||||
|
lg = None
|
||||||
|
field = self.getAppyType(fieldName)
|
||||||
|
if notForPreviouslyEmptyValues:
|
||||||
|
# Check if the previous field value was empty
|
||||||
|
if lg:
|
||||||
|
isEmpty = not changes[name] or not changes[name].get(lg)
|
||||||
|
else:
|
||||||
|
isEmpty = field.isEmptyValue(changes[name], self)
|
||||||
|
if isEmpty:
|
||||||
|
del changes[name]
|
||||||
|
else:
|
||||||
|
changes[name] = (changes[name], field.labelId)
|
||||||
# Add an event in the history
|
# Add an event in the history
|
||||||
self.addHistoryEvent('_datachange_', changes=changes)
|
self.addHistoryEvent('_datachange_', changes=changes)
|
||||||
|
|
||||||
|
@ -640,20 +648,30 @@ class BaseMixin:
|
||||||
historized fields, while p_self already contains the (potentially)
|
historized fields, while p_self already contains the (potentially)
|
||||||
modified values.'''
|
modified values.'''
|
||||||
# Remove from previousData all values that were not changed
|
# Remove from previousData all values that were not changed
|
||||||
for field in previousData.keys():
|
for name in previousData.keys():
|
||||||
prev = previousData[field]
|
field = self.getAppyType(name)
|
||||||
appyType = self.getAppyType(field)
|
prev = previousData[name]
|
||||||
curr = appyType.getValue(self)
|
curr = field.getValue(self)
|
||||||
try:
|
try:
|
||||||
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
||||||
((prev == '') and (curr == None)):
|
((prev == '') and (curr == None)):
|
||||||
del previousData[field]
|
del previousData[name]
|
||||||
|
continue
|
||||||
except UnicodeDecodeError, ude:
|
except UnicodeDecodeError, ude:
|
||||||
# The string comparisons above may imply silent encoding-related
|
# The string comparisons above may imply silent encoding-related
|
||||||
# conversions that may produce this exception.
|
# conversions that may produce this exception.
|
||||||
pass
|
continue
|
||||||
if (appyType.type == 'Ref') and (field in previousData):
|
# In some cases the old value must be formatted.
|
||||||
previousData[field] = [r.title for r in previousData[field]]
|
if field.type == 'Ref':
|
||||||
|
previousData[name] = [r.title for r in previousData[name]]
|
||||||
|
elif (field.type == 'String') and field.isMultilingual():
|
||||||
|
# Consider every language-specific value as a first-class value
|
||||||
|
del previousData[name]
|
||||||
|
for lg in field.languages:
|
||||||
|
lgPrev = prev and prev.get(lg) or None
|
||||||
|
lgCurr = curr and curr.get(lg) or None
|
||||||
|
if lgPrev == lgCurr: continue
|
||||||
|
previousData['%s-%s' % (name, lg)] = lgPrev
|
||||||
if previousData:
|
if previousData:
|
||||||
self.addDataChange(previousData)
|
self.addDataChange(previousData)
|
||||||
|
|
||||||
|
@ -1020,56 +1038,73 @@ class BaseMixin:
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def findNewValue(self, field, history, stopIndex):
|
def findNewValue(self, field, language, history, stopIndex):
|
||||||
'''This function tries to find a more recent version of value of p_field
|
'''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
|
on p_self. In the case of a multilingual field, p_language is
|
||||||
it does not find it there, it returns the current value on p_obj.'''
|
specified. The method first tries to find it in
|
||||||
|
history[:stopIndex+1]. If it does not find it there, it returns the
|
||||||
|
current value on p_obj.'''
|
||||||
i = stopIndex + 1
|
i = stopIndex + 1
|
||||||
|
name = language and ('%s-%s' % (field.name, language)) or field.name
|
||||||
while (i-1) >= 0:
|
while (i-1) >= 0:
|
||||||
i -= 1
|
i -= 1
|
||||||
if history[i]['action'] != '_datachange_': continue
|
if history[i]['action'] != '_datachange_': continue
|
||||||
if field.name not in history[i]['changes']: continue
|
if name not in history[i]['changes']: continue
|
||||||
# We have found it!
|
# We have found it!
|
||||||
return history[i]['changes'][field.name][0] or ''
|
return history[i]['changes'][name][0] or ''
|
||||||
return field.getValue(self) or ''
|
# A most recent version was not found in the history: return the current
|
||||||
|
# field value.
|
||||||
|
val = field.getValue(self)
|
||||||
|
if not language: return val or ''
|
||||||
|
return val and val.get(language) or ''
|
||||||
|
|
||||||
def getHistoryTexts(self, event):
|
def getHistoryTexts(self, event):
|
||||||
'''Returns a tuple (insertText, deleteText) containing texts to show on,
|
'''Returns a tuple (insertText, deleteText) containing texts to show on,
|
||||||
respectively, inserted and deleted chunks of text in a XHTML diff.'''
|
respectively, inserted and deleted chunks of text in a XHTML diff.'''
|
||||||
tool = self.getTool()
|
tool = self.getTool()
|
||||||
userName = tool.getUserName(event['actor'])
|
mapping = {'userName': tool.getUserName(event['actor'])}
|
||||||
mapping = {'userName': userName.decode('utf-8')}
|
|
||||||
res = []
|
res = []
|
||||||
for type in ('insert', 'delete'):
|
for type in ('insert', 'delete'):
|
||||||
msg = self.translate('history_%s' % type, mapping=mapping)
|
msg = self.translate('history_%s' % type, mapping=mapping)
|
||||||
date = tool.formatDate(event['time'], withHour=True)
|
date = tool.formatDate(event['time'], withHour=True)
|
||||||
msg = '%s: %s' % (date, msg)
|
msg = '%s: %s' % (date, msg)
|
||||||
res.append(msg.encode('utf-8'))
|
res.append(msg)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def hasHistory(self, fieldName=None):
|
def hasHistory(self, name=None):
|
||||||
'''Has this object an history? If p_fieldName is specified, the question
|
'''Has this object an history? If p_name is specified, the question
|
||||||
becomes: has this object an history for field p_fieldName?'''
|
becomes: has this object an history for field p_name?'''
|
||||||
if hasattr(self.aq_base, 'workflow_history') and self.workflow_history:
|
if not hasattr(self.aq_base, 'workflow_history') or \
|
||||||
history = self.workflow_history.values()[0]
|
not self.workflow_history: return
|
||||||
if not fieldName:
|
history = self.workflow_history['appy']
|
||||||
for event in history:
|
if not name:
|
||||||
if event['action'] and (event['comments'] != '_invisible_'):
|
for event in history:
|
||||||
|
if event['action'] and (event['comments'] != '_invisible_'):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
field = self.getAppyType(name)
|
||||||
|
multilingual = (field.type == 'String') and field.isMultilingual()
|
||||||
|
for event in history:
|
||||||
|
if event['action'] != '_datachange_': continue
|
||||||
|
# Is there a value present for this field in this data change?
|
||||||
|
if not multilingual:
|
||||||
|
if (name in event['changes']) and \
|
||||||
|
(event['changes'][name][0]):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
for event in history:
|
# At least one language-specific value must be present
|
||||||
if (event['action'] == '_datachange_') and \
|
for lg in field.languages:
|
||||||
(fieldName in event['changes']) and \
|
lgName = '%s-%s' % (field.name, lg)
|
||||||
event['changes'][fieldName][0]: return True
|
if (lgName in event['changes']) and \
|
||||||
|
event['changes'][lgName][0]:
|
||||||
|
return True
|
||||||
|
|
||||||
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False,
|
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False,
|
||||||
batchSize=5):
|
batchSize=5):
|
||||||
'''Returns the history for this object, sorted in reverse order (most
|
'''Returns a copy of the history for this object, sorted in p_reverse
|
||||||
recent change first) if p_reverse is True.'''
|
order if specified (most recent change first), whose invisible events
|
||||||
# Get a copy of the history, reversed if needed, whose invisible events
|
have been removed if p_includeInvisible is True.'''
|
||||||
# have been removed if needed.
|
history = list(self.workflow_history['appy'][1:])
|
||||||
key = self.workflow_history.keys()[0]
|
|
||||||
history = list(self.workflow_history[key][1:])
|
|
||||||
if not includeInvisible:
|
if not includeInvisible:
|
||||||
history = [e for e in history if e['comments'] != '_invisible_']
|
history = [e for e in history if e['comments'] != '_invisible_']
|
||||||
if reverse: history.reverse()
|
if reverse: history.reverse()
|
||||||
|
@ -1088,8 +1123,15 @@ class BaseMixin:
|
||||||
event = history[i].copy()
|
event = history[i].copy()
|
||||||
event['changes'] = {}
|
event['changes'] = {}
|
||||||
for name, oldValue in history[i]['changes'].iteritems():
|
for name, oldValue in history[i]['changes'].iteritems():
|
||||||
# oldValue is a tuple (value, fieldName).
|
# "name" can specify a language-specific part in a
|
||||||
field = self.getAppyType(name)
|
# multilingual field. "oldValue" is a tuple
|
||||||
|
# (value, fieldName).
|
||||||
|
if '-' in name:
|
||||||
|
fieldName, lg = name.split('-')
|
||||||
|
else:
|
||||||
|
fieldName = name
|
||||||
|
lg = None
|
||||||
|
field = self.getAppyType(fieldName)
|
||||||
# Field 'name' may not exist, if the history has been
|
# Field 'name' may not exist, if the history has been
|
||||||
# transferred from another site. In this case we can't show
|
# transferred from another site. In this case we can't show
|
||||||
# this data change.
|
# this data change.
|
||||||
|
@ -1099,21 +1141,24 @@ class BaseMixin:
|
||||||
# For rich text fields, instead of simply showing the
|
# For rich text fields, instead of simply showing the
|
||||||
# previous value, we propose a diff with the next
|
# previous value, we propose a diff with the next
|
||||||
# version, excepted if the previous value is empty.
|
# version, excepted if the previous value is empty.
|
||||||
if field.isEmptyValue(oldValue[0]):
|
if lg: isEmpty = not oldValue[0]
|
||||||
|
else: isEmpty = field.isEmptyValue(oldValue[0])
|
||||||
|
if isEmpty:
|
||||||
val = '-'
|
val = '-'
|
||||||
else:
|
else:
|
||||||
newValue = self.findNewValue(field, history, i-1)
|
newValue= self.findNewValue(field, lg, history, i-1)
|
||||||
# Compute the diff between oldValue and newValue
|
# Compute the diff between oldValue and newValue
|
||||||
iMsg, dMsg = self.getHistoryTexts(event)
|
iMsg, dMsg = self.getHistoryTexts(event)
|
||||||
comparator= HtmlDiff(oldValue[0],newValue,iMsg,dMsg)
|
comparator= HtmlDiff(oldValue[0],newValue,iMsg,dMsg)
|
||||||
val = comparator.get()
|
val = comparator.get()
|
||||||
event['changes'][name] = (val, oldValue[1])
|
|
||||||
else:
|
else:
|
||||||
val = field.getFormattedValue(self, oldValue[0]) or '-'
|
fmt = lg and 'getUnilingualFormattedValue' or \
|
||||||
|
'getFormattedValue'
|
||||||
|
val = getattr(field, fmt)(self, oldValue[0]) or '-'
|
||||||
if isinstance(val, list) or isinstance(val, tuple):
|
if isinstance(val, list) or isinstance(val, tuple):
|
||||||
val = '<ul>%s</ul>' % \
|
val = '<ul>%s</ul>' % \
|
||||||
''.join(['<li>%s</li>' % v for v in val])
|
''.join(['<li>%s</li>' % v for v in val])
|
||||||
event['changes'][name] = (val, oldValue[1])
|
event['changes'][name] = (val, oldValue[1])
|
||||||
else:
|
else:
|
||||||
event = history[i]
|
event = history[i]
|
||||||
res.append(event)
|
res.append(event)
|
||||||
|
|
|
@ -58,7 +58,7 @@ img { border: 0; vertical-align: middle }
|
||||||
.userStrip a:visited { color: #e7e7e7 }
|
.userStrip a:visited { color: #e7e7e7 }
|
||||||
.breadcrumb { font-size: 11pt; padding-bottom: 6px }
|
.breadcrumb { font-size: 11pt; padding-bottom: 6px }
|
||||||
.login { margin: 3px; color: black }
|
.login { margin: 3px; color: black }
|
||||||
input.button { color: #666666; height: 20px; margin-bottom: 5px;
|
input.button { color: #666666; height: 20px; margin-bottom: 5px; margin-top:2px;
|
||||||
cursor:pointer; font-size: 90%; padding-left: 10px;
|
cursor:pointer; font-size: 90%; padding-left: 10px;
|
||||||
background-color: white; background-repeat: no-repeat;
|
background-color: white; background-repeat: no-repeat;
|
||||||
background-position: 8px 25%; box-shadow: 2px 2px 2px #888888}
|
background-position: 8px 25%; box-shadow: 2px 2px 2px #888888}
|
||||||
|
@ -75,7 +75,8 @@ input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;
|
||||||
.message { position: absolute; top: -40px; left: 50%; font-size: 90%;
|
.message { position: absolute; top: -40px; left: 50%; font-size: 90%;
|
||||||
width: 600px; border: 1px #F0C36D solid; padding: 6px;
|
width: 600px; border: 1px #F0C36D solid; padding: 6px;
|
||||||
background-color: #F9EDBE; text-align: center; margin-left: -300px;
|
background-color: #F9EDBE; text-align: center; margin-left: -300px;
|
||||||
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9 }
|
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;
|
||||||
|
z-index: 10 }
|
||||||
.messagePopup { width: 80%; top: 0; left: 0; margin-left: 10px }
|
.messagePopup { width: 80%; top: 0; left: 0; margin-left: 10px }
|
||||||
.focus { font-size: 90%; margin: 7px 0 7px 0; padding: 7px;
|
.focus { font-size: 90%; margin: 7px 0 7px 0; padding: 7px;
|
||||||
background-color: #d7dee4; border-radius: 2px 2px 2px 2px;
|
background-color: #d7dee4; border-radius: 2px 2px 2px 2px;
|
||||||
|
|
|
@ -375,8 +375,11 @@ class AbstractWrapper(object):
|
||||||
<th align=":dleft" width="70%">:_('previous_value')</th>
|
<th align=":dleft" width="70%">:_('previous_value')</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr for="change in event['changes'].items()" valign="top"
|
<tr for="change in event['changes'].items()" valign="top"
|
||||||
var2="field=zobj.getAppyType(change[0])">
|
var2="elems=change[0].split('-');
|
||||||
<td>::_(field.labelId)</td>
|
field=zobj.getAppyType(elems[0]);
|
||||||
|
lg=(len(elems) == 2) and elems[1] or ''">
|
||||||
|
<td><x>::_(field.labelId)</x>
|
||||||
|
<x if="lg">:' (%s)' % ztool.getLanguageName(lg, True)</x></td>
|
||||||
<td>::change[1][0]</td>
|
<td>::change[1][0]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
Loading…
Reference in a new issue