:dayName | +|
---|---|
+ + |
+
+ :contextObj.callField(fieldName, \
+ 'getEventName', contextObj, eventType)">
+
+ :event['name']
+ |
+
+ | + |
+ /
+ /
+
+
+ |
+
+ | + |
+ /
+ /
+
+
+ |
+
::_(fieldInfo[1]['labelId']) + | + ++ + | +
---|---|
:value
+ + +
+ |
+
+
+ |
+
+ + + + + | + ++ + | + ++ + | +
:_('no_ref') | +:widget['pxAdd'] | +:widget['pxObjectTitle'] | +
:_('no_ref')
+ +
+
+
|
+
%s
' % res) + # 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 + # is ok. + if isinstance(res, tuple) and not res: res = self.emptyStringTuple + # Ugly catalog: if value is an empty string or None, it keeps the + # previous index value. + if res in self.emptyValuesCatalogIgnored: res = ' ' + return res + + def getPossibleValues(self,obj,withTranslations=False,withBlankValue=False): + '''Returns the list of possible values for this field if it is a + selection field. If p_withTranslations is True, + instead of returning a list of string values, the result is a list + of tuples (s_value, s_translation). If p_withBlankValue is True, a + blank value is prepended to the list, excepted if the type is + multivalued.''' + if not self.isSelect: raise 'This field is not a selection.' + if isinstance(self.validator, Selection): + # We need to call self.methodName for getting the (dynamic) values. + # If methodName begins with _appy_, it is a special Appy method: + # we will call it on the Mixin (=p_obj) directly. Else, it is a + # user method: we will call it on the wrapper (p_obj.appy()). Some + # args can be hidden into p_methodName, separated with stars, + # like in this example: method1*arg1*arg2. Only string params are + # supported. + methodName = self.validator.methodName + # Unwrap parameters if any. + if methodName.find('*') != -1: + elems = methodName.split('*') + methodName = elems[0] + args = elems[1:] + else: + args = () + # On what object must we call the method that will produce the + # values? + if methodName.startswith('tool:'): + obj = obj.getTool() + methodName = methodName[5:] + # Do we need to call the method on the object or on the wrapper? + if methodName.startswith('_appy_'): + exec 'res = obj.%s(*args)' % methodName + else: + exec 'res = obj.appy().%s(*args)' % methodName + if not withTranslations: res = [v[0] for v in res] + elif isinstance(res, list): res = res[:] + else: + # The list of (static) values is directly given in self.validator. + res = [] + for value in self.validator: + label = '%s_list_%s' % (self.labelId, value) + if withTranslations: + res.append( (value, obj.translate(label)) ) + else: + res.append(value) + if withBlankValue and not self.isMultiValued(): + # Create the blank value to insert at the beginning of the list + if withTranslations: + blankValue = ('', obj.translate('choose_a_value')) + else: + blankValue = '' + # Insert the blank value in the result + if isinstance(res, tuple): + res = (blankValue,) + res + else: + res.insert(0, blankValue) + return res + + def validateValue(self, obj, value): + if self.format == String.CAPTCHA: + challenge = obj.REQUEST.SESSION.get('captcha', None) + # Compute the challenge minus the char to remove + i = challenge['number']-1 + text = challenge['text'][:i] + challenge['text'][i+1:] + if value != text: + return obj.translate('bad_captcha') + elif self.isSelect: + # Check that the value is among possible values + possibleValues = self.getPossibleValues(obj) + if isinstance(value, basestring): + error = value not in possibleValues + else: + error = False + for v in value: + if v not in possibleValues: + error = True + break + if error: return obj.translate('bad_select_value') + + accents = {'é':'e','è':'e','ê':'e','ë':'e','à':'a','â':'a','ä':'a', + 'ù':'u','û':'u','ü':'u','î':'i','ï':'i','ô':'o','ö':'o', + 'ç':'c', 'Ç':'C', + 'Ù':'U','Û':'U','Ü':'U','Î':'I','Ï':'I','Ô':'O','Ö':'O', + 'É':'E','È':'E','Ê':'E','Ë':'E','À':'A','Â':'A','Ä':'A'} + def applyTransform(self, value): + '''Applies a transform as required by self.transform on single + value p_value.''' + if self.transform in ('uppercase', 'lowercase'): + # For those transforms, I will remove any accent, because + # (1) 'é'.upper() or 'Ê'.lower() has no effect; + # (2) most of the time, if the user wants to apply such effect, it + # is for ease of data manipulation, so I guess without accent. + for c, n in self.accents.iteritems(): + if c in value: value = value.replace(c, n) + # Apply the transform + if self.transform == 'lowercase': return value.lower() + elif self.transform == 'uppercase': return value.upper() + elif self.transform == 'capitalize': return value.capitalize() + return value + + def getStorableValue(self, value): + isString = isinstance(value, basestring) + # Apply transform if required + if isString and not self.isEmptyValue(value) and \ + (self.transform != 'none'): + value = self.applyTransform(value) + # Truncate the result if longer than self.maxChars + if isString and self.maxChars and (len(value) > self.maxChars): + value = value[:self.maxChars] + # Get a multivalued value if required. + if value and self.isMultiValued() and \ + (type(value) not in sutils.sequenceTypes): + value = [value] + return value + + def getIndexType(self): + '''Index type varies depending on String parameters.''' + # If String.isSelect, be it multivalued or not, we define a ListIndex: + # this way we can use AND/OR operator. + if self.isSelect: + return 'ListIndex' + elif self.format == String.TEXT: + return 'TextIndex' + elif self.format == String.XHTML: + return 'XhtmlIndex' + return Field.getIndexType(self) + + def getJs(self, layoutType, res): + if self.format == String.XHTML: Field.getJs(self, layoutType, res) + + def getCaptchaChallenge(self, session): + '''Returns a Captcha challenge in the form of a dict. At key "text", + value is a string that the user will be required to re-type, but + without 1 character whose position is at key "number". The challenge + is stored in the p_session, for the server-side subsequent check.''' + length = random.randint(5, 9) # The length of the challenge to encode + number = random.randint(1, length) # The position of the char to remove + text = '' # The challenge the user needs to type (minus one char) + for i in range(length): + j = random.randint(0, 1) + chars = (j == 0) and digits or letters + # Choose a char + text += chars[random.randint(0,len(chars)-1)] + res = {'text': text, 'number': number} + session['captcha'] = res + return res + + def generatePassword(self): + '''Generates a password (we recycle here the captcha challenge + generator).''' + return self.getCaptchaChallenge({})['text'] +# ------------------------------------------------------------------------------ diff --git a/gen/__init__.py b/gen/__init__.py index a37c3db..666b9f9 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -1,275 +1,32 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ -import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO, \ - random -from appy import Object -from appy.gen.layout import Table -from appy.gen.layout import defaultFieldLayouts +import types, string from appy.gen.mail import sendNotification -from appy.gen.indexer import defaultIndexes, XhtmlTextExtractor +from appy.gen.indexer import defaultIndexes from appy.gen import utils as gutils -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 import utils as sutils +# ------------------------------------------------------------------------------ +# Import stuff from appy.fields (and from a few other places too). +# This way, when an app gets "from appy.gen import *", everything is available. +# ------------------------------------------------------------------------------ +from appy.fields import Page, Group, Field, Column, No +from appy.fields.action import Action +from appy.fields.boolean import Boolean +from appy.fields.computed import Computed +from appy.fields.date import Date +from appy.fields.file import File +from appy.fields.float import Float +from appy.fields.info import Info +from appy.fields.integer import Integer +from appy.fields.list import List +from appy.fields.pod import Pod +from appy.fields.ref import Ref, autoref +from appy.fields.string import String, Selection +from appy.gen.layout import Table +from appy import Object + # Default Appy permissions ----------------------------------------------------- r, w, d = ('read', 'write', 'delete') -digit = re.compile('[0-9]') -alpha = re.compile('[a-zA-Z0-9]') -letter = re.compile('[a-zA-Z]') -nullValues = (None, '', []) -validatorTypes = (types.FunctionType, types.UnboundMethodType, - type(re.compile(''))) -emptyTuple = () -labelTypes = ('label', 'descr', 'help') - -def initMasterValue(v): - '''Standardizes p_v as a list of strings.''' - if not isinstance(v, bool) and not v: res = [] - elif type(v) not in sutils.sequenceTypes: res = [v] - else: res = v - return [str(v) for v in res] - -# Descriptor classes used for refining descriptions of elements in types -# (pages, groups,...) ---------------------------------------------------------- -class Page: - '''Used for describing a page, its related phase, show condition, etc.''' - subElements = ('save', 'cancel', 'previous', 'next', 'edit') - def __init__(self, name, phase='main', show=True, showSave=True, - showCancel=True, showPrevious=True, showNext=True, - showEdit=True): - self.name = name - self.phase = phase - self.show = show - # When editing the page, must I show the "save" button? - self.showSave = showSave - # When editing the page, must I show the "cancel" button? - self.showCancel = showCancel - # When editing the page, and when a previous page exists, must I show - # the "previous" button? - self.showPrevious = showPrevious - # When editing the page, and when a next page exists, must I show the - # "next" button? - self.showNext = showNext - # When viewing the page, must I show the "edit" button? - self.showEdit = showEdit - - @staticmethod - def get(pageData): - '''Produces a Page instance from p_pageData. User-defined p_pageData - can be: - (a) a string containing the name of the page; - (b) a string containing