[gen] More work on multilingual string fields.
This commit is contained in:
parent
4131ba899e
commit
7c2d4c1b7a
|
@ -529,20 +529,19 @@ class Field:
|
||||||
|
|
||||||
If p_forSearch is True, it will return a "string" version of the
|
If p_forSearch is True, it will return a "string" version of the
|
||||||
index value suitable for a global search.'''
|
index value suitable for a global search.'''
|
||||||
value = self.getValue(obj)
|
res = self.getValue(obj)
|
||||||
if forSearch and (value != None):
|
# Zope catalog does not like unicode strings.
|
||||||
if isinstance(value, unicode):
|
if isinstance(res, unicode): res = res.encode('utf-8')
|
||||||
res = value.encode('utf-8')
|
if forSearch and (res != None):
|
||||||
elif type(value) in sutils.sequenceTypes:
|
if type(res) in sutils.sequenceTypes:
|
||||||
res = []
|
vals = []
|
||||||
for v in value:
|
for v in res:
|
||||||
if isinstance(v, unicode): res.append(v.encode('utf-8'))
|
if isinstance(v, unicode): vals.append(v.encode('utf-8'))
|
||||||
else: res.append(str(v))
|
else: vals.append(str(v))
|
||||||
res = ' '.join(res)
|
res = ' '.join(vals)
|
||||||
else:
|
else:
|
||||||
res = str(value)
|
res = str(res)
|
||||||
return res
|
return res
|
||||||
return value
|
|
||||||
|
|
||||||
def valueIsInRequest(self, request, name):
|
def valueIsInRequest(self, request, name):
|
||||||
'''Is there a value corresponding to this field in the request? p_name
|
'''Is there a value corresponding to this field in the request? p_name
|
||||||
|
|
187
fields/string.py
187
fields/string.py
|
@ -84,40 +84,87 @@ class String(Field):
|
||||||
PASSWORD = 3
|
PASSWORD = 3
|
||||||
CAPTCHA = 4
|
CAPTCHA = 4
|
||||||
|
|
||||||
|
# Default ways to render multingual fields
|
||||||
|
defaultLanguagesLayouts = {
|
||||||
|
LINE: {'edit': 'vertical', 'view': 'vertical'},
|
||||||
|
TEXT: {'edit': 'horizontal', 'view': 'vertical'},
|
||||||
|
XHTML: {'edit': 'horizontal', 'view': 'horizontal'},
|
||||||
|
}
|
||||||
|
|
||||||
|
# pxView part for formats String.LINE (but that are not selections) and
|
||||||
|
# String.PASSWORD (a fake view for String.PASSWORD and no view at all for
|
||||||
|
# String.CAPTCHA).
|
||||||
|
pxViewLine = Px('''
|
||||||
|
<span if="not value" class="smaller">-</span>
|
||||||
|
<x if="value">
|
||||||
|
<!-- A password -->
|
||||||
|
<x if="fmt == 3">********</x>
|
||||||
|
<!-- A URL -->
|
||||||
|
<a if="(fmt != 3) and isUrl" target="_blank" href=":value">:value</a>
|
||||||
|
<!-- Any other value -->
|
||||||
|
<x if="(fmt != 3) and not isUrl">::value</x>
|
||||||
|
</x>''')
|
||||||
|
|
||||||
|
# pxView part for format String.TEXT.
|
||||||
|
pxViewText = Px('''
|
||||||
|
<span if="not value" class="smaller">-</span>
|
||||||
|
<x if="value">::zobj.formatText(value, format='html')</x>''')
|
||||||
|
|
||||||
|
# pxView part for format String.XHTML.
|
||||||
|
pxViewRich = Px('''
|
||||||
|
<div if="not mayAjaxEdit" class="xhtml">::value or '-'</div>
|
||||||
|
<div if="mayAjaxEdit" class="xhtml" contenteditable="true"
|
||||||
|
id=":'%s_%s_ck' % (zobj.id, name)">::value or '-'</div>
|
||||||
|
<script if="mayAjaxEdit">::field.getJsInlineInit(zobj,None)</script>''')
|
||||||
|
|
||||||
|
# PX displaying the language code and name besides the part of the
|
||||||
|
# multilingual field storing content in this language.
|
||||||
|
pxLanguage = Px('''
|
||||||
|
<td style=":'padding-top:%dpx' % lgTop" width="25px">
|
||||||
|
<span class="language help"
|
||||||
|
title=":ztool.getLanguageName(lg)">:lg.upper()</span>
|
||||||
|
</td>''')
|
||||||
|
|
||||||
|
pxMultilingual = Px('''
|
||||||
|
<!-- Horizontally-layouted multilingual field -->
|
||||||
|
<table if="mLayout == 'horizontal'" width="100%">
|
||||||
|
<tr valign="top"><x for="lg in field.languages"><x>:field.pxLanguage</x>
|
||||||
|
<td var="requestValue=requestValue[lg]|None;
|
||||||
|
value=value[lg]|emptyDefault">:field.subPx[layoutType][fmt]</td>
|
||||||
|
</x></tr></table>
|
||||||
|
|
||||||
|
<!-- Vertically-layouted multilingual field -->
|
||||||
|
<table if="mLayout == 'vertical'">
|
||||||
|
<tr valign="top" height="20px" for="lg in field.languages">
|
||||||
|
<x>:field.pxLanguage</x>
|
||||||
|
<td var="requestValue=requestValue[lg]|None;
|
||||||
|
value=value[lg]|emptyDefault">:field.subPx[layoutType][fmt]</td>
|
||||||
|
</tr></table>''')
|
||||||
|
|
||||||
pxView = Px('''
|
pxView = Px('''
|
||||||
<x var="fmt=field.format; isUrl=field.isUrl;
|
<x var="fmt=field.format; isUrl=field.isUrl;
|
||||||
|
multilingual=field.isMultilingual();
|
||||||
|
mLayout=multilingual and field.getLanguagesLayout('view');
|
||||||
mayAjaxEdit=not showChanges and field.inlineEdit and \
|
mayAjaxEdit=not showChanges and field.inlineEdit and \
|
||||||
zobj.mayEdit(field.writePermission)">
|
zobj.mayEdit(field.writePermission)">
|
||||||
<x if="fmt in (0, 3)">
|
<x if="field.isSelect">
|
||||||
<ul if="value and isMultiple">
|
<span if="not value" class="smaller">-</span>
|
||||||
<li for="sv in value"><i>::sv</i></li>
|
<x if="value and not isMultiple">::value</x>
|
||||||
</ul>
|
<ul if="value and isMultiple"><li for="sv in value"><i>::sv</i></li></ul>
|
||||||
<x if="value and not isMultiple">
|
|
||||||
<!-- A password -->
|
|
||||||
<x if="fmt == 3">********</x>
|
|
||||||
<!-- A URL -->
|
|
||||||
<a if="(fmt != 3) and isUrl" target="_blank" href=":value">:value</a>
|
|
||||||
<!-- Any other value -->
|
|
||||||
<x if="(fmt != 3) and not isUrl">::value</x>
|
|
||||||
</x>
|
|
||||||
</x>
|
</x>
|
||||||
<!-- Unformatted text -->
|
<!-- Any other unilingual field -->
|
||||||
<x if="value and (fmt == 1)">::zobj.formatText(value, format='html')</x>
|
<x if="not field.isSelect and not multilingual"
|
||||||
<!-- XHTML text -->
|
var2="lg=None">:field.subPx['view'][fmt]</x>
|
||||||
<x if="fmt == 2">
|
<!-- Any other multilingual field -->
|
||||||
<div if="not mayAjaxEdit" class="xhtml">::value or '-'</div>
|
<x if="not field.isSelect and multilingual"
|
||||||
<div if="mayAjaxEdit" class="xhtml" contenteditable="true"
|
var2="lgTop=1; emptyDefault='-'">:field.pxMultilingual</x>
|
||||||
id=":'%s_%s_ck' % (zobj.id, name)">::value or '-'</div>
|
<!-- If this field is a master field -->
|
||||||
<script if="mayAjaxEdit">::field.getJsInlineInit(zobj,None)</script>
|
|
||||||
</x>
|
|
||||||
<span if="not value and (fmt != 2)" class="smaller">-</span>
|
|
||||||
<input type="hidden" if="masterCss" class=":masterCss" value=":rawValue"
|
<input type="hidden" if="masterCss" class=":masterCss" value=":rawValue"
|
||||||
name=":name" id=":name"/>
|
name=":name" id=":name"/></x>''')
|
||||||
</x>''')
|
|
||||||
|
|
||||||
# pxEdit part for formats String.LINE (but that are not selections),
|
# pxEdit part for formats String.LINE (but that are not selections),
|
||||||
# String.PASSWORD and String.CAPTCHA.
|
# String.PASSWORD and String.CAPTCHA.
|
||||||
pxEditText = Px('''
|
pxEditLine = Px('''
|
||||||
<input var="inputId=not lg and name or '%s_%s' % (name, lg);
|
<input var="inputId=not lg and name or '%s_%s' % (name, lg);
|
||||||
placeholder=field.getAttribute(obj, 'placeholder') or ''"
|
placeholder=field.getAttribute(obj, 'placeholder') or ''"
|
||||||
id=":inputId" name=":inputId" size=":field.width"
|
id=":inputId" name=":inputId" size=":field.width"
|
||||||
|
@ -141,12 +188,10 @@ class String(Field):
|
||||||
<script if="fmt == 2"
|
<script if="fmt == 2"
|
||||||
type="text/javascript">::field.getJsInit(zobj, lg)</script>''')
|
type="text/javascript">::field.getJsInit(zobj, lg)</script>''')
|
||||||
|
|
||||||
# Mapping formats -> PXs defining the edit widgets.
|
|
||||||
editPx = {LINE:pxEditText, TEXT:pxEditTextArea, XHTML:pxEditTextArea,
|
|
||||||
PASSWORD:pxEditText, CAPTCHA:pxEditText}
|
|
||||||
pxEdit = Px('''
|
pxEdit = Px('''
|
||||||
<x var="fmt=field.format;
|
<x var="fmt=field.format;
|
||||||
multilingual=field.isMultilingual()">
|
multilingual=field.isMultilingual();
|
||||||
|
mLayout=multilingual and field.getLanguagesLayout('edit')">
|
||||||
<select if="field.isSelect"
|
<select if="field.isSelect"
|
||||||
var2="possibleValues=field.getPossibleValues(zobj, \
|
var2="possibleValues=field.getPossibleValues(zobj, \
|
||||||
withTranslations=True, withBlankValue=True)"
|
withTranslations=True, withBlankValue=True)"
|
||||||
|
@ -158,22 +203,14 @@ class String(Field):
|
||||||
selected=":field.isSelected(zobj, name, val[0], rawValue)"
|
selected=":field.isSelected(zobj, name, val[0], rawValue)"
|
||||||
title=":val[1]">:ztool.truncateValue(val[1],field.width)</option>
|
title=":val[1]">:ztool.truncateValue(val[1],field.width)</option>
|
||||||
</select>
|
</select>
|
||||||
<!-- Any other unilingual field. -->
|
<!-- Any other unilingual field -->
|
||||||
<x if="not field.isSelect and not multilingual"
|
<x if="not field.isSelect and not multilingual"
|
||||||
var2="lg=None">:field.editPx[fmt]</x>
|
var2="lg=None">:field.subPx['edit'][fmt]</x>
|
||||||
<!-- Any other multilingual field. -->
|
<!-- Any other multilingual field -->
|
||||||
<table if="not field.isSelect and multilingual" width="100%">
|
<x if="not field.isSelect and multilingual"
|
||||||
<tr valign="top">
|
var2="lgTop=(fmt!=2) and 3 or 1;
|
||||||
<x for="lg in field.languages">
|
emptyDefault=''">:field.pxMultilingual</x>
|
||||||
<td style=":(fmt==2) and 'padding-top:1px' or 'padding-top:3px'"
|
</x>''')
|
||||||
width="25px">
|
|
||||||
<span class="language help"
|
|
||||||
title=":ztool.getLanguageName(lg)">:lg.upper()</span></td>
|
|
||||||
<td var="requestValue=requestValue[lg];
|
|
||||||
value=value[lg]|''">:field.editPx[fmt]</td>
|
|
||||||
</x>
|
|
||||||
</tr>
|
|
||||||
</table></x>''')
|
|
||||||
|
|
||||||
pxCell = Px('''
|
pxCell = Px('''
|
||||||
<x var="multipleValues=value and isMultiple">
|
<x var="multipleValues=value and isMultiple">
|
||||||
|
@ -212,6 +249,14 @@ class String(Field):
|
||||||
</select>
|
</select>
|
||||||
</x><br/>''')
|
</x><br/>''')
|
||||||
|
|
||||||
|
# Sub-PX to use according to String format.
|
||||||
|
subPx = {
|
||||||
|
'edit': {LINE:pxEditLine, TEXT:pxEditTextArea, XHTML:pxEditTextArea,
|
||||||
|
PASSWORD:pxEditLine, CAPTCHA:pxEditLine},
|
||||||
|
'view': {LINE:pxViewLine, TEXT:pxViewText, XHTML:pxViewRich,
|
||||||
|
PASSWORD:pxViewLine, CAPTCHA:pxViewLine}
|
||||||
|
}
|
||||||
|
|
||||||
# Some predefined functions that may also be used as validators
|
# Some predefined functions that may also be used as validators
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _MODULO_97(obj, value, complement=False):
|
def _MODULO_97(obj, value, complement=False):
|
||||||
|
@ -324,7 +369,8 @@ class String(Field):
|
||||||
label=None, sdefault='', scolspan=1, swidth=None, sheight=None,
|
label=None, sdefault='', scolspan=1, swidth=None, sheight=None,
|
||||||
persist=True, transform='none', placeholder=None,
|
persist=True, transform='none', placeholder=None,
|
||||||
styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
|
styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
|
||||||
spellcheck=False, languages=('en',), inlineEdit=False):
|
spellcheck=False, languages=('en',), languagesLayouts=None,
|
||||||
|
inlineEdit=False):
|
||||||
# According to format, the widget will be different: input field,
|
# According to format, the widget will be different: input field,
|
||||||
# textarea, inline editor... Note that there can be only one String
|
# textarea, inline editor... Note that there can be only one String
|
||||||
# field of format CAPTCHA by page, because the captcha challenge is
|
# field of format CAPTCHA by page, because the captcha challenge is
|
||||||
|
@ -343,6 +389,11 @@ class String(Field):
|
||||||
# field content in all the supported languages. The field is also used
|
# field content in all the supported languages. The field is also used
|
||||||
# by the CK spell checker.
|
# by the CK spell checker.
|
||||||
self.languages = languages
|
self.languages = languages
|
||||||
|
# When content exists in several languages, how to render them? Either
|
||||||
|
# horizontally (one below the other), or vertically (one besides the
|
||||||
|
# other). Specify here a dict whose keys are layouts ("edit", "view")
|
||||||
|
# and whose values are either "horizontal" or "vertical".
|
||||||
|
self.languagesLayouts = languagesLayouts
|
||||||
# When format in XHTML, can the field be inline-edited (ckeditor)?
|
# When format in XHTML, can the field be inline-edited (ckeditor)?
|
||||||
self.inlineEdit = inlineEdit
|
self.inlineEdit = inlineEdit
|
||||||
# The following field has a direct impact on the text entered by the
|
# The following field has a direct impact on the text entered by the
|
||||||
|
@ -392,8 +443,13 @@ class String(Field):
|
||||||
|
|
||||||
def checkParameters(self):
|
def checkParameters(self):
|
||||||
'''Ensures this String is correctly defined.'''
|
'''Ensures this String is correctly defined.'''
|
||||||
if self.isSelect and self.isMultilingual():
|
error = None
|
||||||
raise Exception("A selection field can't be multilingual.")
|
if self.isMultilingual():
|
||||||
|
if self.isSelect:
|
||||||
|
error = "A selection field can't be multilingual."
|
||||||
|
elif self.format in (String.PASSWORD, String.CAPTCHA):
|
||||||
|
error = "A password or captcha field can't be multilingual."
|
||||||
|
if error: raise Exception(error)
|
||||||
|
|
||||||
def isSelection(self):
|
def isSelection(self):
|
||||||
'''Does the validator of this type definition define a list of values
|
'''Does the validator of this type definition define a list of values
|
||||||
|
@ -428,6 +484,13 @@ class String(Field):
|
||||||
elif self.isMultiValued():
|
elif self.isMultiValued():
|
||||||
return {'view': 'l-f', 'edit': 'lrv-f'}
|
return {'view': 'l-f', 'edit': 'lrv-f'}
|
||||||
|
|
||||||
|
def getLanguagesLayout(self, layoutType):
|
||||||
|
'''Gets the way to render a multilingual field on p_layoutType.'''
|
||||||
|
if self.languagesLayouts and (layoutType in self.languagesLayouts):
|
||||||
|
return self.languagesLayouts[layoutType]
|
||||||
|
# Else, return a default value that depends of the format.
|
||||||
|
return String.defaultLanguagesLayouts[self.format][layoutType]
|
||||||
|
|
||||||
def getValue(self, obj):
|
def getValue(self, obj):
|
||||||
# Cheat if this field represents p_obj's state.
|
# Cheat if this field represents p_obj's state.
|
||||||
if self.name == 'state': return obj.State()
|
if self.name == 'state': return obj.State()
|
||||||
|
@ -545,17 +608,30 @@ class String(Field):
|
||||||
res[lg]=self.getUnilingualFormattedValue(obj,value[lg],showChanges)
|
res[lg]=self.getUnilingualFormattedValue(obj,value[lg],showChanges)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def extractText(self, value):
|
||||||
|
'''Extracts pure text from XHTML p_value.'''
|
||||||
|
return XhtmlTextExtractor(raiseOnError=False).parse('<p>%s</p>' % value)
|
||||||
|
|
||||||
emptyStringTuple = ('',)
|
emptyStringTuple = ('',)
|
||||||
emptyValuesCatalogIgnored = (None, '')
|
emptyValuesCatalogIgnored = (None, '')
|
||||||
|
|
||||||
def getIndexValue(self, obj, forSearch=False):
|
def getIndexValue(self, obj, forSearch=False):
|
||||||
'''For indexing purposes, we return only strings, not unicodes.'''
|
'''Pure text must be extracted from rich content; multilingual content
|
||||||
res = Field.getIndexValue(self, obj, forSearch)
|
must be concatenated.'''
|
||||||
if isinstance(res, unicode):
|
print 'INDEX value computing...', self.name, obj.title
|
||||||
res = res.encode('utf-8')
|
isXhtml = self.format == String.XHTML
|
||||||
if res and forSearch and (self.format == String.XHTML):
|
if self.isMultilingual():
|
||||||
# Convert the value to simple text.
|
res = self.getValue(obj)
|
||||||
extractor = XhtmlTextExtractor(raiseOnError=False)
|
if res:
|
||||||
res = extractor.parse('<p>%s</p>' % res)
|
vals = []
|
||||||
|
for v in res.itervalues():
|
||||||
|
if isinstance(v, unicode): v = v.encode('utf-8')
|
||||||
|
if isXhtml: vals.append(self.extractText(v))
|
||||||
|
else: vals.append(v)
|
||||||
|
res = ' '.join(vals)
|
||||||
|
else:
|
||||||
|
res = Field.getIndexValue(self, obj, forSearch)
|
||||||
|
if res and isXhtml: res = self.extractText(res)
|
||||||
# Ugly catalog: if I give an empty tuple as index value, it keeps the
|
# Ugly catalog: if I give an empty tuple as index value, it keeps the
|
||||||
# previous value. If I give him a tuple containing an empty string, it
|
# previous value. If I give him a tuple containing an empty string, it
|
||||||
# is ok.
|
# is ok.
|
||||||
|
@ -563,6 +639,7 @@ class String(Field):
|
||||||
# Ugly catalog: if value is an empty string or None, it keeps the
|
# Ugly catalog: if value is an empty string or None, it keeps the
|
||||||
# previous index value.
|
# previous index value.
|
||||||
if res in self.emptyValuesCatalogIgnored: res = ' '
|
if res in self.emptyValuesCatalogIgnored: res = ' '
|
||||||
|
print 'INDEX value for', self.name, 'is', res
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getPossibleValues(self, obj, withTranslations=False,
|
def getPossibleValues(self, obj, withTranslations=False,
|
||||||
|
|
|
@ -65,7 +65,7 @@ input.button { color: #666666; height: 20px; margin-bottom: 5px;
|
||||||
input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;
|
input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;
|
||||||
margin-bottom: 3px }
|
margin-bottom: 3px }
|
||||||
.fake { background-color: #e6e6e6 !important ; cursor:help !important }
|
.fake { background-color: #e6e6e6 !important ; cursor:help !important }
|
||||||
.xhtml { background-color: white; padding: 6px; font-size: 95% }
|
.xhtml { background-color: white; padding: 4px; font-size: 95% }
|
||||||
.xhtml img { margin-right: 5px }
|
.xhtml img { margin-right: 5px }
|
||||||
.xhtml p { margin: 3px 0 7px 0 }
|
.xhtml p { margin: 3px 0 7px 0 }
|
||||||
.clickable { cursor: pointer }
|
.clickable { cursor: pointer }
|
||||||
|
@ -168,4 +168,4 @@ td.search { padding-top: 8px }
|
||||||
.tab { padding: 0 10px 0 10px; text-align: center; font-size: 90%;
|
.tab { padding: 0 10px 0 10px; text-align: center; font-size: 90%;
|
||||||
font-weight: bold}
|
font-weight: bold}
|
||||||
.language {color: grey; font-size: 7pt; border: grey 1px solid; padding: 2px;
|
.language {color: grey; font-size: 7pt; border: grey 1px solid; padding: 2px;
|
||||||
margin: 0 2px 0 4px}
|
margin: 0 2px 0 4px; font-family: monospace }
|
||||||
|
|
Loading…
Reference in a new issue