[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. representation of the field value coming from the request.
This method computes the real (potentially converted or manipulated This method computes the real (potentially converted or manipulated
in some other way) value as can be stored in the database.''' 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 return value
def getMasterData(self): def getMasterData(self):
@ -572,7 +572,7 @@ class Field:
'''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
type-specific validation. p_value is never empty.''' type-specific validation. p_value is never empty.'''
return None return
def securityCheck(self, obj, value): def securityCheck(self, obj, value):
'''This method performs some security checks on the p_value that '''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, show='view', page='main', group=None, layouts=None, move=0,
indexed=False, searchable=False, specificReadPermission=False, indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, 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, master=None, masterValue=None, focus=False, historized=False,
sync=True, mapping=None, label=None, sdefault='', scolspan=1, sync=True, mapping=None, label=None, sdefault='', scolspan=1,
swidth=None, sheight=None, context={}): 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 self.method = method
# Does field computation produce plain text or XHTML? # Does field computation produce plain text or XHTML?
self.plainText = plainText self.plainText = plainText
if isinstance(method, basestring): if isinstance(method, Px):
# When field computation is done with a macro, we know the result # When field computation is done with a PX, the result is XHTML.
# will be HTML.
self.plainText = False self.plainText = False
# The context is a dict (or method returning a dict) that will be given # If method is a PX, its context can be given in p_context.
# to the macro specified in self.method. If the dict contains key
# "someKey", it will be available to the macro as "options/someKey".
self.context = context self.context = context
Field.__init__(self, None, multiplicity, default, show, page, group, Field.__init__(self, None, multiplicity, default, show, page, group,
layouts, move, indexed, searchable, layouts, move, indexed, searchable,
@ -70,33 +67,15 @@ class Computed(Field):
swidth, sheight) swidth, sheight)
self.validable = False 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): def getValue(self, obj):
'''Computes the value instead of getting it in the database.''' '''Computes the value instead of getting it in the database.'''
if not self.method: return if not self.method: return
if isinstance(self.method, basestring): if isinstance(self.method, Px):
# self.method is a path to a macro that will produce the field value obj = obj.appy()
return self.callMacro(obj, self.method) ctx = {'obj': obj, 'field': self,
'_': obj.translate, 'tool': obj.tool}
ctx.update(self.context)
return self.method(ctx)
else: else:
# self.method is a method that will return the field value # self.method is a method that will return the field value
return self.callMethod(obj, self.method, cache=False) return self.callMethod(obj, self.method, cache=False)

View file

@ -140,17 +140,30 @@ class List(Field):
for v in value: for v in value:
sv = Object() sv = Object()
for name, field in self.fields: 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) res.append(sv)
return res 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 '''Returns the value of inner field named p_name in row number p_i
within the whole list of values p_outerValue.''' within the whole list of values p_outerValue.'''
if i == -1: return '' if i == -1: return ''
if not outerValue: return '' if not outerValue: return ''
if i >= len(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): def getCss(self, layoutType, res):
'''Gets the CSS required by sub-fields if any.''' '''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 field, we add in p_values an entry with the "ready-to-store" field
value.''' value.'''
rq = self.REQUEST rq = self.REQUEST
for appyType in self.getAppyTypes('edit', rq.form.get('page')): for field in self.getAppyTypes('edit', rq.form.get('page')):
if not appyType.validable: continue if not field.validable: continue
value = appyType.getRequestValue(rq) value = field.getRequestValue(rq)
message = appyType.validate(self, value) message = field.validate(self, value)
if message: if message:
setattr(errors, appyType.name, message) setattr(errors, field.name, message)
else: else:
setattr(values, appyType.name, appyType.getStorableValue(value)) setattr(values, field.name, field.getStorableValue(value))
# Validate sub-fields within Lists # Validate sub-fields within Lists
if appyType.type != 'List': continue if field.type != 'List': continue
i = -1 i = -1
for row in value: for row in value:
i += 1 i += 1
for name, field in appyType.fields: for name, subField in field.fields:
message = field.validate(self, getattr(row,name,None)) message = subField.validate(self, getattr(row,name,None))
if message: if message:
setattr(errors, '%s*%d' % (field.name, i), message) setattr(errors, '%s*%d' % (subField.name, i), message)
def interFieldValidation(self, errors, values): def interFieldValidation(self, errors, values):
'''This method is called when individual validation of all fields '''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. '''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 If p_onlyIfSync is True, it returns the value only if appyType can be
retrieved in synchronous mode.''' retrieved in synchronous mode.'''
appyType = self.getAppyType(name) field = self.getAppyType(name)
if not onlyIfSync or (onlyIfSync and appyType.sync[layoutType]): if not onlyIfSync or (onlyIfSync and field.sync[layoutType]):
# We must really get the field value. # 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. # The field is an inner field from a List.
listName, name, i = name.split('*') listName, name, i = name.split('*')
listType = self.getAppyType(listName) 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): def getFormattedFieldValue(self, name, value, showChanges=False):
'''Gets a nice, string representation of p_value which is a value from '''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; appFolder=app.data; url = ztool.getIncludeUrl;
appName=ztool.getAppName(); _=ztool.translate; appName=ztool.getAppName(); _=ztool.translate;
req=ztool.REQUEST; resp=req.RESPONSE; req=ztool.REQUEST; resp=req.RESPONSE;
dummy=setattr(req, 'pxContext', _ctx_);
lang=ztool.getUserLanguage(); q=ztool.quote; lang=ztool.getUserLanguage(); q=ztool.quote;
layoutType=ztool.getLayoutType(); layoutType=ztool.getLayoutType();
showPortlet=ztool.showPortlet(zobj, layoutType); showPortlet=ztool.showPortlet(zobj, layoutType);
@ -589,6 +590,7 @@ class AbstractWrapper(object):
appFolder=app.data; url = ztool.getIncludeUrl; appFolder=app.data; url = ztool.getIncludeUrl;
appName=ztool.getAppName(); _=ztool.translate; appName=ztool.getAppName(); _=ztool.translate;
req=ztool.REQUEST; resp=req.RESPONSE; req=ztool.REQUEST; resp=req.RESPONSE;
dummy=setattr(req, 'pxContext', _ctx_);
lang=ztool.getUserLanguage(); q=ztool.quote; lang=ztool.getUserLanguage(); q=ztool.quote;
action=req.get('action', None); action=req.get('action', None);
px=req['px'].split(':'); px=req['px'].split(':');

View file

@ -90,6 +90,9 @@ class Px:
as is, without re-applying the template (else, an infinite as is, without re-applying the template (else, an infinite
recursion would occur). recursion would occur).
''' '''
# Developer, forget the following line forever.
if '_ctx_' not in context: context['_ctx_'] = context
if self.hook and applyTemplate: if self.hook and applyTemplate:
# Call the template PX, filling the hook with the current PX. # Call the template PX, filling the hook with the current PX.
context[self.hook] = self context[self.hook] = self