[fields] computed.py: plainText is now False by default, method can now be a PX [fields] list.py: bugfixes in the validation process; [gen] within aby PX, its context is now available as a special var '_ctx_': to use with caution only for the needs of Appy itself. It is not meant to be used by Appy developers.

This commit is contained in:
Gaetan Delannay 2013-09-20 17:42:07 +02:00
parent 59dc619c7f
commit 6206dbe59c
6 changed files with 48 additions and 51 deletions

View file

@ -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

View file

@ -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)

View file

@ -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.'''

View file

@ -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

View file

@ -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(':');

View file

@ -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