diff --git a/fields/__init__.py b/fields/__init__.py index 77a3b98..61cb288 100644 --- a/fields/__init__.py +++ b/fields/__init__.py @@ -555,7 +555,7 @@ class Field: representation of the field value coming from the request. This method computes the real (potentially converted or manipulated in some other way) value as can be stored in the database.''' - if self.isEmptyValue(value): return None + if self.isEmptyValue(value): return return value def getMasterData(self): @@ -572,7 +572,7 @@ class Field: '''This method may be overridden by child classes and will be called at the right moment by m_validate defined below for triggering type-specific validation. p_value is never empty.''' - return None + return def securityCheck(self, obj, value): '''This method performs some security checks on the p_value that diff --git a/fields/computed.py b/fields/computed.py index 0cc876f..37418e8 100644 --- a/fields/computed.py +++ b/fields/computed.py @@ -46,21 +46,18 @@ class Computed(Field): show='view', page='main', group=None, layouts=None, move=0, indexed=False, searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, - maxChars=None, colspan=1, method=None, plainText=True, + maxChars=None, colspan=1, method=None, plainText=False, master=None, masterValue=None, focus=False, historized=False, sync=True, mapping=None, label=None, sdefault='', scolspan=1, swidth=None, sheight=None, context={}): - # The Python method used for computing the field value + # The Python method used for computing the field value, or a PX. self.method = method # Does field computation produce plain text or XHTML? self.plainText = plainText - if isinstance(method, basestring): - # When field computation is done with a macro, we know the result - # will be HTML. + if isinstance(method, Px): + # When field computation is done with a PX, the result is XHTML. self.plainText = False - # The context is a dict (or method returning a dict) that will be given - # to the macro specified in self.method. If the dict contains key - # "someKey", it will be available to the macro as "options/someKey". + # If method is a PX, its context can be given in p_context. self.context = context Field.__init__(self, None, multiplicity, default, show, page, group, layouts, move, indexed, searchable, @@ -70,33 +67,15 @@ class Computed(Field): swidth, sheight) self.validable = False - def callMacro(self, obj, macroPath): - '''Returns the macro corresponding to p_macroPath. The base folder - where we search is "ui".''' - # Get the special page in Appy that allows to call a macro - macroPage = obj.ui.callMacro - # Get, from p_macroPath, the page where the macro lies, and the macro - # name. - names = self.method.split('/') - # Get the page where the macro lies - page = obj.ui - for name in names[:-1]: - page = getattr(page, name) - macroName = names[-1] - # Compute the macro context. - ctx = {'contextObj':obj, 'page':page, 'macroName':macroName} - if callable(self.context): - ctx.update(self.context(obj.appy())) - else: - ctx.update(self.context) - return macroPage(obj, **ctx) - def getValue(self, obj): '''Computes the value instead of getting it in the database.''' if not self.method: return - if isinstance(self.method, basestring): - # self.method is a path to a macro that will produce the field value - return self.callMacro(obj, self.method) + if isinstance(self.method, Px): + obj = obj.appy() + ctx = {'obj': obj, 'field': self, + '_': obj.translate, 'tool': obj.tool} + ctx.update(self.context) + return self.method(ctx) else: # self.method is a method that will return the field value return self.callMethod(obj, self.method, cache=False) diff --git a/fields/list.py b/fields/list.py index db57b84..a4a8635 100644 --- a/fields/list.py +++ b/fields/list.py @@ -140,17 +140,30 @@ class List(Field): for v in value: sv = Object() for name, field in self.fields: - setattr(sv, name, field.getStorableValue(getattr(v, name))) + subValue = getattr(v, name) + try: + setattr(sv, name, field.getStorableValue(subValue)) + except ValueError: + # The value for this field for this specific row is + # incorrect. It can happen in the process of validating the + # whole List field (a call to getStorableValue occurs at + # this time). We don't care about it, because later on we + # will have sub-field specific validation that will also + # detect the error and will prevent storing the wrong value + # in the database. + setattr(sv, name, subValue) res.append(sv) return res - def getInnerValue(self, outerValue, name, i): + def getInnerValue(self, obj, outerValue, name, i): '''Returns the value of inner field named p_name in row number p_i within the whole list of values p_outerValue.''' if i == -1: return '' if not outerValue: return '' if i >= len(outerValue): return '' - return getattr(outerValue[i], name, '') + # Return the value, or a potential default value. + return getattr(outerValue[i], name, '') or \ + self.getField(name).getValue(obj) or '' def getCss(self, layoutType, res): '''Gets the CSS required by sub-fields if any.''' diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index a0b07cc..c9603ab 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -300,23 +300,23 @@ class BaseMixin: field, we add in p_values an entry with the "ready-to-store" field value.''' rq = self.REQUEST - for appyType in self.getAppyTypes('edit', rq.form.get('page')): - if not appyType.validable: continue - value = appyType.getRequestValue(rq) - message = appyType.validate(self, value) + for field in self.getAppyTypes('edit', rq.form.get('page')): + if not field.validable: continue + value = field.getRequestValue(rq) + message = field.validate(self, value) if message: - setattr(errors, appyType.name, message) + setattr(errors, field.name, message) else: - setattr(values, appyType.name, appyType.getStorableValue(value)) + setattr(values, field.name, field.getStorableValue(value)) # Validate sub-fields within Lists - if appyType.type != 'List': continue + if field.type != 'List': continue i = -1 for row in value: i += 1 - for name, field in appyType.fields: - message = field.validate(self, getattr(row,name,None)) + for name, subField in field.fields: + message = subField.validate(self, getattr(row,name,None)) if message: - setattr(errors, '%s*%d' % (field.name, i), message) + setattr(errors, '%s*%d' % (subField.name, i), message) def interFieldValidation(self, errors, values): '''This method is called when individual validation of all fields @@ -638,14 +638,14 @@ class BaseMixin: '''Returns the database value of field named p_name for p_self. If p_onlyIfSync is True, it returns the value only if appyType can be retrieved in synchronous mode.''' - appyType = self.getAppyType(name) - if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]): + field = self.getAppyType(name) + if not onlyIfSync or (onlyIfSync and field.sync[layoutType]): # We must really get the field value. - if '*' not in name: return appyType.getValue(self) + if '*' not in name: return field.getValue(self) # The field is an inner field from a List. listName, name, i = name.split('*') listType = self.getAppyType(listName) - return listType.getInnerValue(outerValue, name, int(i)) + return listType.getInnerValue(self, outerValue, name, int(i)) def getFormattedFieldValue(self, name, value, showChanges=False): '''Gets a nice, string representation of p_value which is a value from diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index 178b377..dc75a03 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -88,6 +88,7 @@ class AbstractWrapper(object): appFolder=app.data; url = ztool.getIncludeUrl; appName=ztool.getAppName(); _=ztool.translate; req=ztool.REQUEST; resp=req.RESPONSE; + dummy=setattr(req, 'pxContext', _ctx_); lang=ztool.getUserLanguage(); q=ztool.quote; layoutType=ztool.getLayoutType(); showPortlet=ztool.showPortlet(zobj, layoutType); @@ -589,6 +590,7 @@ class AbstractWrapper(object): appFolder=app.data; url = ztool.getIncludeUrl; appName=ztool.getAppName(); _=ztool.translate; req=ztool.REQUEST; resp=req.RESPONSE; + dummy=setattr(req, 'pxContext', _ctx_); lang=ztool.getUserLanguage(); q=ztool.quote; action=req.get('action', None); px=req['px'].split(':'); diff --git a/px/__init__.py b/px/__init__.py index 0daf084..b1c3af3 100644 --- a/px/__init__.py +++ b/px/__init__.py @@ -90,6 +90,9 @@ class Px: as is, without re-applying the template (else, an infinite recursion would occur). ''' + # Developer, forget the following line forever. + if '_ctx_' not in context: context['_ctx_'] = context + if self.hook and applyTemplate: # Call the template PX, filling the hook with the current PX. context[self.hook] = self