[gen] More work on string multilingual fields.
This commit is contained in:
		
							parent
							
								
									6770d23a50
								
							
						
					
					
						commit
						4131ba899e
					
				
					 5 changed files with 210 additions and 87 deletions
				
			
		|  | @ -63,7 +63,7 @@ class Field: | ||||||
|              value=not isSearch and \ |              value=not isSearch and \ | ||||||
|                    field.getFormattedValue(zobj, rawValue, showChanges); |                    field.getFormattedValue(zobj, rawValue, showChanges); | ||||||
|              requestValue=not isSearch and zobj.getRequestFieldValue(name); |              requestValue=not isSearch and zobj.getRequestFieldValue(name); | ||||||
|              inRequest=req.has_key(name); |              inRequest=field.valueIsInRequest(req, name); | ||||||
|              error=req.get('%s_error' % name); |              error=req.get('%s_error' % name); | ||||||
|              isMultiple=(field.multiplicity[1] == None) or \ |              isMultiple=(field.multiplicity[1] == None) or \ | ||||||
|                         (field.multiplicity[1] > 1); |                         (field.multiplicity[1] > 1); | ||||||
|  | @ -544,6 +544,14 @@ class Field: | ||||||
|             return res |             return res | ||||||
|         return value |         return value | ||||||
| 
 | 
 | ||||||
|  |     def valueIsInRequest(self, request, name): | ||||||
|  |         '''Is there a value corresponding to this field in the request? p_name | ||||||
|  |            can be different from self.name (ie, if it is a field within another | ||||||
|  |            (List) field). In most cases, checking that this p_name is in the | ||||||
|  |            request is sufficient. But in some cases it may be more complex, ie | ||||||
|  |            for string multilingual fields.''' | ||||||
|  |         return request.has_key(name) | ||||||
|  | 
 | ||||||
|     def getRequestValue(self, request, requestName=None): |     def getRequestValue(self, request, requestName=None): | ||||||
|         '''Gets a value for this field as carried in the request object. In the |         '''Gets a value for this field as carried in the request object. In the | ||||||
|            simplest cases, the request value is a single value whose name in the |            simplest cases, the request value is a single value whose name in the | ||||||
|  | @ -601,6 +609,18 @@ class Field: | ||||||
|         '''Returns True if the p_value must be considered as an empty value.''' |         '''Returns True if the p_value must be considered as an empty value.''' | ||||||
|         return value in self.nullValues |         return value in self.nullValues | ||||||
| 
 | 
 | ||||||
|  |     def isCompleteValue(self, value, obj=None): | ||||||
|  |         '''Returns True if the p_value must be considered as "complete". While, | ||||||
|  |            in most cases, a "complete" value simply means a "non empty" value | ||||||
|  |            (see m_isEmptyValue above), in some special cases it is more subtle. | ||||||
|  |            For example, a multilingual string value is not empty as soon as a | ||||||
|  |            value is given for some language but will not be considered as | ||||||
|  |            complete while a value is missing for some language. Another example: | ||||||
|  |            a Date with the "hour" part required will not be considered as empty | ||||||
|  |            if the "day, month, year" part is present but will not be considered | ||||||
|  |            as complete without the "hour, minute" part.''' | ||||||
|  |         return not self.isEmptyValue(value, obj) | ||||||
|  | 
 | ||||||
|     def validateValue(self, obj, value): |     def validateValue(self, obj, value): | ||||||
|         '''This method may be overridden by child classes and will be called at |         '''This method may be overridden by child classes and will be called at | ||||||
|            the right moment by m_validate defined below for triggering |            the right moment by m_validate defined below for triggering | ||||||
|  | @ -623,8 +643,8 @@ class Field: | ||||||
|            m_getRequestValue defined above, is valid according to this type |            m_getRequestValue defined above, is valid according to this type | ||||||
|            definition. If it is the case, None is returned. Else, a translated |            definition. If it is the case, None is returned. Else, a translated | ||||||
|            error message is returned.''' |            error message is returned.''' | ||||||
|         # Check that a value is given if required. |         # If the value is required, check that a (complete) value is present. | ||||||
|         if self.isEmptyValue(value, obj): |         if not self.isCompleteValue(value, obj): | ||||||
|             if self.required and self.isClientVisible(obj): |             if self.required and self.isClientVisible(obj): | ||||||
|                 # If the field is required, but not visible according to |                 # If the field is required, but not visible according to | ||||||
|                 # master/slave relationships, we consider it not to be required. |                 # master/slave relationships, we consider it not to be required. | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ class Boolean(Field): | ||||||
|     cLayouts = {'view': 'lf|', 'edit': 'flrv|'} |     cLayouts = {'view': 'lf|', 'edit': 'flrv|'} | ||||||
|     # Layout for radio buttons (render = "radios") |     # Layout for radio buttons (render = "radios") | ||||||
|     rLayouts = {'edit': 'f', 'view': 'f', 'search': 'l-f'} |     rLayouts = {'edit': 'f', 'view': 'f', 'search': 'l-f'} | ||||||
|  |     rlLayouts = {'edit': 'l-f', 'view': 'lf', 'search': 'l-f'} | ||||||
| 
 | 
 | ||||||
|     pxView = pxCell = Px('''<x>:value</x> |     pxView = pxCell = Px('''<x>:value</x> | ||||||
|      <input type="hidden" if="masterCss" |      <input type="hidden" if="masterCss" | ||||||
|  |  | ||||||
							
								
								
									
										260
									
								
								fields/string.py
									
										
									
									
									
								
							
							
						
						
									
										260
									
								
								fields/string.py
									
										
									
									
									
								
							|  | @ -77,6 +77,13 @@ class String(Field): | ||||||
|     URL = c('(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,5})?' \ |     URL = c('(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,5})?' \ | ||||||
|             '(([0-9]{1,5})?\/.*)?') |             '(([0-9]{1,5})?\/.*)?') | ||||||
| 
 | 
 | ||||||
|  |     # Possible values for "format" | ||||||
|  |     LINE = 0 | ||||||
|  |     TEXT = 1 | ||||||
|  |     XHTML = 2 | ||||||
|  |     PASSWORD = 3 | ||||||
|  |     CAPTCHA = 4 | ||||||
|  | 
 | ||||||
|     pxView = Px(''' |     pxView = Px(''' | ||||||
|      <x var="fmt=field.format; isUrl=field.isUrl; |      <x var="fmt=field.format; isUrl=field.isUrl; | ||||||
|              mayAjaxEdit=not showChanges and field.inlineEdit and \ |              mayAjaxEdit=not showChanges and field.inlineEdit and \ | ||||||
|  | @ -101,16 +108,45 @@ class String(Field): | ||||||
|        <div if="not mayAjaxEdit" class="xhtml">::value or '-'</div> |        <div if="not mayAjaxEdit" class="xhtml">::value or '-'</div> | ||||||
|        <div if="mayAjaxEdit" class="xhtml" contenteditable="true" |        <div if="mayAjaxEdit" class="xhtml" contenteditable="true" | ||||||
|             id=":'%s_%s_ck' % (zobj.id, name)">::value or '-'</div> |             id=":'%s_%s_ck' % (zobj.id, name)">::value or '-'</div> | ||||||
|        <script if="mayAjaxEdit">::field.getJsInlineInit(zobj)</script> |        <script if="mayAjaxEdit">::field.getJsInlineInit(zobj,None)</script> | ||||||
|       </x> |       </x> | ||||||
|       <span if="not value and (fmt != 2)" class="smaller">-</span> |       <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), | ||||||
|  |     # String.PASSWORD and String.CAPTCHA. | ||||||
|  |     pxEditText = Px(''' | ||||||
|  |      <input var="inputId=not lg and name or '%s_%s' % (name, lg); | ||||||
|  |                  placeholder=field.getAttribute(obj, 'placeholder') or ''" | ||||||
|  |             id=":inputId" name=":inputId" size=":field.width" | ||||||
|  |             maxlength=":field.maxChars" placeholder=":placeholder" | ||||||
|  |             value=":inRequest and requestValue or value" | ||||||
|  |             style=":'text-transform:%s' % field.transform" | ||||||
|  |             type=":(fmt == 3) and 'password' or 'text'"/> | ||||||
|  |      <!-- Display a captcha if required --> | ||||||
|  |      <span if="fmt == 4">:_('captcha_text', \ | ||||||
|  |                             mapping=field.getCaptchaChallenge(req.SESSION)) | ||||||
|  |      </span>''') | ||||||
|  | 
 | ||||||
|  |     # pxEdit part for formats String.TEXT and String.XHTML. | ||||||
|  |     pxEditTextArea = Px(''' | ||||||
|  |      <textarea var="inputId=not lg and name or '%s_%s' % (name, lg)" | ||||||
|  |                id=":inputId" name=":inputId" cols=":field.width" | ||||||
|  |                class=":(fmt == 2) and ('rich_%s' % name) or ''" | ||||||
|  |                style=":'text-transform:%s' % field.transform" | ||||||
|  |                rows=":field.height">:inRequest and requestValue or value | ||||||
|  |      </textarea> | ||||||
|  |      <script if="fmt == 2" | ||||||
|  |              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; | ||||||
|              isOneLine=fmt in (0,3,4)"> |              multilingual=field.isMultilingual()"> | ||||||
|       <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)" | ||||||
|  | @ -122,28 +158,22 @@ 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> | ||||||
|       <x if="isOneLine and not field.isSelect" |       <!-- Any other unilingual field. --> | ||||||
|          var2="placeholder=field.getAttribute(obj, 'placeholder') or ''"> |       <x if="not field.isSelect and not multilingual" | ||||||
|        <input id=":name" name=":name" size=":field.width" |          var2="lg=None">:field.editPx[fmt]</x> | ||||||
|               maxlength=":field.maxChars" placeholder=":placeholder" |       <!-- Any other multilingual field. --> | ||||||
|               value=":inRequest and requestValue or value" |       <table if="not field.isSelect and multilingual" width="100%"> | ||||||
|               style=":'text-transform:%s' % field.transform" |        <tr valign="top"> | ||||||
|               type=":(fmt == 3) and 'password' or 'text'"/> |         <x for="lg in field.languages"> | ||||||
|        <!-- Display a captcha if required --> |          <td style=":(fmt==2) and 'padding-top:1px' or 'padding-top:3px'" | ||||||
|        <span if="fmt == 4">:_('captcha_text', \ |              width="25px"> | ||||||
|                               mapping=field.getCaptchaChallenge(req.SESSION)) |           <span class="language help" | ||||||
|        </span> |                 title=":ztool.getLanguageName(lg)">:lg.upper()</span></td> | ||||||
|  |          <td var="requestValue=requestValue[lg]; | ||||||
|  |                   value=value[lg]|''">:field.editPx[fmt]</td> | ||||||
|         </x> |         </x> | ||||||
|       <x if="fmt in (1,2)"> |        </tr> | ||||||
|        <textarea id=":name" name=":name" cols=":field.width" |       </table></x>''') | ||||||
|                  class=":(fmt == 2) and ('rich_%s' % name) or ''" |  | ||||||
|                  style=":'text-transform:%s' % field.transform" |  | ||||||
|                  rows=":field.height">:inRequest and requestValue or value |  | ||||||
|        </textarea> |  | ||||||
|        <script if="fmt == 2" |  | ||||||
|                type="text/javascript">::field.getJsInit(zobj)</script> |  | ||||||
|       </x> |  | ||||||
|      </x>''') |  | ||||||
| 
 | 
 | ||||||
|     pxCell = Px(''' |     pxCell = Px(''' | ||||||
|      <x var="multipleValues=value and isMultiple"> |      <x var="multipleValues=value and isMultiple"> | ||||||
|  | @ -285,12 +315,6 @@ class String(Field): | ||||||
|             if not alpha.match(c): return False |             if not alpha.match(c): return False | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|     # Possible values for "format" |  | ||||||
|     LINE = 0 |  | ||||||
|     TEXT = 1 |  | ||||||
|     XHTML = 2 |  | ||||||
|     PASSWORD = 3 |  | ||||||
|     CAPTCHA = 4 |  | ||||||
|     def __init__(self, validator=None, multiplicity=(0,1), default=None, |     def __init__(self, validator=None, multiplicity=(0,1), default=None, | ||||||
|                  format=LINE, show=True, page='main', group=None, layouts=None, |                  format=LINE, show=True, page='main', group=None, layouts=None, | ||||||
|                  move=0, indexed=False, searchable=False, |                  move=0, indexed=False, searchable=False, | ||||||
|  | @ -300,7 +324,7 @@ 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, contentLanguage=None, inlineEdit=False): |                  spellcheck=False, languages=('en',), 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 | ||||||
|  | @ -314,8 +338,11 @@ class String(Field): | ||||||
|         self.allowImageUpload = allowImageUpload |         self.allowImageUpload = allowImageUpload | ||||||
|         # When format is XHTML, do we run the CK spellchecker ? |         # When format is XHTML, do we run the CK spellchecker ? | ||||||
|         self.spellcheck = spellcheck |         self.spellcheck = spellcheck | ||||||
|         # What is the language of field content? |         # If "languages" holds more than one language, the field will be | ||||||
|         self.contentLanguage = contentLanguage |         # multi-lingual and several widgets will allow to edit/visualize the | ||||||
|  |         # field content in all the supported languages. The field is also used | ||||||
|  |         # by the CK spell checker. | ||||||
|  |         self.languages = languages | ||||||
|         # 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 | ||||||
|  | @ -361,6 +388,12 @@ class String(Field): | ||||||
|                           not self.isSelect |                           not self.isSelect | ||||||
|         self.swidth = self.swidth or self.width |         self.swidth = self.swidth or self.width | ||||||
|         self.sheight = self.sheight or self.height |         self.sheight = self.sheight or self.height | ||||||
|  |         self.checkParameters() | ||||||
|  | 
 | ||||||
|  |     def checkParameters(self): | ||||||
|  |         '''Ensures this String is correctly defined.''' | ||||||
|  |         if self.isSelect and self.isMultilingual(): | ||||||
|  |             raise Exception("A selection field can't be multilingual.") | ||||||
| 
 | 
 | ||||||
|     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 | ||||||
|  | @ -376,6 +409,8 @@ class String(Field): | ||||||
|                 res = False |                 res = False | ||||||
|         return res |         return res | ||||||
| 
 | 
 | ||||||
|  |     def isMultilingual(self): return len(self.languages) > 1 | ||||||
|  | 
 | ||||||
|     def getDefaultLayouts(self): |     def getDefaultLayouts(self): | ||||||
|         '''Returns the default layouts for this type. Default layouts can vary |         '''Returns the default layouts for this type. Default layouts can vary | ||||||
|            acccording to format, multiplicity or history.''' |            acccording to format, multiplicity or history.''' | ||||||
|  | @ -394,7 +429,7 @@ class String(Field): | ||||||
|             return {'view': 'l-f', 'edit': 'lrv-f'} |             return {'view': 'l-f', 'edit': 'lrv-f'} | ||||||
| 
 | 
 | ||||||
|     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() | ||||||
|         value = Field.getValue(self, obj) |         value = Field.getValue(self, obj) | ||||||
|         if not value: |         if not value: | ||||||
|  | @ -406,27 +441,46 @@ class String(Field): | ||||||
|             value = list(value) |             value = list(value) | ||||||
|         return value |         return value | ||||||
| 
 | 
 | ||||||
|     def store(self, obj, value): |     def valueIsInRequest(self, request, name): | ||||||
|         '''When the value is XHTML, we perform some cleanup.''' |         if not self.isMultilingual(): | ||||||
|         if not self.persist: return |             return Field.valueIsInRequest(self, request, name) | ||||||
|         if (self.format == String.XHTML) and value: |         return request.has_key('%s_%s' % (name, self.languages[0])) | ||||||
|             # When image upload is allowed, ckeditor inserts some "style" attrs |  | ||||||
|             # (ie for image size when images are resized). So in this case we |  | ||||||
|             # can't remove style-related information. |  | ||||||
|             try: |  | ||||||
|                 value = XhtmlCleaner(keepStyles=False).clean(value) |  | ||||||
|             except XhtmlCleaner.Error, e: |  | ||||||
|                 # Errors while parsing p_value can't prevent the user from |  | ||||||
|                 # storing it. |  | ||||||
|                 obj.log('Unparsable XHTML content in field "%s".' % self.name, |  | ||||||
|                         type='warning') |  | ||||||
|         Field.store(self, obj, value) |  | ||||||
| 
 | 
 | ||||||
|     def storeFromAjax(self, obj): |     def getRequestValue(self, request, requestName=None): | ||||||
|         '''Stores the new field value from an Ajax request, or do nothing if |         '''The request value may be multilingual.''' | ||||||
|            the action was canceled.''' |         name = requestName or self.name | ||||||
|         rq = obj.REQUEST |         # A unilingual field. | ||||||
|         if rq.get('cancel') != 'True': self.store(obj, rq['fieldContent']) |         if not self.isMultilingual(): return request.get(name, None) | ||||||
|  |         # A multilingual field. | ||||||
|  |         res = {} | ||||||
|  |         for language in self.languages: | ||||||
|  |             res[language] = request.get('%s_%s' % (name, language), None) | ||||||
|  |         return res | ||||||
|  | 
 | ||||||
|  |     def isEmptyValue(self, value, obj=None): | ||||||
|  |         '''Returns True if the p_value must be considered as an empty value.''' | ||||||
|  |         if not self.isMultilingual(): | ||||||
|  |             return Field.isEmptyValue(self, value, obj) | ||||||
|  |         # For a multilingual value, as soon as a value is not empty for a given | ||||||
|  |         # language, the whole value is considered as not being empty. | ||||||
|  |         if not value: return True | ||||||
|  |         for v in value.itervalues(): | ||||||
|  |             if not Field.isEmptyValue(self, v, obj): return | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def isCompleteValue(self, value, obj=None): | ||||||
|  |         '''Returns True if the p_value must be considered as complete. For a | ||||||
|  |            unilingual field, being complete simply means not being empty. For a | ||||||
|  |            multilingual field, being complete means that a value is present for | ||||||
|  |            every language''' | ||||||
|  |         if not self.isMultilingual(): | ||||||
|  |             return Field.isCompleteValue(self, value, obj) | ||||||
|  |         # As soon as a given language value is empty, the global value is not | ||||||
|  |         # complete. | ||||||
|  |         if not value: return True | ||||||
|  |         for v in value.itervalues(): | ||||||
|  |             if Field.isEmptyValue(self, v, obj): return | ||||||
|  |         return True | ||||||
| 
 | 
 | ||||||
|     def getDiffValue(self, obj, value): |     def getDiffValue(self, obj, value): | ||||||
|         '''Returns a version of p_value that includes the cumulative diffs |         '''Returns a version of p_value that includes the cumulative diffs | ||||||
|  | @ -452,8 +506,8 @@ class String(Field): | ||||||
|         comparator = HtmlDiff(res, value or '', iMsg, dMsg) |         comparator = HtmlDiff(res, value or '', iMsg, dMsg) | ||||||
|         return comparator.get() |         return comparator.get() | ||||||
| 
 | 
 | ||||||
|     def getFormattedValue(self, obj, value, showChanges=False): |     def getUnilingualFormattedValue(self, obj, value, showChanges=False): | ||||||
|         if self.isEmptyValue(value): return '' |         if Field.isEmptyValue(self, value): return '' | ||||||
|         res = value |         res = value | ||||||
|         if self.isSelect: |         if self.isSelect: | ||||||
|             if isinstance(self.validator, Selection): |             if isinstance(self.validator, Selection): | ||||||
|  | @ -480,6 +534,17 @@ class String(Field): | ||||||
|            (res.startswith('\n') or res.startswith('\r\n')): res = ' ' + res |            (res.startswith('\n') or res.startswith('\r\n')): res = ' ' + res | ||||||
|         return res |         return res | ||||||
| 
 | 
 | ||||||
|  |     def getFormattedValue(self, obj, value, showChanges=False): | ||||||
|  |         if not self.isMultilingual(): | ||||||
|  |             return self.getUnilingualFormattedValue(obj, value, showChanges) | ||||||
|  |         # Return the dict of values whose individual, language-specific values | ||||||
|  |         # have been formatted via m_getUnilingualFormattedValue. | ||||||
|  |         if not value: return value | ||||||
|  |         res = {} | ||||||
|  |         for lg in self.languages: | ||||||
|  |             res[lg]=self.getUnilingualFormattedValue(obj,value[lg],showChanges) | ||||||
|  |         return res | ||||||
|  | 
 | ||||||
|     emptyStringTuple = ('',) |     emptyStringTuple = ('',) | ||||||
|     emptyValuesCatalogIgnored = (None, '') |     emptyValuesCatalogIgnored = (None, '') | ||||||
|     def getIndexValue(self, obj, forSearch=False): |     def getIndexValue(self, obj, forSearch=False): | ||||||
|  | @ -623,12 +688,23 @@ class String(Field): | ||||||
|         elif self.transform == 'capitalize': return value.capitalize() |         elif self.transform == 'capitalize': return value.capitalize() | ||||||
|         return value |         return value | ||||||
| 
 | 
 | ||||||
|     def getStorableValue(self, value): |     def getUnilingualStorableValue(self, value): | ||||||
|         isString = isinstance(value, basestring) |         isString = isinstance(value, basestring) | ||||||
|  |         isEmpty = Field.isEmptyValue(self, value) | ||||||
|         # Apply transform if required |         # Apply transform if required | ||||||
|         if isString and not self.isEmptyValue(value) and \ |         if isString and not isEmpty and (self.transform != 'none'): | ||||||
|            (self.transform != 'none'): |  | ||||||
|            value = self.applyTransform(value) |            value = self.applyTransform(value) | ||||||
|  |         # Clean XHTML if format is XHTML | ||||||
|  |         if (self.format == String.XHTML) and not isEmpty: | ||||||
|  |             # When image upload is allowed, ckeditor inserts some "style" attrs | ||||||
|  |             # (ie for image size when images are resized). So in this case we | ||||||
|  |             # can't remove style-related information. | ||||||
|  |             try: | ||||||
|  |                 value = XhtmlCleaner(keepStyles=False).clean(value) | ||||||
|  |             except XhtmlCleaner.Error, e: | ||||||
|  |                 # Errors while parsing p_value can't prevent the user from | ||||||
|  |                 # storing it. | ||||||
|  |                 pass | ||||||
|         # 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] | ||||||
|  | @ -638,6 +714,33 @@ class String(Field): | ||||||
|             value = [value] |             value = [value] | ||||||
|         return value |         return value | ||||||
| 
 | 
 | ||||||
|  |     def getStorableValue(self, value): | ||||||
|  |         if not self.isMultilingual(): | ||||||
|  |             return self.getUnilingualStorableValue(value) | ||||||
|  |         # A multilingual value is stored as a dict whose keys are ISO 2-letters | ||||||
|  |         # language codes and whose values are strings storing content in the | ||||||
|  |         # language ~{s_language: s_content}~. | ||||||
|  |         if not value: return | ||||||
|  |         for lg in self.languages: | ||||||
|  |             value[lg] = self.getUnilingualStorableValue(value[lg]) | ||||||
|  |         return value | ||||||
|  | 
 | ||||||
|  |     def store(self, obj, value): | ||||||
|  |         '''Stores p_value on p_obj for this field.''' | ||||||
|  |         if self.isMultilingual() and value and \ | ||||||
|  |            (not isinstance(value, dict) or (len(value) != len(self.languages))): | ||||||
|  |             raise Exception('Multilingual field "%s" accepts a dict whose '\ | ||||||
|  |                             'keys are in field.languages and whose ' \ | ||||||
|  |                             'values are strings.' % self.name) | ||||||
|  |         Field.store(self, obj, value) | ||||||
|  | 
 | ||||||
|  |     def storeFromAjax(self, obj): | ||||||
|  |         '''Stores the new field value from an Ajax request, or do nothing if | ||||||
|  |            the action was canceled.''' | ||||||
|  |         rq = obj.REQUEST | ||||||
|  |         if rq.get('cancel') != 'True': | ||||||
|  |             self.store(obj, self.getStorableValue(rq['fieldContent'])) | ||||||
|  | 
 | ||||||
|     def getIndexType(self): |     def getIndexType(self): | ||||||
|         '''Index type varies depending on String parameters.''' |         '''Index type varies depending on String parameters.''' | ||||||
|         # If String.isSelect, be it multivalued or not, we define a ListIndex: |         # If String.isSelect, be it multivalued or not, we define a ListIndex: | ||||||
|  | @ -679,19 +782,19 @@ class String(Field): | ||||||
|                    'fi': 'fi_FI', 'fr': 'fr_FR', 'de': 'de_DE', 'el': 'el_GR', |                    'fi': 'fi_FI', 'fr': 'fr_FR', 'de': 'de_DE', 'el': 'el_GR', | ||||||
|                    'it': 'it_IT', 'nb': 'nb_NO', 'pt': 'pt_PT', 'es': 'es_ES', |                    'it': 'it_IT', 'nb': 'nb_NO', 'pt': 'pt_PT', 'es': 'es_ES', | ||||||
|                    'sv': 'sv_SE'} |                    'sv': 'sv_SE'} | ||||||
|     def getCkLanguage(self): |     def getCkLanguage(self, language): | ||||||
|         '''Gets the language for CK editor SCAYT. We will use |         '''Gets the language for CK editor SCAYT. p_language is one of | ||||||
|            self.contentLanguage. If it is not supported by CK, we use |            self.languages if the field is multilingual, None else. If p_language | ||||||
|            english.''' |            is not supported by CK, we use english.''' | ||||||
|         lang = self.contentLanguage |         if not language: language = self.languages[0] | ||||||
|         if lang and (lang in self.ckLanguages): return self.ckLanguages[lang] |         if language in self.ckLanguages: return self.ckLanguages[language] | ||||||
|         return 'en_US' |         return 'en_US' | ||||||
| 
 | 
 | ||||||
|     def getCkParams(self, obj): |     def getCkParams(self, obj, language): | ||||||
|         '''Gets the base params to set on a rich text field.''' |         '''Gets the base params to set on a rich text field.''' | ||||||
|         ckAttrs = {'toolbar': 'Appy', |         ckAttrs = {'toolbar': 'Appy', | ||||||
|                    'format_tags': ';'.join(self.styles), |                    'format_tags': ';'.join(self.styles), | ||||||
|                    'scayt_sLang': self.getCkLanguage()} |                    'scayt_sLang': self.getCkLanguage(language)} | ||||||
|         if self.width: ckAttrs['width'] = self.width |         if self.width: ckAttrs['width'] = self.width | ||||||
|         if self.spellcheck: ckAttrs['scayt_autoStartup'] = True |         if self.spellcheck: ckAttrs['scayt_autoStartup'] = True | ||||||
|         if self.allowImageUpload: |         if self.allowImageUpload: | ||||||
|  | @ -704,31 +807,28 @@ class String(Field): | ||||||
|             ck.append('%s: %s' % (k, sv)) |             ck.append('%s: %s' % (k, sv)) | ||||||
|         return ', '.join(ck) |         return ', '.join(ck) | ||||||
| 
 | 
 | ||||||
|     def getJsInit(self, obj): |     def getJsInit(self, obj, language): | ||||||
|         '''Gets the Javascript init code for displaying a rich editor for this |         '''Gets the Javascript init code for displaying a rich editor for this | ||||||
|            field (rich field only).''' |            field (rich field only). If the field is multilingual, we must init | ||||||
|  |            the rich text editor for a given p_language (among self.languages). | ||||||
|  |            Else, p_languages is None.''' | ||||||
|  |         name = not language and self.name or ('%s_%s' % (self.name, language)) | ||||||
|         return 'CKEDITOR.replace("%s", {%s})' % \ |         return 'CKEDITOR.replace("%s", {%s})' % \ | ||||||
|                (self.name, self.getCkParams(obj)) |                (name, self.getCkParams(obj, language)) | ||||||
| 
 | 
 | ||||||
|     def getJsInlineInit(self, obj): |     def getJsInlineInit(self, obj, language): | ||||||
|         '''Gets the Javascript init code for enabling inline edition of this |         '''Gets the Javascript init code for enabling inline edition of this | ||||||
|            field (rich text only).''' |            field (rich text only). If the field is multilingual, the current | ||||||
|  |            p_language is given. Else, p_language is None.''' | ||||||
|         uid = obj.id |         uid = obj.id | ||||||
|  |         name = not language and self.name or ('%s_%s' % (self.name, language)) | ||||||
|         return "CKEDITOR.disableAutoInline = true;\n" \ |         return "CKEDITOR.disableAutoInline = true;\n" \ | ||||||
|                "CKEDITOR.inline('%s_%s_ck', {%s, on: {blur: " \ |                "CKEDITOR.inline('%s_%s_ck', {%s, on: {blur: " \ | ||||||
|                "function( event ) { var content = event.editor.getData(); " \ |                "function( event ) { var content = event.editor.getData(); " \ | ||||||
|                "doInlineSave('%s', '%s', '%s', content)}}})" % \ |                "doInlineSave('%s', '%s', '%s', content)}}})" % \ | ||||||
|                (uid, self.name, self.getCkParams(obj), uid, self.name, |                (uid, name, self.getCkParams(obj, language), uid, name, | ||||||
|                 obj.absolute_url()) |                 obj.absolute_url()) | ||||||
| 
 | 
 | ||||||
|         return "CKEDITOR.disableAutoInline = true;\n" \ |  | ||||||
|                "CKEDITOR.inline('%s_%s_ck', {on: {blur: " \ |  | ||||||
|                "function( event ) { var data = event.editor.getData(); " \ |  | ||||||
|                "askAjaxChunk('%s_%s','POST','%s','%s:pxSave', " \ |  | ||||||
|                "{'fieldContent': encodeURIComponent(data)}, " \ |  | ||||||
|                "null, evalInnerScripts);}}});"% \ |  | ||||||
|                (uid, self.name, uid, self.name, obj.absolute_url(), self.name) |  | ||||||
| 
 |  | ||||||
|     def isSelected(self, obj, fieldName, vocabValue, dbValue): |     def isSelected(self, obj, fieldName, vocabValue, dbValue): | ||||||
|         '''When displaying a selection box (only for fields with a validator |         '''When displaying a selection box (only for fields with a validator | ||||||
|            being a list), must the _vocabValue appear as selected? p_fieldName |            being a list), must the _vocabValue appear as selected? p_fieldName | ||||||
|  |  | ||||||
|  | @ -29,13 +29,13 @@ input[type=submit] { border: 1px solid #d0d0d0; background-color: #f8f8f8; | ||||||
|                      cursor: pointer } |                      cursor: pointer } | ||||||
| input[type=password] { border: 1px solid #d0d0d0; background-color: #f8f8f8; | input[type=password] { border: 1px solid #d0d0d0; background-color: #f8f8f8; | ||||||
|                        font-family: "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif } |                        font-family: "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif } | ||||||
| input[type=text] { border: 1px solid #d0d0d0; background-color: #f8f8f8; | input[type=text] { border: 1px solid #d0d0d0; | ||||||
|                    font-family: "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif; |                    font-family: "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif; | ||||||
|                    margin-bottom: 1px } |                    margin-bottom: 1px } | ||||||
| select { border: 1px solid #d0d0d0; background-color: #f8f8f8 } | select { border: 1px solid #d0d0d0; background-color: #f8f8f8 } | ||||||
| 
 | 
 | ||||||
| textarea { width: 99%; font: 100% "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif; | textarea { width: 99%; font: 100% "Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,Verdana,sans-serif; | ||||||
|            border: 1px solid #d0d0d0; background-color: #f8f8f8 } |            border: 1px solid #d0d0d0; background-color: white } | ||||||
| label { color: #888888; font-size: 11px; margin: 3px 0; | label { color: #888888; font-size: 11px; margin: 3px 0; | ||||||
|         text-transform: uppercase } |         text-transform: uppercase } | ||||||
| legend { padding-bottom: 2px; padding-right: 3px; color: black } | legend { padding-bottom: 2px; padding-right: 3px; color: black } | ||||||
|  | @ -167,3 +167,5 @@ td.search { padding-top: 8px } | ||||||
| .tabs { position:relative; bottom:-2px } | .tabs { position:relative; bottom:-2px } | ||||||
| .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; | ||||||
|  |            margin: 0 2px 0 4px} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay